PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::network::v2::dicom_server_v2 Class Reference

DICOM server using network_system's messaging_server for connection management. More...

#include <dicom_server_v2.h>

Collaboration diagram for kcenon::pacs::network::v2::dicom_server_v2:
Collaboration graph

Public Types

using clock = std::chrono::steady_clock
 
using duration = std::chrono::milliseconds
 
using time_point = clock::time_point
 
using association_established_callback
 Callback type for association established events.
 
using association_closed_callback
 Callback type for association closed events.
 
using error_callback = std::function<void(const std::string& error)>
 Callback type for error events.
 

Public Member Functions

 dicom_server_v2 (const server_config &config)
 Construct server with configuration.
 
 ~dicom_server_v2 ()
 Destructor (stops server if running)
 
 dicom_server_v2 (const dicom_server_v2 &)=delete
 
dicom_server_v2 & operator= (const dicom_server_v2 &)=delete
 
 dicom_server_v2 (dicom_server_v2 &&)=delete
 
dicom_server_v2 & operator= (dicom_server_v2 &&)=delete
 
void register_service (services::scp_service_ptr service)
 Register an SCP service.
 
std::vector< std::string > supported_sop_classes () const
 Get list of supported SOP Class UIDs.
 
Result< std::monostate > start ()
 Start the server.
 
void stop (duration timeout=std::chrono::seconds{30})
 Stop the server gracefully.
 
void wait_for_shutdown ()
 Wait for server shutdown.
 
bool is_running () const noexcept
 Check if server is running.
 
size_t active_associations () const noexcept
 Get number of active associations.
 
server_statistics get_statistics () const
 Get server statistics.
 
const server_config & config () const noexcept
 Get server configuration.
 
void on_association_established (association_established_callback callback)
 Set callback for association established events.
 
void on_association_closed (association_closed_callback callback)
 Set callback for association closed events.
 
void on_error (error_callback callback)
 Set callback for error events.
 
void set_access_control (std::shared_ptr< security::access_control_manager > acm)
 Set the access control manager for RBAC.
 
std::shared_ptr< security::access_control_manager > get_access_control () const noexcept
 Get the access control manager.
 
void set_access_control_enabled (bool enabled)
 Enable or disable access control enforcement.
 
bool is_access_control_enabled () const noexcept
 Check if access control is enabled.
 

Private Member Functions

void on_connection (std::shared_ptr< kcenon::network::interfaces::i_session > session)
 Handle new connection from server.
 
void on_disconnection (std::string_view session_id)
 Handle disconnection notification.
 
void on_receive (std::string_view session_id, const std::vector< uint8_t > &data)
 Handle receive data (forwarded to handler)
 
void on_network_error (std::string_view session_id, std::error_code ec)
 Handle network error.
 
void create_handler (std::shared_ptr< kcenon::network::interfaces::i_session > session)
 Create and register a new handler for a session.
 
void remove_handler (const std::string &session_id)
 Remove handler by session ID.
 
std::shared_ptr< dicom_association_handler > find_handler (const std::string &session_id) const
 Find handler by session ID.
 
void check_idle_timeouts ()
 Check for idle timeouts on handlers.
 
dicom_association_handler::service_map build_service_map () const
 Build service map from registered services.
 
void report_error (const std::string &error)
 Report error through callback.
 

Private Attributes

server_config config_
 Server configuration.
 
std::shared_ptr< kcenon::network::interfaces::i_protocol_server > server_
 network_system's protocol server (via tcp_facade)
 
std::vector< services::scp_service_ptr > services_
 Registered SCP services.
 
std::map< std::string, services::scp_service * > sop_class_to_service_
 Map from SOP Class UID to service (non-owning pointers)
 
std::unordered_map< std::string, std::shared_ptr< dicom_association_handler > > handlers_
 Active association handlers (keyed by session ID)
 
server_statistics stats_
 Server statistics.
 
std::atomic< bool > running_ {false}
 Running flag.
 
std::mutex handlers_mutex_
 Handler mutex (protects handlers_ map)
 
std::mutex services_mutex_
 Service mutex (protects services_ and sop_class_to_service_)
 
std::mutex stats_mutex_
 Statistics mutex.
 
std::condition_variable shutdown_cv_
 Shutdown condition variable.
 
std::mutex shutdown_mutex_
 Shutdown mutex.
 
association_established_callback on_established_cb_
 Association established callback.
 
association_closed_callback on_closed_cb_
 Association closed callback.
 
error_callback on_error_cb_
 Error callback.
 
std::mutex callback_mutex_
 Callback mutex.
 
std::shared_ptr< security::access_control_manager > access_control_
 Access control manager for RBAC.
 
std::atomic< bool > access_control_enabled_ {false}
 Whether access control is enabled.
 
std::mutex acl_mutex_
 Access control mutex.
 

Detailed Description

DICOM server using network_system's messaging_server for connection management.

This class provides the same functionality as dicom_server but uses network_system's messaging_server for TCP connection management. Key benefits include:

  • No manual thread management: Accept loop and I/O handled by ASIO internally
  • Built-in session tracking: Automatic cleanup on disconnect
  • TLS support: Ready for secure DICOM (future enhancement)
  • Proven scalability: ASIO's async model for efficient connection handling

Architecture

└── messaging_server (network_system)
└── messaging_session (per connection)
└── dicom_association_handler (DICOM protocol)
└── scp_service (DIMSE handling)
Bridges network_system sessions with DICOM protocol handling.
DICOM server using network_system's messaging_server for connection management.

Thread Safety

All public methods are thread-safe. The server uses internal mutexes to protect shared state and delegates I/O operations to network_system's thread model.

Migration Path

This class is API-compatible with dicom_server. To migrate:

  1. Replace dicom_server with dicom_server_v2
  2. No changes needed to service registration or callbacks
  3. Feature flag: PACS_USE_NETWORK_SYSTEM_SERVER (when available)
Example: Usage
config.ae_title = "MY_PACS";
config.port = 11112;
// Register services (same API as dicom_server)
server.register_service(std::make_unique<verification_scp>());
server.register_service(std::make_unique<storage_scp>(storage_path));
// Set callbacks (same API as dicom_server)
server.on_association_established([](const std::string& session_id,
const std::string& calling_ae,
const std::string& called_ae) {
std::cout << "Association: " << calling_ae << " -> " << called_ae << '\n';
});
// Start server
auto result = server.start();
if (result.is_err()) {
std::cerr << "Failed to start: " << result.error() << '\n';
return 1;
}
// Server runs until stop() is called
server.wait_for_shutdown();
const server_config & config() const noexcept
Get server configuration.
void register_service(services::scp_service_ptr service)
Register an SCP service.
Configuration for DICOM server.
size_t max_associations
Maximum concurrent associations (0 = unlimited)
uint16_t port
Port to listen on (default: 11112, standard alternate DICOM port)
std::string ae_title
Application Entity Title for this server (16 chars max)

Definition at line 116 of file dicom_server_v2.h.

Member Typedef Documentation

◆ association_closed_callback

Initial value:
std::function<void(const std::string& session_id, bool graceful)>

Callback type for association closed events.

Definition at line 133 of file dicom_server_v2.h.

◆ association_established_callback

Initial value:
std::function<void(const std::string& session_id,
const std::string& calling_ae,
const std::string& called_ae)>

Callback type for association established events.

Definition at line 127 of file dicom_server_v2.h.

◆ clock

using kcenon::pacs::network::v2::dicom_server_v2::clock = std::chrono::steady_clock

Definition at line 122 of file dicom_server_v2.h.

◆ duration

using kcenon::pacs::network::v2::dicom_server_v2::duration = std::chrono::milliseconds

Definition at line 123 of file dicom_server_v2.h.

◆ error_callback

using kcenon::pacs::network::v2::dicom_server_v2::error_callback = std::function<void(const std::string& error)>

Callback type for error events.

Definition at line 137 of file dicom_server_v2.h.

◆ time_point

Definition at line 124 of file dicom_server_v2.h.

Constructor & Destructor Documentation

◆ dicom_server_v2() [1/3]

kcenon::pacs::network::v2::dicom_server_v2::dicom_server_v2 ( const server_config & config)
explicit

Construct server with configuration.

Parameters
configServer configuration

Definition at line 38 of file dicom_server_v2.cpp.

39 : config_(config) {
40 stats_.start_time = clock::now();
42}
server_config config_
Server configuration.
server_statistics stats_
Server statistics.
std::chrono::steady_clock::time_point last_activity
Time of last activity.
std::chrono::steady_clock::time_point start_time
Server start time.

References kcenon::pacs::network::server_statistics::last_activity, kcenon::pacs::network::server_statistics::start_time, and stats_.

◆ ~dicom_server_v2()

kcenon::pacs::network::v2::dicom_server_v2::~dicom_server_v2 ( )

Destructor (stops server if running)

Definition at line 44 of file dicom_server_v2.cpp.

44 {
45 try {
46 if (running_) {
47 stop();
48 }
49 } catch (...) {
50 // Suppress exceptions in destructor to prevent std::terminate
51 }
52}
std::atomic< bool > running_
Running flag.
void stop(duration timeout=std::chrono::seconds{30})
Stop the server gracefully.

References running_, and stop().

Here is the call graph for this function:

◆ dicom_server_v2() [2/3]

kcenon::pacs::network::v2::dicom_server_v2::dicom_server_v2 ( const dicom_server_v2 & )
delete

◆ dicom_server_v2() [3/3]

kcenon::pacs::network::v2::dicom_server_v2::dicom_server_v2 ( dicom_server_v2 && )
delete

Member Function Documentation

◆ active_associations()

size_t kcenon::pacs::network::v2::dicom_server_v2::active_associations ( ) const
nodiscardnoexcept

Get number of active associations.

Returns
Current number of active DICOM associations

Definition at line 285 of file dicom_server_v2.cpp.

285 {
286 std::lock_guard<std::mutex> lock(handlers_mutex_);
287 return handlers_.size();
288}
std::unordered_map< std::string, std::shared_ptr< dicom_association_handler > > handlers_
Active association handlers (keyed by session ID)
std::mutex handlers_mutex_
Handler mutex (protects handlers_ map)

References handlers_, and handlers_mutex_.

Referenced by get_statistics().

Here is the caller graph for this function:

◆ build_service_map()

dicom_association_handler::service_map kcenon::pacs::network::v2::dicom_server_v2::build_service_map ( ) const
nodiscardprivate

Build service map from registered services.

Definition at line 560 of file dicom_server_v2.cpp.

560 {
561 std::lock_guard<std::mutex> lock(services_mutex_);
563}
std::mutex services_mutex_
Service mutex (protects services_ and sop_class_to_service_)
std::map< std::string, services::scp_service * > sop_class_to_service_
Map from SOP Class UID to service (non-owning pointers)

References services_mutex_, and sop_class_to_service_.

Referenced by create_handler().

Here is the caller graph for this function:

◆ check_idle_timeouts()

void kcenon::pacs::network::v2::dicom_server_v2::check_idle_timeouts ( )
private

Check for idle timeouts on handlers.

Definition at line 527 of file dicom_server_v2.cpp.

527 {
528 if (config_.idle_timeout.count() == 0) {
529 return; // No timeout configured
530 }
531
532 auto now = clock::now();
533 std::vector<std::string> timed_out;
534
535 {
536 std::lock_guard<std::mutex> lock(handlers_mutex_);
537 for (const auto& [session_id, handler] : handlers_) {
538 auto idle_duration = std::chrono::duration_cast<std::chrono::seconds>(
539 now - handler->last_activity());
540
541 if (idle_duration >= config_.idle_timeout) {
542 timed_out.push_back(session_id);
543 }
544 }
545 }
546
547 // Stop timed-out handlers
548 for (const auto& session_id : timed_out) {
549 auto handler = find_handler(session_id);
550 if (handler) {
551 handler->stop(false); // Force abort
552 }
553 }
554}
std::shared_ptr< dicom_association_handler > find_handler(const std::string &session_id) const
Find handler by session ID.
std::chrono::seconds idle_timeout
Idle timeout for associations (0 = no timeout)

References config_, find_handler(), handlers_, handlers_mutex_, and kcenon::pacs::network::server_config::idle_timeout.

Here is the call graph for this function:

◆ config()

const server_config & kcenon::pacs::network::v2::dicom_server_v2::config ( ) const
nodiscardnoexcept

Get server configuration.

Returns
Reference to current configuration

Definition at line 297 of file dicom_server_v2.cpp.

297 {
298 return config_;
299}

References config_.

◆ create_handler()

void kcenon::pacs::network::v2::dicom_server_v2::create_handler ( std::shared_ptr< kcenon::network::interfaces::i_session > session)
private

Create and register a new handler for a session.

Definition at line 422 of file dicom_server_v2.cpp.

423 {
424
425#ifdef PACS_WITH_NETWORK_SYSTEM
426 const std::string session_id(session->id());
427#else
428 const std::string session_id;
429 (void)session;
430#endif
431
432 // Build service map for handler
433 auto service_map = build_service_map();
434
435 // Create handler
436 auto handler = std::make_shared<dicom_association_handler>(
437 std::move(session), config_, service_map);
438
439 // Set up access control if configured
440 {
441 std::lock_guard<std::mutex> acl_lock(acl_mutex_);
442 if (access_control_) {
443 handler->set_access_control(access_control_);
444 handler->set_access_control_enabled(access_control_enabled_);
445 }
446 }
447
448 // Set up handler callbacks
449 auto weak_this = std::weak_ptr<dicom_server_v2*>(
450 std::shared_ptr<dicom_server_v2*>(nullptr, [](dicom_server_v2**) {}));
451 // Note: We can't use shared_from_this() since dicom_server_v2 doesn't inherit
452 // from enable_shared_from_this. Instead, capture 'this' directly since the
453 // handler's lifetime is bounded by the server's lifetime.
454
455 handler->set_established_callback(
456 [this](const std::string& sid, const std::string& calling_ae,
457 const std::string& called_ae) {
458 // Update statistics
459 {
460 std::lock_guard<std::mutex> lock(stats_mutex_);
462 stats_.last_activity = clock::now();
463 }
464
465 // Forward to user callback
466 {
467 std::lock_guard<std::mutex> lock(callback_mutex_);
468 if (on_established_cb_) {
469 on_established_cb_(sid, calling_ae, called_ae);
470 }
471 }
472 });
473
474 handler->set_closed_callback(
475 [this](const std::string& sid, bool graceful) {
476 // Forward to user callback
477 {
478 std::lock_guard<std::mutex> lock(callback_mutex_);
479 if (on_closed_cb_) {
480 on_closed_cb_(sid, graceful);
481 }
482 }
483
484 // Remove handler from map
485 remove_handler(sid);
486 });
487
488 handler->set_error_callback(
489 [this](const std::string& /*sid*/, const std::string& error) {
490 report_error(error);
491 });
492
493 // Register handler
494 {
495 std::lock_guard<std::mutex> lock(handlers_mutex_);
496 handlers_[session_id] = handler;
497 }
498
499 // Start handler (begins processing PDUs)
500 handler->start();
501}
std::mutex acl_mutex_
Access control mutex.
void report_error(const std::string &error)
Report error through callback.
association_closed_callback on_closed_cb_
Association closed callback.
std::shared_ptr< security::access_control_manager > access_control_
Access control manager for RBAC.
dicom_association_handler::service_map build_service_map() const
Build service map from registered services.
association_established_callback on_established_cb_
Association established callback.
void remove_handler(const std::string &session_id)
Remove handler by session ID.
std::atomic< bool > access_control_enabled_
Whether access control is enabled.
uint64_t total_associations
Total associations since server start.

References access_control_, access_control_enabled_, acl_mutex_, build_service_map(), callback_mutex_, config_, handlers_, handlers_mutex_, kcenon::pacs::network::server_statistics::last_activity, on_closed_cb_, on_established_cb_, remove_handler(), report_error(), stats_, stats_mutex_, and kcenon::pacs::network::server_statistics::total_associations.

Referenced by on_connection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ find_handler()

std::shared_ptr< dicom_association_handler > kcenon::pacs::network::v2::dicom_server_v2::find_handler ( const std::string & session_id) const
nodiscardprivate

Find handler by session ID.

Definition at line 518 of file dicom_server_v2.cpp.

518 {
519 std::lock_guard<std::mutex> lock(handlers_mutex_);
520 auto it = handlers_.find(session_id);
521 if (it != handlers_.end()) {
522 return it->second;
523 }
524 return nullptr;
525}

References handlers_, and handlers_mutex_.

Referenced by check_idle_timeouts(), on_disconnection(), on_network_error(), and on_receive().

Here is the caller graph for this function:

◆ get_access_control()

std::shared_ptr< security::access_control_manager > kcenon::pacs::network::v2::dicom_server_v2::get_access_control ( ) const
nodiscardnoexcept

Get the access control manager.

Returns
Shared pointer to access control manager, or nullptr if not set

Definition at line 586 of file dicom_server_v2.cpp.

586 {
587 std::lock_guard<std::mutex> lock(acl_mutex_);
588 return access_control_;
589}

References access_control_, and acl_mutex_.

◆ get_statistics()

server_statistics kcenon::pacs::network::v2::dicom_server_v2::get_statistics ( ) const
nodiscard

Get server statistics.

Returns
Current server statistics

Definition at line 290 of file dicom_server_v2.cpp.

290 {
291 std::lock_guard<std::mutex> lock(stats_mutex_);
292 server_statistics result = stats_;
294 return result;
295}
size_t active_associations() const noexcept
Get number of active associations.
size_t active_associations
Currently active associations.

References kcenon::pacs::network::server_statistics::active_associations, active_associations(), stats_, and stats_mutex_.

Here is the call graph for this function:

◆ is_access_control_enabled()

bool kcenon::pacs::network::v2::dicom_server_v2::is_access_control_enabled ( ) const
nodiscardnoexcept

Check if access control is enabled.

Returns
true if access control is enforced

Definition at line 595 of file dicom_server_v2.cpp.

595 {
597}

References access_control_enabled_.

◆ is_running()

bool kcenon::pacs::network::v2::dicom_server_v2::is_running ( ) const
nodiscardnoexcept

Check if server is running.

Returns
true if server is accepting connections

Definition at line 281 of file dicom_server_v2.cpp.

281 {
282 return running_;
283}

References running_.

◆ on_association_closed()

void kcenon::pacs::network::v2::dicom_server_v2::on_association_closed ( association_closed_callback callback)

Set callback for association closed events.

Parameters
callbackFunction to call when an association is closed

Definition at line 310 of file dicom_server_v2.cpp.

310 {
311 std::lock_guard<std::mutex> lock(callback_mutex_);
312 on_closed_cb_ = std::move(callback);
313}

References callback_mutex_, and on_closed_cb_.

◆ on_association_established()

void kcenon::pacs::network::v2::dicom_server_v2::on_association_established ( association_established_callback callback)

Set callback for association established events.

Parameters
callbackFunction to call when an association is established

Definition at line 305 of file dicom_server_v2.cpp.

305 {
306 std::lock_guard<std::mutex> lock(callback_mutex_);
307 on_established_cb_ = std::move(callback);
308}

References callback_mutex_, and on_established_cb_.

◆ on_connection()

void kcenon::pacs::network::v2::dicom_server_v2::on_connection ( std::shared_ptr< kcenon::network::interfaces::i_session > session)
private

Handle new connection from server.

Definition at line 324 of file dicom_server_v2.cpp.

325 {
326
327 if (!running_ || !session) {
328 return;
329 }
330
331 // Check max associations limit
332 {
333 std::lock_guard<std::mutex> lock(handlers_mutex_);
334 if (config_.max_associations > 0 &&
336 // Reject due to resource limit
337 report_error("Max associations limit reached, rejecting connection");
338
339 std::lock_guard<std::mutex> stats_lock(stats_mutex_);
341
342#ifdef PACS_WITH_NETWORK_SYSTEM
343 session->close();
344#endif
345 return;
346 }
347 }
348
349 // Create handler for this session
350 create_handler(std::move(session));
351}
void create_handler(std::shared_ptr< kcenon::network::interfaces::i_session > session)
Create and register a new handler for a session.
uint64_t rejected_associations
Total associations rejected due to limit.

References config_, create_handler(), handlers_, handlers_mutex_, kcenon::pacs::network::server_config::max_associations, kcenon::pacs::network::server_statistics::rejected_associations, report_error(), running_, stats_, and stats_mutex_.

Referenced by start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ on_disconnection()

void kcenon::pacs::network::v2::dicom_server_v2::on_disconnection ( std::string_view session_id)
private

Handle disconnection notification.

Definition at line 353 of file dicom_server_v2.cpp.

353 {
354 // Skip if server is shutting down
355 if (!running_) {
356 return;
357 }
358
359 std::string sid(session_id);
360
361 // Notify handler of disconnection before removing it
362 auto handler = find_handler(sid);
363 if (handler) {
364 handler->handle_disconnect();
365 }
366
367 // Remove handler for this session
368 remove_handler(sid);
369}

References find_handler(), remove_handler(), and running_.

Referenced by start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ on_error()

void kcenon::pacs::network::v2::dicom_server_v2::on_error ( error_callback callback)

Set callback for error events.

Parameters
callbackFunction to call on server errors

Definition at line 315 of file dicom_server_v2.cpp.

315 {
316 std::lock_guard<std::mutex> lock(callback_mutex_);
317 on_error_cb_ = std::move(callback);
318}
error_callback on_error_cb_
Error callback.

References callback_mutex_, and on_error_cb_.

◆ on_network_error()

void kcenon::pacs::network::v2::dicom_server_v2::on_network_error ( std::string_view session_id,
std::error_code ec )
private

Handle network error.

Definition at line 392 of file dicom_server_v2.cpp.

394 {
395
396 // Skip if server is shutting down
397 if (!running_) {
398 return;
399 }
400
401 std::string sid(session_id);
402
403 std::ostringstream oss;
404 oss << "Network error on session " << sid
405 << ": " << ec.message() << " (" << ec.value() << ")";
406 report_error(oss.str());
407
408 // Notify handler of the error
409 auto handler = find_handler(sid);
410 if (handler) {
411 handler->handle_error(ec);
412 }
413
414 // Remove the handler - it will clean itself up
415 remove_handler(sid);
416}

References find_handler(), remove_handler(), report_error(), and running_.

Referenced by start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ on_receive()

void kcenon::pacs::network::v2::dicom_server_v2::on_receive ( std::string_view session_id,
const std::vector< uint8_t > & data )
private

Handle receive data (forwarded to handler)

Definition at line 371 of file dicom_server_v2.cpp.

373 {
374
375 std::string sid(session_id);
376
377 // Find handler and forward data
378 auto handler = find_handler(sid);
379 if (handler) {
380 // Forward data to handler for PDU processing
381 handler->feed_data(data);
382
383 // Update statistics
384 {
385 std::lock_guard<std::mutex> lock(stats_mutex_);
386 stats_.bytes_received += data.size();
387 stats_.last_activity = clock::now();
388 }
389 }
390}
uint64_t bytes_received
Total bytes received.

References kcenon::pacs::network::server_statistics::bytes_received, find_handler(), kcenon::pacs::network::server_statistics::last_activity, stats_, and stats_mutex_.

Referenced by start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ operator=() [1/2]

dicom_server_v2 & kcenon::pacs::network::v2::dicom_server_v2::operator= ( const dicom_server_v2 & )
delete

◆ operator=() [2/2]

dicom_server_v2 & kcenon::pacs::network::v2::dicom_server_v2::operator= ( dicom_server_v2 && )
delete

◆ register_service()

void kcenon::pacs::network::v2::dicom_server_v2::register_service ( services::scp_service_ptr service)

Register an SCP service.

The server takes ownership of the service and routes DIMSE messages to it based on the SOP Classes it supports.

Parameters
serviceThe SCP service to register
Note
Services must be registered before calling start()

Definition at line 58 of file dicom_server_v2.cpp.

58 {
59 if (!service) {
60 return;
61 }
62
63 std::lock_guard<std::mutex> lock(services_mutex_);
64
65 // Register SOP Class mappings
66 for (const auto& sop_class : service->supported_sop_classes()) {
67 sop_class_to_service_[sop_class] = service.get();
68 }
69
70 services_.push_back(std::move(service));
71}
std::vector< services::scp_service_ptr > services_
Registered SCP services.

References services_, services_mutex_, and sop_class_to_service_.

◆ remove_handler()

void kcenon::pacs::network::v2::dicom_server_v2::remove_handler ( const std::string & session_id)
private

Remove handler by session ID.

Definition at line 503 of file dicom_server_v2.cpp.

503 {
504 // Skip if server is shutting down - handlers are cleaned up in stop()
505 if (!running_) {
506 return;
507 }
508
509 std::lock_guard<std::mutex> lock(handlers_mutex_);
510 auto it = handlers_.find(session_id);
511 if (it != handlers_.end()) {
512 // Handler cleans itself up via stop() in destructor
513 handlers_.erase(it);
514 }
515}

References handlers_, handlers_mutex_, and running_.

Referenced by create_handler(), on_disconnection(), and on_network_error().

Here is the caller graph for this function:

◆ report_error()

void kcenon::pacs::network::v2::dicom_server_v2::report_error ( const std::string & error)
private

Report error through callback.

Definition at line 565 of file dicom_server_v2.cpp.

565 {
566 std::lock_guard<std::mutex> lock(callback_mutex_);
567 if (on_error_cb_) {
568 on_error_cb_(error);
569 }
570}

References callback_mutex_, and on_error_cb_.

Referenced by create_handler(), on_connection(), and on_network_error().

Here is the caller graph for this function:

◆ set_access_control()

void kcenon::pacs::network::v2::dicom_server_v2::set_access_control ( std::shared_ptr< security::access_control_manager > acm)

Set the access control manager for RBAC.

When set, the server will validate permissions before allowing DICOM operations. If not set, all operations are allowed.

Parameters
acmShared pointer to access control manager
Note
Should be set before calling start()

Definition at line 576 of file dicom_server_v2.cpp.

577 {
578 std::lock_guard<std::mutex> lock(acl_mutex_);
579 access_control_ = std::move(acm);
580 if (access_control_) {
582 }
583}

References access_control_, access_control_enabled_, and acl_mutex_.

◆ set_access_control_enabled()

void kcenon::pacs::network::v2::dicom_server_v2::set_access_control_enabled ( bool enabled)

Enable or disable access control enforcement.

Parameters
enabledIf true, access control is enforced; if false, all operations allowed

Definition at line 591 of file dicom_server_v2.cpp.

591 {
592 access_control_enabled_ = enabled;
593}

References access_control_enabled_.

◆ start()

Result< std::monostate > kcenon::pacs::network::v2::dicom_server_v2::start ( )
nodiscard

Start the server.

Binds to the configured port and begins accepting connections using network_system's messaging_server.

Returns
Success or error with description

Definition at line 90 of file dicom_server_v2.cpp.

90 {
91 if (running_.exchange(true)) {
92 return error_info("Server already running");
93 }
94
95 // Validate configuration
96 if (config_.ae_title.empty()) {
97 running_ = false;
98 return error_info("AE Title cannot be empty");
99 }
100
101 if (config_.ae_title.length() > AE_TITLE_LENGTH) {
102 running_ = false;
103 return error_info("AE Title exceeds 16 characters");
104 }
105
106 if (config_.port == 0) {
107 running_ = false;
108 return error_info("Invalid port number");
109 }
110
111 // Check that at least one service is registered
112 {
113 std::lock_guard<std::mutex> lock(services_mutex_);
114 if (services_.empty()) {
115 running_ = false;
116 return error_info("No services registered");
117 }
118 }
119
120 // Reset statistics
121 {
122 std::lock_guard<std::mutex> lock(stats_mutex_);
123 stats_.start_time = clock::now();
130 stats_.bytes_sent = 0;
131 }
132
133#ifdef PACS_WITH_NETWORK_SYSTEM
134 try {
135 // Create TCP server via tcp_facade
136 kcenon::network::facade::tcp_facade facade;
137 kcenon::network::facade::tcp_facade::server_config srv_cfg;
138 srv_cfg.server_id = config_.ae_title;
139 srv_cfg.port = config_.port;
140 auto server_result = compat::factory_result(facade.create_server(srv_cfg));
141 if (server_result.is_err()) {
142 running_ = false;
143 return server_result.error();
144 }
145 server_ = std::move(server_result.value());
146
147 // Set up server-level callbacks
148 server_->set_connection_callback(
149 [this](std::shared_ptr<kcenon::network::interfaces::i_session> session) {
150 on_connection(std::move(session));
151 });
152
153 server_->set_disconnection_callback(
154 [this](std::string_view session_id) {
155 on_disconnection(session_id);
156 });
157
158 server_->set_receive_callback(
159 [this](std::string_view session_id,
160 const std::vector<uint8_t>& data) {
161 on_receive(session_id, data);
162 });
163
164 server_->set_error_callback(
165 [this](std::string_view session_id,
166 std::error_code ec) {
167 on_network_error(session_id, ec);
168 });
169
170 // Start the server on configured port
171 auto result = server_->start(config_.port);
172 if (result.is_err()) {
173 running_ = false;
174 server_.reset();
175 return error_info("Failed to start server");
176 }
177
178 return std::monostate{};
179 } catch (const std::exception& e) {
180 running_ = false;
181 if (server_) {
182 server_.reset();
183 }
184 return error_info(std::string("Exception during server start: ") + e.what());
185 } catch (...) {
186 running_ = false;
187 if (server_) {
188 server_.reset();
189 }
190 return error_info("Unknown exception during server start");
191 }
192#else
193 running_ = false;
194 return error_info("dicom_server_v2 requires PACS_WITH_NETWORK_SYSTEM");
195#endif
196}
void on_network_error(std::string_view session_id, std::error_code ec)
Handle network error.
void on_receive(std::string_view session_id, const std::vector< uint8_t > &data)
Handle receive data (forwarded to handler)
std::shared_ptr< kcenon::network::interfaces::i_protocol_server > server_
network_system's protocol server (via tcp_facade)
void on_disconnection(std::string_view session_id)
Handle disconnection notification.
void on_connection(std::shared_ptr< kcenon::network::interfaces::i_session > session)
Handle new connection from server.
auto factory_result(kcenon::common::Result< T > result) -> kcenon::common::Result< T >
constexpr size_t AE_TITLE_LENGTH
AE Title length (fixed 16 characters, space-padded)
Definition pdu_types.h:273
kcenon::common::error_info error_info
Error information type.
Definition result.h:40
uint64_t messages_processed
Total DIMSE messages processed.

References kcenon::pacs::network::server_statistics::active_associations, kcenon::pacs::network::server_config::ae_title, kcenon::pacs::network::AE_TITLE_LENGTH, kcenon::pacs::network::server_statistics::bytes_received, kcenon::pacs::network::server_statistics::bytes_sent, config_, kcenon::pacs::compat::factory_result(), kcenon::pacs::network::server_statistics::last_activity, kcenon::pacs::network::server_statistics::messages_processed, on_connection(), on_disconnection(), on_network_error(), on_receive(), kcenon::pacs::network::server_config::port, kcenon::pacs::network::server_statistics::rejected_associations, running_, server_, services_, services_mutex_, kcenon::pacs::network::server_statistics::start_time, stats_, stats_mutex_, and kcenon::pacs::network::server_statistics::total_associations.

Here is the call graph for this function:

◆ stop()

void kcenon::pacs::network::v2::dicom_server_v2::stop ( duration timeout = std::chrono::seconds{30})

Stop the server gracefully.

Stops accepting new connections and waits for active associations to complete or timeout.

Parameters
timeoutMaximum time to wait for associations to close

Definition at line 198 of file dicom_server_v2.cpp.

198 {
199 if (!running_.exchange(false)) {
200 return; // Already stopped
201 }
202
203#ifdef PACS_WITH_NETWORK_SYSTEM
204 // Phase 1: Stop accepting new connections
205 if (server_) {
206 try {
207 // Stop the server - this closes the acceptor
208 (void)server_->stop();
209 } catch (...) {
210 // Suppress exceptions during server stop
211 }
212 }
213
214 // Phase 2: Wait for handlers to complete gracefully
215 auto deadline = clock::now() + timeout;
216 {
217 std::unique_lock<std::mutex> lock(handlers_mutex_);
218 while (!handlers_.empty() && clock::now() < deadline) {
219 // Release lock while waiting
220 lock.unlock();
221 std::this_thread::sleep_for(std::chrono::milliseconds{100});
222 lock.lock();
223 }
224 }
225
226 // Phase 3: Force stop remaining handlers
227 // Collect handlers first, then stop them without holding the lock
228 // to avoid potential deadlock if handler->stop() triggers callbacks
229 std::vector<std::shared_ptr<dicom_association_handler>> handlers_to_stop;
230 {
231 std::lock_guard<std::mutex> lock(handlers_mutex_);
232 handlers_to_stop.reserve(handlers_.size());
233 for (auto& [session_id, handler] : handlers_) {
234 handlers_to_stop.push_back(handler);
235 }
236 handlers_.clear();
237 }
238
239 // Stop handlers without holding the lock
240 for (auto& handler : handlers_to_stop) {
241 try {
242 handler->stop(false); // Force abort
243 } catch (...) {
244 // Suppress exceptions during handler stop
245 }
246 }
247 handlers_to_stop.clear();
248
249 // Allow any pending callbacks to complete
250 std::this_thread::sleep_for(std::chrono::milliseconds{50});
251
252 // Clear callbacks to break reference cycles before server is destroyed.
253 // The server object itself is kept alive until dicom_server_v2 is destroyed
254 // to avoid use-after-free in the adapter's background I/O threads.
255 if (server_) {
256 server_->set_connection_callback(nullptr);
257 server_->set_disconnection_callback(nullptr);
258 server_->set_receive_callback(nullptr);
259 server_->set_error_callback(nullptr);
260 }
261#else
262 (void)timeout; // Unused without network_system
263#endif
264
265 // Notify shutdown waiters
266 {
267 std::lock_guard<std::mutex> lock(shutdown_mutex_);
268 shutdown_cv_.notify_all();
269 }
270}
std::condition_variable shutdown_cv_
Shutdown condition variable.
constexpr int timeout
Lock timeout exceeded.

References handlers_, handlers_mutex_, running_, server_, shutdown_cv_, and shutdown_mutex_.

Referenced by ~dicom_server_v2().

Here is the caller graph for this function:

◆ supported_sop_classes()

std::vector< std::string > kcenon::pacs::network::v2::dicom_server_v2::supported_sop_classes ( ) const
nodiscard

Get list of supported SOP Class UIDs.

Returns
Vector of all SOP Classes supported by registered services

Definition at line 73 of file dicom_server_v2.cpp.

73 {
74 std::lock_guard<std::mutex> lock(services_mutex_);
75
76 std::vector<std::string> sop_classes;
77 sop_classes.reserve(sop_class_to_service_.size());
78
79 for (const auto& [uid, _] : sop_class_to_service_) {
80 sop_classes.push_back(uid);
81 }
82
83 return sop_classes;
84}
std::string_view uid

References services_mutex_, sop_class_to_service_, and uid.

◆ wait_for_shutdown()

void kcenon::pacs::network::v2::dicom_server_v2::wait_for_shutdown ( )

Wait for server shutdown.

Blocks until the server is stopped (either by calling stop() or due to an error).

Definition at line 272 of file dicom_server_v2.cpp.

272 {
273 std::unique_lock<std::mutex> lock(shutdown_mutex_);
274 shutdown_cv_.wait(lock, [this]() { return !running_; });
275}

References running_, shutdown_cv_, and shutdown_mutex_.

Member Data Documentation

◆ access_control_

std::shared_ptr<security::access_control_manager> kcenon::pacs::network::v2::dicom_server_v2::access_control_
private

Access control manager for RBAC.

Definition at line 396 of file dicom_server_v2.h.

Referenced by create_handler(), get_access_control(), and set_access_control().

◆ access_control_enabled_

std::atomic<bool> kcenon::pacs::network::v2::dicom_server_v2::access_control_enabled_ {false}
private

Whether access control is enabled.

Definition at line 399 of file dicom_server_v2.h.

399{false};

Referenced by create_handler(), is_access_control_enabled(), set_access_control(), and set_access_control_enabled().

◆ acl_mutex_

std::mutex kcenon::pacs::network::v2::dicom_server_v2::acl_mutex_
mutableprivate

Access control mutex.

Definition at line 402 of file dicom_server_v2.h.

Referenced by create_handler(), get_access_control(), and set_access_control().

◆ callback_mutex_

std::mutex kcenon::pacs::network::v2::dicom_server_v2::callback_mutex_
mutableprivate

Callback mutex.

Definition at line 393 of file dicom_server_v2.h.

Referenced by create_handler(), on_association_closed(), on_association_established(), on_error(), and report_error().

◆ config_

server_config kcenon::pacs::network::v2::dicom_server_v2::config_
private

Server configuration.

Definition at line 348 of file dicom_server_v2.h.

Referenced by check_idle_timeouts(), config(), create_handler(), on_connection(), and start().

◆ handlers_

std::unordered_map<std::string, std::shared_ptr<dicom_association_handler> > kcenon::pacs::network::v2::dicom_server_v2::handlers_
private

Active association handlers (keyed by session ID)

Definition at line 360 of file dicom_server_v2.h.

Referenced by active_associations(), check_idle_timeouts(), create_handler(), find_handler(), on_connection(), remove_handler(), and stop().

◆ handlers_mutex_

std::mutex kcenon::pacs::network::v2::dicom_server_v2::handlers_mutex_
mutableprivate

Handler mutex (protects handlers_ map)

Definition at line 369 of file dicom_server_v2.h.

Referenced by active_associations(), check_idle_timeouts(), create_handler(), find_handler(), on_connection(), remove_handler(), and stop().

◆ on_closed_cb_

association_closed_callback kcenon::pacs::network::v2::dicom_server_v2::on_closed_cb_
private

Association closed callback.

Definition at line 387 of file dicom_server_v2.h.

Referenced by create_handler(), and on_association_closed().

◆ on_error_cb_

error_callback kcenon::pacs::network::v2::dicom_server_v2::on_error_cb_
private

Error callback.

Definition at line 390 of file dicom_server_v2.h.

Referenced by on_error(), and report_error().

◆ on_established_cb_

association_established_callback kcenon::pacs::network::v2::dicom_server_v2::on_established_cb_
private

Association established callback.

Definition at line 384 of file dicom_server_v2.h.

Referenced by create_handler(), and on_association_established().

◆ running_

std::atomic<bool> kcenon::pacs::network::v2::dicom_server_v2::running_ {false}
private

◆ server_

std::shared_ptr<kcenon::network::interfaces::i_protocol_server> kcenon::pacs::network::v2::dicom_server_v2::server_
private

network_system's protocol server (via tcp_facade)

Definition at line 351 of file dicom_server_v2.h.

Referenced by start(), and stop().

◆ services_

std::vector<services::scp_service_ptr> kcenon::pacs::network::v2::dicom_server_v2::services_
private

Registered SCP services.

Definition at line 354 of file dicom_server_v2.h.

Referenced by register_service(), and start().

◆ services_mutex_

std::mutex kcenon::pacs::network::v2::dicom_server_v2::services_mutex_
mutableprivate

Service mutex (protects services_ and sop_class_to_service_)

Definition at line 372 of file dicom_server_v2.h.

Referenced by build_service_map(), register_service(), start(), and supported_sop_classes().

◆ shutdown_cv_

std::condition_variable kcenon::pacs::network::v2::dicom_server_v2::shutdown_cv_
private

Shutdown condition variable.

Definition at line 378 of file dicom_server_v2.h.

Referenced by stop(), and wait_for_shutdown().

◆ shutdown_mutex_

std::mutex kcenon::pacs::network::v2::dicom_server_v2::shutdown_mutex_
private

Shutdown mutex.

Definition at line 381 of file dicom_server_v2.h.

Referenced by stop(), and wait_for_shutdown().

◆ sop_class_to_service_

std::map<std::string, services::scp_service*> kcenon::pacs::network::v2::dicom_server_v2::sop_class_to_service_
private

Map from SOP Class UID to service (non-owning pointers)

Definition at line 357 of file dicom_server_v2.h.

Referenced by build_service_map(), register_service(), and supported_sop_classes().

◆ stats_

server_statistics kcenon::pacs::network::v2::dicom_server_v2::stats_
mutableprivate

Server statistics.

Definition at line 363 of file dicom_server_v2.h.

Referenced by create_handler(), dicom_server_v2(), get_statistics(), on_connection(), on_receive(), and start().

◆ stats_mutex_

std::mutex kcenon::pacs::network::v2::dicom_server_v2::stats_mutex_
mutableprivate

Statistics mutex.

Definition at line 375 of file dicom_server_v2.h.

Referenced by create_handler(), get_statistics(), on_connection(), on_receive(), and start().


The documentation for this class was generated from the following files: