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

Bridges network_system sessions with DICOM protocol handling. More...

#include <dicom_association_handler.h>

Inheritance diagram for kcenon::pacs::network::v2::dicom_association_handler:
Inheritance graph
Collaboration diagram for kcenon::pacs::network::v2::dicom_association_handler:
Collaboration graph

Public Types

using session_ptr = std::shared_ptr<kcenon::network::interfaces::i_session>
 
using service_map = std::map<std::string, services::scp_service*>
 
using clock = std::chrono::steady_clock
 
using time_point = clock::time_point
 
using duration = std::chrono::milliseconds
 

Public Member Functions

 dicom_association_handler (session_ptr session, const server_config &config, const service_map &services)
 Construct a handler for a network session.
 
 ~dicom_association_handler ()
 Destructor (stops handler if still running).
 
 dicom_association_handler (const dicom_association_handler &)=delete
 
dicom_association_handleroperator= (const dicom_association_handler &)=delete
 
 dicom_association_handler (dicom_association_handler &&)=delete
 
dicom_association_handleroperator= (dicom_association_handler &&)=delete
 
void start ()
 Start processing the session.
 
void stop (bool graceful=false)
 Stop the handler and close the session.
 
void feed_data (const std::vector< uint8_t > &data)
 Feed received data to the handler.
 
void handle_disconnect ()
 Handle session disconnection.
 
void handle_error (std::error_code ec)
 Handle session error.
 
handler_state state () const noexcept
 Get current handler state.
 
bool is_established () const noexcept
 Check if the association is established.
 
bool is_closed () const noexcept
 Check if the handler is closed.
 
std::string session_id () const
 Get the session identifier.
 
std::string calling_ae () const
 Get the calling AE title.
 
std::string called_ae () const
 Get the called AE title.
 
Result< std::reference_wrapper< association > > get_association ()
 Get the underlying association object.
 
Result< std::reference_wrapper< const association > > get_association () const
 
time_point last_activity () const noexcept
 Get time of last activity.
 
void set_established_callback (association_established_callback callback)
 Set callback for association established event.
 
void set_closed_callback (association_closed_callback callback)
 Set callback for association closed event.
 
void set_error_callback (handler_error_callback callback)
 Set callback for error events.
 
uint64_t pdus_received () const noexcept
 Get number of PDUs received.
 
uint64_t pdus_sent () const noexcept
 Get number of PDUs sent.
 
uint64_t messages_processed () const noexcept
 Get number of DIMSE messages processed.
 
void set_access_control (std::shared_ptr< security::access_control_manager > acm)
 Set the access control manager for RBAC.
 
void set_access_control_enabled (bool enabled)
 Enable or disable access control enforcement.
 

Static Public Attributes

static constexpr size_t pdu_header_size = 6
 PDU header size (type + reserved + length)
 
static constexpr size_t max_pdu_size = 64 * 1024 * 1024
 Maximum PDU size for safety checks.
 

Private Member Functions

void on_data_received (const std::vector< uint8_t > &data)
 Handle data received from session.
 
void on_disconnected (const std::string &session_id)
 Handle session disconnection.
 
void on_error (std::error_code ec)
 Handle session error.
 
void process_buffer ()
 Process accumulated buffer for complete PDUs.
 
void process_pdu (const integration::pdu_data &pdu)
 Process a complete PDU.
 
void handle_associate_rq (const std::vector< uint8_t > &payload)
 Handle A-ASSOCIATE-RQ PDU.
 
void handle_p_data_tf (const std::vector< uint8_t > &payload)
 Handle P-DATA-TF PDU.
 
void handle_release_rq ()
 Handle A-RELEASE-RQ PDU.
 
void handle_abort (const std::vector< uint8_t > &payload)
 Handle A-ABORT PDU.
 
void send_associate_ac ()
 Send A-ASSOCIATE-AC response.
 
void send_associate_rj (reject_result result, uint8_t source, uint8_t reason)
 Send A-ASSOCIATE-RJ response.
 
void send_p_data_tf (const std::vector< uint8_t > &payload)
 Send P-DATA-TF PDU.
 
void send_release_rp ()
 Send A-RELEASE-RP response.
 
void send_abort (abort_source source, abort_reason reason)
 Send A-ABORT PDU.
 
void send_pdu (pdu_type type, const std::vector< uint8_t > &payload)
 Send raw PDU data.
 
Result< std::monostate > dispatch_to_service (uint8_t context_id, const dimse::dimse_message &msg)
 Dispatch DIMSE message to appropriate service.
 
services::scp_servicefind_service (const std::string &sop_class_uid) const
 Find service for SOP Class UID.
 
void transition_to (handler_state new_state)
 Transition to new state.
 
void touch ()
 Update last activity timestamp.
 
void report_error (const std::string &error)
 Report error through callback.
 
void close_handler (bool graceful)
 Close and cleanup.
 

Private Attributes

session_ptr session_
 Network session.
 
server_config config_
 Server configuration.
 
service_map services_
 Service registry (non-owning pointers)
 
association association_
 DICOM association.
 
std::atomic< handler_statestate_ {handler_state::idle}
 Current handler state.
 
std::vector< uint8_t > receive_buffer_
 PDU receive buffer.
 
uint32_t expected_pdu_length_ {0}
 Expected PDU length (0 if waiting for header)
 
pdu_type current_pdu_type_ {pdu_type::abort}
 Current PDU type being received.
 
time_point last_activity_
 Last activity timestamp.
 
std::atomic< uint64_t > pdus_received_ {0}
 Statistics.
 
std::atomic< uint64_t > pdus_sent_ {0}
 
std::atomic< uint64_t > messages_processed_ {0}
 
association_established_callback established_callback_
 Callbacks.
 
association_closed_callback closed_callback_
 
handler_error_callback error_callback_
 
std::mutex mutex_
 Thread safety.
 
std::mutex callback_mutex_
 
std::shared_ptr< security::access_control_manageraccess_control_
 Access control manager for RBAC.
 
std::optional< security::user_contextuser_context_
 User context for this association (set after A-ASSOCIATE negotiation)
 
bool access_control_enabled_ {false}
 Whether access control is enabled.
 

Detailed Description

Bridges network_system sessions with DICOM protocol handling.

This class wraps a network_system messaging_session to provide DICOM-specific behavior including:

  • PDU Framing: Handles the 6-byte PDU header parsing and accumulation of fragmented PDUs from the TCP stream.
  • State Machine: Manages DICOM association states (idle, awaiting, established, releasing, closed).
  • Service Dispatching: Routes DIMSE messages to registered SCP services.
  • Association Negotiation: Handles A-ASSOCIATE-RQ/AC/RJ PDU processing.

Thread Safety

All public methods are thread-safe. The handler can be accessed from multiple threads (e.g., network I/O thread and service threads).

Lifecycle

  1. Construct with a session and server configuration
  2. Call start() to begin processing incoming PDUs
  3. Handle association negotiation automatically
  4. DIMSE messages are dispatched to registered services
  5. Call stop() or let graceful release complete

Definition at line 148 of file dicom_association_handler.h.

Member Typedef Documentation

◆ clock

◆ duration

◆ service_map

◆ session_ptr

◆ time_point

Constructor & Destructor Documentation

◆ dicom_association_handler() [1/3]

kcenon::pacs::network::v2::dicom_association_handler::dicom_association_handler ( session_ptr session,
const server_config & config,
const service_map & services )

Construct a handler for a network session.

Parameters
sessionThe network_system session to wrap
configServer configuration for association negotiation
servicesMap from SOP Class UID to service implementation
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 37 of file dicom_association_handler.cpp.

41 : session_(std::move(session))
42 , config_(config)
43 , services_(services)
44 , last_activity_(clock::now()) {
45}
service_map services_
Service registry (non-owning pointers)

◆ ~dicom_association_handler()

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

Destructor (stops handler if still running).

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 47 of file dicom_association_handler.cpp.

47 {
48 try {
49 // Ensure we're stopped
50 if (!is_closed()) {
51 stop(false); // Force abort if still running
52 }
53 } catch (...) {
54 // Suppress exceptions in destructor to prevent std::terminate
55 }
56}
bool is_closed() const noexcept
Check if the handler is closed.
void stop(bool graceful=false)
Stop the handler and close the session.

References is_closed(), and stop().

Here is the call graph for this function:

◆ dicom_association_handler() [2/3]

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

◆ dicom_association_handler() [3/3]

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

Member Function Documentation

◆ called_ae()

std::string kcenon::pacs::network::v2::dicom_association_handler::called_ae ( ) const
nodiscard

Get the called AE title.

Returns
Called AE title if negotiated, empty otherwise
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 132 of file dicom_association_handler.cpp.

132 {
133 std::lock_guard<std::mutex> lock(mutex_);
134 return std::string(association_.called_ae());
135}
std::string_view called_ae() const noexcept
Get called AE title.

References association_, kcenon::pacs::network::association::called_ae(), and mutex_.

Here is the call graph for this function:

◆ calling_ae()

std::string kcenon::pacs::network::v2::dicom_association_handler::calling_ae ( ) const
nodiscard

Get the calling AE title.

Returns
Calling AE title if negotiated, empty otherwise
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 127 of file dicom_association_handler.cpp.

127 {
128 std::lock_guard<std::mutex> lock(mutex_);
129 return std::string(association_.calling_ae());
130}
std::string_view calling_ae() const noexcept
Get calling AE title.

References association_, kcenon::pacs::network::association::calling_ae(), and mutex_.

Here is the call graph for this function:

◆ close_handler()

void kcenon::pacs::network::v2::dicom_association_handler::close_handler ( bool graceful)
private

Close and cleanup.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 769 of file dicom_association_handler.cpp.

769 {
770 handler_state expected = state_.load();
771 if (expected == handler_state::closed) {
772 return; // Already closed
773 }
774
776
777#ifdef PACS_WITH_NETWORK_SYSTEM
778 // Close the session
779 try {
780 std::lock_guard<std::mutex> lock(mutex_);
781 if (session_) {
782 session_->close();
783 }
784 } catch (...) {
785 // Suppress exceptions during session cleanup
786 }
787#endif
788
789 // Notify closed callback
790 try {
791 std::lock_guard<std::mutex> cb_lock(callback_mutex_);
792 if (closed_callback_) {
793 closed_callback_(session_id(), graceful);
794 }
795 } catch (...) {
796 // Suppress exceptions during callback invocation
797 }
798}
void transition_to(handler_state new_state)
Transition to new state.
std::string session_id() const
Get the session identifier.
std::atomic< handler_state > state_
Current handler state.
@ closed
Association closed (released or aborted)

References callback_mutex_, kcenon::pacs::network::v2::closed, closed_callback_, mutex_, session_, session_id(), state_, and transition_to().

Referenced by handle_abort(), handle_associate_rq(), handle_p_data_tf(), handle_release_rq(), on_disconnected(), on_error(), process_buffer(), process_pdu(), and stop().

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

◆ dispatch_to_service()

Result< std::monostate > kcenon::pacs::network::v2::dicom_association_handler::dispatch_to_service ( uint8_t context_id,
const dimse::dimse_message & msg )
private

Dispatch DIMSE message to appropriate service.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 657 of file dicom_association_handler.cpp.

659 {
660
661 // Find the abstract syntax for this context
662 std::string abstract_syntax;
663 for (const auto& ctx : association_.accepted_contexts()) {
664 if (ctx.id == context_id) {
665 abstract_syntax = ctx.abstract_syntax;
666 break;
667 }
668 }
669
670 if (abstract_syntax.empty()) {
671 return error_info("Unknown presentation context ID");
672 }
673
674 // Find service for this SOP class
676 if (!service) {
677 return error_info("No service registered for SOP Class: " + abstract_syntax);
678 }
679
680 // Perform access control check if enabled
682 return error_info("Access denied: unregistered AE title");
683 }
685 // Map DIMSE command to DICOM operation
687 switch (msg.command()) {
690 break;
693 break;
696 break;
699 break;
702 break;
705 break;
708 break;
711 break;
714 break;
717 break;
720 break;
721 default:
722 // Response messages don't need access control
723 break;
724 }
725
726 // Check permission
727 auto check_result = access_control_->check_dicom_operation(*user_context_, op);
728 if (!check_result) {
729 return error_info("Access denied: " + check_result.reason);
730 }
731 }
732
733 // Dispatch to service
734 return service->handle_message(association_, context_id, msg);
735}
const std::vector< accepted_presentation_context > & accepted_contexts() const noexcept
Get all accepted presentation contexts.
services::scp_service * find_service(const std::string &sop_class_uid) const
Find service for SOP Class UID.
std::shared_ptr< security::access_control_manager > access_control_
Access control manager for RBAC.
bool access_control_enabled_
Whether access control is enabled.
std::optional< security::user_context > user_context_
User context for this association (set after A-ASSOCIATE negotiation)
@ n_get_rq
N-GET Request - Get attribute values.
@ c_store_rq
C-STORE Request - Store composite SOP instance.
@ n_create_rq
N-CREATE Request - Create SOP instance.
@ n_set_rq
N-SET Request - Set attribute values.
@ c_echo_rq
C-ECHO Request - Verify DICOM connection.
@ c_find_rq
C-FIND Request - Query for matching instances.
@ n_delete_rq
N-DELETE Request - Delete SOP instance.
@ n_event_report_rq
N-EVENT-REPORT Request - Report event notification.
@ c_get_rq
C-GET Request - Retrieve composite SOP instances.
@ c_move_rq
C-MOVE Request - Move composite SOP instances.
@ n_action_rq
N-ACTION Request - Request action.
@ abstract_syntax
Abstract Syntax Sub-item.
DicomOperation
DICOM operation types for permission checking.
@ op
Ophthalmic Photography / Tomography.
kcenon::common::error_info error_info
Error information type.
Definition result.h:40

References kcenon::pacs::network::abstract_syntax, kcenon::pacs::network::association::accepted_contexts(), access_control_, access_control_enabled_, association_, kcenon::pacs::network::dimse::c_echo_rq, kcenon::pacs::network::dimse::c_find_rq, kcenon::pacs::network::dimse::c_get_rq, kcenon::pacs::network::dimse::c_move_rq, kcenon::pacs::network::dimse::c_store_rq, kcenon::pacs::security::CEcho, kcenon::pacs::security::CFind, kcenon::pacs::security::CGet, kcenon::pacs::security::CMove, kcenon::pacs::network::dimse::dimse_message::command(), kcenon::pacs::security::CStore, find_service(), kcenon::pacs::network::dimse::n_action_rq, kcenon::pacs::network::dimse::n_create_rq, kcenon::pacs::network::dimse::n_delete_rq, kcenon::pacs::network::dimse::n_event_report_rq, kcenon::pacs::network::dimse::n_get_rq, kcenon::pacs::network::dimse::n_set_rq, kcenon::pacs::security::NAction, kcenon::pacs::security::NCreate, kcenon::pacs::security::NDelete, kcenon::pacs::security::NEventReport, kcenon::pacs::security::NGet, kcenon::pacs::security::NSet, and user_context_.

Referenced by handle_p_data_tf().

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

◆ feed_data()

void kcenon::pacs::network::v2::dicom_association_handler::feed_data ( const std::vector< uint8_t > & data)

Feed received data to the handler.

Called by the server when data arrives for this handler's session. This replaces the session-level receive callback pattern used with messaging_session. In the i_protocol_server architecture, the server receives data via its receive callback and forwards it here.

Parameters
dataThe received data bytes
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 216 of file dicom_association_handler.cpp.

216 {
217 on_data_received(data);
218}
void on_data_received(const std::vector< uint8_t > &data)
Handle data received from session.

References on_data_received().

Here is the call graph for this function:

◆ find_service()

services::scp_service * kcenon::pacs::network::v2::dicom_association_handler::find_service ( const std::string & sop_class_uid) const
nodiscardprivate

Find service for SOP Class UID.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 737 of file dicom_association_handler.cpp.

738 {
739 auto it = services_.find(sop_class_uid);
740 if (it != services_.end()) {
741 return it->second;
742 }
743 return nullptr;
744}

References services_.

Referenced by dispatch_to_service().

Here is the caller graph for this function:

◆ get_association() [1/2]

Result< std::reference_wrapper< association > > kcenon::pacs::network::v2::dicom_association_handler::get_association ( )
nodiscard

Get the underlying association object.

Returns
Result containing reference to the association or error if not established
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 137 of file dicom_association_handler.cpp.

137 {
138 std::lock_guard<std::mutex> lock(mutex_);
139 if (!is_established()) {
141 "Association not established", "network"};
142 }
143 return std::ref(association_);
144}
bool is_established() const noexcept
Check if the association is established.
constexpr int invalid_association_state
Definition result.h:101

References association_, kcenon::pacs::error_codes::invalid_association_state, is_established(), and mutex_.

Here is the call graph for this function:

◆ get_association() [2/2]

Result< std::reference_wrapper< const association > > kcenon::pacs::network::v2::dicom_association_handler::get_association ( ) const
nodiscard

Definition at line 146 of file dicom_association_handler.cpp.

146 {
147 std::lock_guard<std::mutex> lock(mutex_);
148 if (!is_established()) {
150 "Association not established", "network"};
151 }
152 return std::cref(association_);
153}

References association_, kcenon::pacs::error_codes::invalid_association_state, is_established(), and mutex_.

Here is the call graph for this function:

◆ handle_abort()

void kcenon::pacs::network::v2::dicom_association_handler::handle_abort ( const std::vector< uint8_t > & payload)
private

Handle A-ABORT PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 588 of file dicom_association_handler.cpp.

588 {
589 // Parse abort source and reason if present
590 abort_source source = abort_source::service_user;
591 abort_reason reason = abort_reason::not_specified;
592
593 if (payload.size() >= 4) {
594 // Reserved, Reserved, Source, Reason
595 source = static_cast<abort_source>(payload[2]);
596 reason = static_cast<abort_reason>(payload[3]);
597 }
598
599 association_.process_abort(source, reason);
600
601 // Close immediately
602 close_handler(false);
603}
void process_abort(const abort_source &source, const abort_reason &reason)
Process received A-ABORT PDU.
@ not_specified
Reason not specified.
@ service_user
DICOM UL service-user.
const atna_coded_value source
Source Role ID (110153)

References association_, close_handler(), kcenon::pacs::network::not_specified, kcenon::pacs::network::association::process_abort(), and kcenon::pacs::network::service_user.

Referenced by process_pdu().

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

◆ handle_associate_rq()

void kcenon::pacs::network::v2::dicom_association_handler::handle_associate_rq ( const std::vector< uint8_t > & payload)
private

Handle A-ASSOCIATE-RQ PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 368 of file dicom_association_handler.cpp.

368 {
369 // Build full PDU for decoder (add header back)
370 std::vector<uint8_t> full_pdu;
371 full_pdu.reserve(pdu_header_size + payload.size());
372
373 // PDU type
374 full_pdu.push_back(static_cast<uint8_t>(pdu_type::associate_rq));
375 // Reserved
376 full_pdu.push_back(0x00);
377 // Length (big-endian)
378 uint32_t length = static_cast<uint32_t>(payload.size());
379 full_pdu.push_back(static_cast<uint8_t>((length >> 24) & 0xFF));
380 full_pdu.push_back(static_cast<uint8_t>((length >> 16) & 0xFF));
381 full_pdu.push_back(static_cast<uint8_t>((length >> 8) & 0xFF));
382 full_pdu.push_back(static_cast<uint8_t>(length & 0xFF));
383 // Payload
384 full_pdu.insert(full_pdu.end(), payload.begin(), payload.end());
385
386 // Decode the A-ASSOCIATE-RQ
387 auto result = pdu_decoder::decode_associate_rq(full_pdu);
388 if (!result.is_ok()) {
389 report_error("Failed to decode A-ASSOCIATE-RQ");
391 static_cast<uint8_t>(reject_source::service_provider_acse),
392 static_cast<uint8_t>(reject_reason_provider_acse::no_reason));
393 close_handler(false);
394 return;
395 }
396
397 const associate_rq& rq = result.value();
398
399 // Validate called AE title
400 if (rq.called_ae_title != config_.ae_title) {
401 report_error("Called AE title mismatch: expected '" + config_.ae_title +
402 "', got '" + rq.called_ae_title + "'");
404 static_cast<uint8_t>(reject_source::service_user),
406 close_handler(false);
407 return;
408 }
409
410 // Validate calling AE title against whitelist
412 bool found = false;
413 for (const auto& allowed : config_.ae_whitelist) {
414 if (allowed == rq.calling_ae_title) {
415 found = true;
416 break;
417 }
418 }
419 if (!found) {
420 report_error("Calling AE title not in whitelist: " + rq.calling_ae_title);
422 static_cast<uint8_t>(reject_source::service_user),
424 close_handler(false);
425 return;
426 }
427 }
428
429 // Build SCP config for association negotiation
430 scp_config scp_cfg;
431 scp_cfg.ae_title = config_.ae_title;
432 scp_cfg.max_pdu_length = config_.max_pdu_size;
433 scp_cfg.implementation_class_uid = config_.implementation_class_uid;
434 scp_cfg.implementation_version_name = config_.implementation_version_name;
435
436 // Collect supported abstract syntaxes from services
437 for (const auto& [uid, _] : services_) {
438 scp_cfg.supported_abstract_syntaxes.push_back(uid);
439 }
440
441 // Accept compressed and uncompressed transfer syntaxes
442 scp_cfg.supported_transfer_syntaxes = {
443 "1.2.840.10008.1.2.4.201", // HTJ2K Lossless Only
444 "1.2.840.10008.1.2.4.202", // HTJ2K RPCL Lossless Only
445 "1.2.840.10008.1.2.4.203", // HTJ2K (Lossless or Lossy)
446 "1.2.840.10008.1.2.4.90", // JPEG 2000 Lossless
447 "1.2.840.10008.1.2.4.91", // JPEG 2000 Lossy
448 "1.2.840.10008.1.2.4.80", // JPEG-LS Lossless
449 "1.2.840.10008.1.2.4.81", // JPEG-LS Near-Lossless
450 "1.2.840.10008.1.2.4.70", // JPEG Lossless
451 "1.2.840.10008.1.2.4.50", // JPEG Baseline
452 "1.2.840.10008.1.2.5", // RLE Lossless
453 "1.2.840.10008.1.2.1", // Explicit VR Little Endian
454 "1.2.840.10008.1.2", // Implicit VR Little Endian
455 };
456
457 // Perform association negotiation
458 association_ = association::accept(rq, scp_cfg);
459
460 // Check if any presentation contexts were accepted
461 if (association_.accepted_contexts().empty()) {
462 report_error("No presentation contexts accepted");
464 static_cast<uint8_t>(reject_source::service_user),
465 static_cast<uint8_t>(reject_reason_user::no_reason));
466 close_handler(false);
467 return;
468 }
469
470 // Send A-ASSOCIATE-AC
472
473 // Transition to established state
476
477 // Set up user context for access control
479 user_context_ = access_control_->get_context_for_ae(
480 rq.calling_ae_title, session_id());
481 }
482
483 // Notify callback
484 {
485 std::lock_guard<std::mutex> cb_lock(callback_mutex_);
488 rq.calling_ae_title,
489 rq.called_ae_title);
490 }
491 }
492}
static association accept(const associate_rq &rq, const scp_config &config)
Accept an incoming SCP association.
void set_state(association_state new_state)
Force state transition (for testing).
static DecodeResult< associate_rq > decode_associate_rq(std::span< const uint8_t > data)
Decode an A-ASSOCIATE-RQ PDU.
void send_associate_rj(reject_result result, uint8_t source, uint8_t reason)
Send A-ASSOCIATE-RJ response.
association_established_callback established_callback_
Callbacks.
static constexpr size_t pdu_header_size
PDU header size (type + reserved + length)
void report_error(const std::string &error)
Report error through callback.
@ established
Association established, processing DIMSE.
@ rejected_permanent
Rejected-permanent.
@ associate_rq
A-ASSOCIATE-RQ (Association Request)
@ service_provider_acse
DICOM UL service-provider (ACSE)
@ service_user
DICOM UL service-user.
@ established
Sta6: Association established, ready for DIMSE.
@ called_ae_not_recognized
Called-AE-title not recognized.
@ calling_ae_not_recognized
Calling-AE-title not recognized.
@ length
Linear distance measurement.
uint32_t max_pdu_size
Maximum PDU size for data transfer.
std::vector< std::string > ae_whitelist
AE Title whitelist (empty = accept all)
std::string implementation_version_name
Implementation Version Name.
std::string ae_title
Application Entity Title for this server (16 chars max)
std::string implementation_class_uid
Implementation Class UID.
bool accept_unknown_calling_ae
Accept unknown calling AE titles (when whitelist is non-empty)
std::string_view uid

References kcenon::pacs::network::association::accept(), kcenon::pacs::network::server_config::accept_unknown_calling_ae, kcenon::pacs::network::association::accepted_contexts(), access_control_, access_control_enabled_, kcenon::pacs::network::scp_config::ae_title, kcenon::pacs::network::server_config::ae_title, kcenon::pacs::network::server_config::ae_whitelist, kcenon::pacs::network::associate_rq, association_, callback_mutex_, kcenon::pacs::network::called_ae_not_recognized, kcenon::pacs::network::associate_rq::called_ae_title, kcenon::pacs::network::calling_ae_not_recognized, kcenon::pacs::network::associate_rq::calling_ae_title, close_handler(), config_, kcenon::pacs::network::pdu_decoder::decode_associate_rq(), kcenon::pacs::network::established, kcenon::pacs::network::v2::established, established_callback_, kcenon::pacs::network::scp_config::implementation_class_uid, kcenon::pacs::network::server_config::implementation_class_uid, kcenon::pacs::network::scp_config::implementation_version_name, kcenon::pacs::network::server_config::implementation_version_name, kcenon::pacs::network::scp_config::max_pdu_length, kcenon::pacs::network::server_config::max_pdu_size, kcenon::pacs::network::no_reason, pdu_header_size, kcenon::pacs::network::rejected_permanent, report_error(), send_associate_ac(), send_associate_rj(), kcenon::pacs::network::service_provider_acse, kcenon::pacs::network::service_user, services_, session_id(), kcenon::pacs::network::association::set_state(), kcenon::pacs::network::scp_config::supported_abstract_syntaxes, kcenon::pacs::network::scp_config::supported_transfer_syntaxes, transition_to(), uid, and user_context_.

Referenced by process_pdu().

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

◆ handle_disconnect()

void kcenon::pacs::network::v2::dicom_association_handler::handle_disconnect ( )

Handle session disconnection.

Called by the server when the session disconnects.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 220 of file dicom_association_handler.cpp.

220 {
222}
void on_disconnected(const std::string &session_id)
Handle session disconnection.

References on_disconnected(), and session_id().

Here is the call graph for this function:

◆ handle_error()

void kcenon::pacs::network::v2::dicom_association_handler::handle_error ( std::error_code ec)

Handle session error.

Called by the server when an error occurs on the session.

Parameters
ecThe error code
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 224 of file dicom_association_handler.cpp.

224 {
225 on_error(ec);
226}
void on_error(std::error_code ec)
Handle session error.

References on_error().

Here is the call graph for this function:

◆ handle_p_data_tf()

void kcenon::pacs::network::v2::dicom_association_handler::handle_p_data_tf ( const std::vector< uint8_t > & payload)
private

Handle P-DATA-TF PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 494 of file dicom_association_handler.cpp.

494 {
495 // Build full PDU for decoder
496 std::vector<uint8_t> full_pdu;
497 full_pdu.reserve(pdu_header_size + payload.size());
498
499 full_pdu.push_back(static_cast<uint8_t>(pdu_type::p_data_tf));
500 full_pdu.push_back(0x00);
501 uint32_t length = static_cast<uint32_t>(payload.size());
502 full_pdu.push_back(static_cast<uint8_t>((length >> 24) & 0xFF));
503 full_pdu.push_back(static_cast<uint8_t>((length >> 16) & 0xFF));
504 full_pdu.push_back(static_cast<uint8_t>((length >> 8) & 0xFF));
505 full_pdu.push_back(static_cast<uint8_t>(length & 0xFF));
506 full_pdu.insert(full_pdu.end(), payload.begin(), payload.end());
507
508 // Decode P-DATA-TF
509 auto result = pdu_decoder::decode_p_data_tf(full_pdu);
510 if (!result.is_ok()) {
511 report_error("Failed to decode P-DATA-TF");
513 close_handler(false);
514 return;
515 }
516
517 const p_data_tf_pdu& p_data = result.value();
518
519 // Process each PDV
520 for (const auto& pdv : p_data.pdvs) {
521 // Get abstract syntax for this presentation context
522 std::string abstract_syntax;
523 for (const auto& ctx : association_.accepted_contexts()) {
524 if (ctx.id == pdv.context_id) {
525 abstract_syntax = ctx.abstract_syntax;
526 break;
527 }
528 }
529
530 if (abstract_syntax.empty()) {
531 report_error("Unknown presentation context ID: " +
532 std::to_string(pdv.context_id));
533 continue;
534 }
535
536 // If this is a complete command, decode DIMSE message
537 // Note: For simplicity, we're handling command-only messages here.
538 // Full DIMSE message handling with dataset fragmentation would require
539 // accumulating PDVs until is_last is true for both command and data.
540 if (pdv.is_last && pdv.is_command) {
541 // For command-only messages (like C-ECHO), dataset is empty
542 std::vector<uint8_t> empty_dataset;
543
544 // Get transfer syntax for the context
545 auto ts_result = association_.context_transfer_syntax(pdv.context_id);
546 if (ts_result.is_err()) {
547 report_error("Invalid presentation context: " +
548 ts_result.error().message);
549 continue;
550 }
551
552 // Decode DIMSE command
554 std::span<const uint8_t>(pdv.data),
555 std::span<const uint8_t>(empty_dataset),
556 ts_result.value());
557
558 if (dimse_result.is_err()) {
559 report_error("Failed to decode DIMSE message: " +
560 dimse_result.error().message);
561 continue;
562 }
563
564 messages_processed_.fetch_add(1, std::memory_order_relaxed);
565
566 // Dispatch to service
567 auto dispatch_result = dispatch_to_service(pdv.context_id, dimse_result.value());
568 if (dispatch_result.is_err()) {
569 report_error("Service dispatch failed: " + dispatch_result.error().message);
570 }
571 }
572 }
573}
Result< encoding::transfer_syntax > context_transfer_syntax(uint8_t pc_id) const
Get the transfer syntax for an accepted context.
static auto decode(std::span< const uint8_t > command_data, std::span< const uint8_t > dataset_data, const encoding::transfer_syntax &dataset_ts) -> dimse_result< dimse_message >
Decode a DIMSE message from bytes.
static DecodeResult< p_data_tf_pdu > decode_p_data_tf(std::span< const uint8_t > data)
Decode a P-DATA-TF PDU.
Result< std::monostate > dispatch_to_service(uint8_t context_id, const dimse::dimse_message &msg)
Dispatch DIMSE message to appropriate service.
void send_abort(abort_source source, abort_reason reason)
Send A-ABORT PDU.
kcenon::pacs::Result< T > dimse_result
Result type for DIMSE operations using standardized kcenon::pacs::Result<T>
@ p_data_tf
P-DATA-TF (Data Transfer)
@ invalid_pdu_parameter
Invalid PDU parameter value.
@ service_provider
DICOM UL service-provider (ACSE)

References kcenon::pacs::network::abstract_syntax, kcenon::pacs::network::association::accepted_contexts(), association_, close_handler(), kcenon::pacs::network::association::context_transfer_syntax(), kcenon::pacs::network::dimse::dimse_message::decode(), kcenon::pacs::network::pdu_decoder::decode_p_data_tf(), dispatch_to_service(), kcenon::pacs::network::invalid_pdu_parameter, messages_processed_, kcenon::pacs::network::p_data_tf, pdu_header_size, kcenon::pacs::network::p_data_tf_pdu::pdvs, report_error(), send_abort(), and kcenon::pacs::network::service_provider.

Referenced by process_pdu().

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

◆ handle_release_rq()

void kcenon::pacs::network::v2::dicom_association_handler::handle_release_rq ( )
private

Handle A-RELEASE-RQ PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 575 of file dicom_association_handler.cpp.

575 {
577
578 // Process release on the association
580
581 // Send A-RELEASE-RP
583
584 // Close gracefully
585 close_handler(true);
586}
void process_release_rq()
Process received A-RELEASE-RQ.
@ releasing
Graceful release in progress.

References association_, close_handler(), kcenon::pacs::network::association::process_release_rq(), kcenon::pacs::network::v2::releasing, send_release_rp(), and transition_to().

Referenced by process_pdu().

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

◆ is_closed()

bool kcenon::pacs::network::v2::dicom_association_handler::is_closed ( ) const
nodiscardnoexcept

Check if the handler is closed.

Returns
true if handler is in closed state
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 113 of file dicom_association_handler.cpp.

113 {
114 return state_.load(std::memory_order_acquire) == handler_state::closed;
115}

References kcenon::pacs::network::v2::closed, and state_.

Referenced by on_data_received(), and ~dicom_association_handler().

Here is the caller graph for this function:

◆ is_established()

bool kcenon::pacs::network::v2::dicom_association_handler::is_established ( ) const
nodiscardnoexcept

Check if the association is established.

Returns
true if association is in established state
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 109 of file dicom_association_handler.cpp.

109 {
110 return state_.load(std::memory_order_acquire) == handler_state::established;
111}

References kcenon::pacs::network::v2::established, and state_.

Referenced by get_association(), and get_association().

Here is the caller graph for this function:

◆ last_activity()

dicom_association_handler::time_point kcenon::pacs::network::v2::dicom_association_handler::last_activity ( ) const
nodiscardnoexcept

Get time of last activity.

Returns
Time point of last PDU received/sent
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 155 of file dicom_association_handler.cpp.

155 {
156 std::lock_guard<std::mutex> lock(mutex_);
157 return last_activity_;
158}

References last_activity_, and mutex_.

◆ messages_processed()

uint64_t kcenon::pacs::network::v2::dicom_association_handler::messages_processed ( ) const
nodiscardnoexcept

Get number of DIMSE messages processed.

Returns
Count of DIMSE messages handled
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 193 of file dicom_association_handler.cpp.

193 {
194 return messages_processed_.load(std::memory_order_relaxed);
195}

References messages_processed_.

◆ on_data_received()

void kcenon::pacs::network::v2::dicom_association_handler::on_data_received ( const std::vector< uint8_t > & data)
private

Handle data received from session.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 232 of file dicom_association_handler.cpp.

232 {
233 if (is_closed()) {
234 return;
235 }
236
237 {
238 std::lock_guard<std::mutex> lock(mutex_);
239 // Append to receive buffer
240 receive_buffer_.insert(receive_buffer_.end(), data.begin(), data.end());
241 touch();
242 }
243
244 // Process any complete PDUs
246}
void process_buffer()
Process accumulated buffer for complete PDUs.
std::vector< uint8_t > receive_buffer_
PDU receive buffer.

References is_closed(), mutex_, process_buffer(), receive_buffer_, and touch().

Referenced by feed_data().

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

◆ on_disconnected()

void kcenon::pacs::network::v2::dicom_association_handler::on_disconnected ( const std::string & session_id)
private

Handle session disconnection.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 248 of file dicom_association_handler.cpp.

248 {
249 close_handler(true);
250}

References close_handler().

Referenced by handle_disconnect().

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

◆ on_error()

void kcenon::pacs::network::v2::dicom_association_handler::on_error ( std::error_code ec)
private

Handle session error.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 252 of file dicom_association_handler.cpp.

252 {
253 std::ostringstream oss;
254 oss << "Network error: " << ec.message() << " (" << ec.value() << ")";
255 report_error(oss.str());
256
257 close_handler(false);
258}

References close_handler(), and report_error().

Referenced by handle_error().

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

◆ operator=() [1/2]

◆ operator=() [2/2]

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

◆ pdus_received()

uint64_t kcenon::pacs::network::v2::dicom_association_handler::pdus_received ( ) const
nodiscardnoexcept

Get number of PDUs received.

Returns
Count of PDUs received
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 185 of file dicom_association_handler.cpp.

185 {
186 return pdus_received_.load(std::memory_order_relaxed);
187}

References pdus_received_.

◆ pdus_sent()

uint64_t kcenon::pacs::network::v2::dicom_association_handler::pdus_sent ( ) const
nodiscardnoexcept

Get number of PDUs sent.

Returns
Count of PDUs sent
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 189 of file dicom_association_handler.cpp.

189 {
190 return pdus_sent_.load(std::memory_order_relaxed);
191}

References pdus_sent_.

◆ process_buffer()

void kcenon::pacs::network::v2::dicom_association_handler::process_buffer ( )
private

Process accumulated buffer for complete PDUs.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 264 of file dicom_association_handler.cpp.

264 {
265 std::lock_guard<std::mutex> lock(mutex_);
266
267 while (receive_buffer_.size() >= pdu_header_size) {
268 // Check if we have a complete PDU
269 auto length_opt = pdu_decoder::pdu_length(receive_buffer_);
270 if (!length_opt) {
271 // Not enough data yet
272 break;
273 }
274
275 size_t pdu_total_length = *length_opt;
276 if (receive_buffer_.size() < pdu_total_length) {
277 // Not enough data for complete PDU
278 break;
279 }
280
281 // Extract the complete PDU
282 std::vector<uint8_t> pdu_data(
283 receive_buffer_.begin(),
284 receive_buffer_.begin() + static_cast<std::ptrdiff_t>(pdu_total_length));
285
286 // Remove from buffer
287 receive_buffer_.erase(
288 receive_buffer_.begin(),
289 receive_buffer_.begin() + static_cast<std::ptrdiff_t>(pdu_total_length));
290
291 // Parse PDU type
292 auto type_opt = pdu_decoder::peek_pdu_type(pdu_data);
293 if (!type_opt) {
294 report_error("Invalid PDU type received");
296 close_handler(false);
297 return;
298 }
299
300 pdus_received_.fetch_add(1, std::memory_order_relaxed);
301
302 // Extract payload (skip 6-byte header)
303 std::vector<uint8_t> payload(
304 pdu_data.begin() + pdu_header_size,
305 pdu_data.end());
306
307 // Create pdu_data structure for processing
308 integration::pdu_data pdu(*type_opt, std::move(payload));
310 }
311}
static std::optional< size_t > pdu_length(std::span< const uint8_t > data)
Check if a complete PDU is available in the buffer.
static std::optional< pdu_type > peek_pdu_type(std::span< const uint8_t > data)
Get the PDU type from buffer without full decoding.
void process_pdu(const integration::pdu_data &pdu)
Process a complete PDU.
std::variant< associate_rq, associate_ac, associate_rj, p_data_tf_pdu, release_rq_pdu, release_rp_pdu, abort_pdu > pdu
Variant type that can hold any PDU.
Definition pdu_decoder.h:62

References close_handler(), mutex_, pdu_header_size, kcenon::pacs::network::pdu_decoder::pdu_length(), pdus_received_, kcenon::pacs::network::pdu_decoder::peek_pdu_type(), process_pdu(), receive_buffer_, report_error(), send_abort(), kcenon::pacs::network::service_provider, and kcenon::pacs::network::unrecognized_pdu.

Referenced by on_data_received().

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

◆ process_pdu()

void kcenon::pacs::network::v2::dicom_association_handler::process_pdu ( const integration::pdu_data & pdu)
private

Process a complete PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 313 of file dicom_association_handler.cpp.

313 {
314 // Note: mutex_ is already held by caller (process_buffer)
315
316 switch (pdu.type) {
319 handle_associate_rq(pdu.payload);
320 } else {
321 report_error("Unexpected A-ASSOCIATE-RQ in current state");
323 close_handler(false);
324 }
325 break;
326
329 handle_p_data_tf(pdu.payload);
330 } else {
331 report_error("Unexpected P-DATA-TF in current state");
333 close_handler(false);
334 }
335 break;
336
340 } else {
341 report_error("Unexpected A-RELEASE-RQ in current state");
343 close_handler(false);
344 }
345 break;
346
347 case pdu_type::abort:
348 handle_abort(pdu.payload);
349 break;
350
354 // These are responses, SCP should not receive them in normal flow
355 report_error("Unexpected PDU type for SCP: " + std::string(to_string(pdu.type)));
357 close_handler(false);
358 break;
359
360 default:
361 report_error("Unknown PDU type");
363 close_handler(false);
364 break;
365 }
366}
void handle_associate_rq(const std::vector< uint8_t > &payload)
Handle A-ASSOCIATE-RQ PDU.
void handle_p_data_tf(const std::vector< uint8_t > &payload)
Handle P-DATA-TF PDU.
void handle_abort(const std::vector< uint8_t > &payload)
Handle A-ABORT PDU.
constexpr const char * to_string(handler_state state) noexcept
Convert handler_state to string representation.
@ idle
Initial state, waiting for A-ASSOCIATE-RQ.
@ associate_rj
A-ASSOCIATE-RJ (Association Reject)
@ associate_ac
A-ASSOCIATE-AC (Association Accept)
@ release_rq
A-RELEASE-RQ (Release Request)
@ release_rp
A-RELEASE-RP (Release Response)

References kcenon::pacs::network::abort, kcenon::pacs::network::associate_ac, kcenon::pacs::network::associate_rj, kcenon::pacs::network::associate_rq, close_handler(), kcenon::pacs::network::v2::established, handle_abort(), handle_associate_rq(), handle_p_data_tf(), handle_release_rq(), kcenon::pacs::network::v2::idle, kcenon::pacs::network::p_data_tf, kcenon::pacs::network::release_rp, kcenon::pacs::network::release_rq, report_error(), send_abort(), kcenon::pacs::network::service_provider, state_, kcenon::pacs::network::v2::to_string(), kcenon::pacs::network::unexpected_pdu, and kcenon::pacs::network::unrecognized_pdu.

Referenced by process_buffer().

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

◆ report_error()

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

Report error through callback.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 762 of file dicom_association_handler.cpp.

762 {
763 std::lock_guard<std::mutex> lock(callback_mutex_);
764 if (error_callback_) {
765 error_callback_(session_id(), error);
766 }
767}

References callback_mutex_, error_callback_, and session_id().

Referenced by handle_associate_rq(), handle_p_data_tf(), on_error(), process_buffer(), process_pdu(), and start().

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

◆ send_abort()

void kcenon::pacs::network::v2::dicom_association_handler::send_abort ( abort_source source,
abort_reason reason )
private

Send A-ABORT PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 632 of file dicom_association_handler.cpp.

632 {
633 auto encoded = pdu_encoder::encode_abort(source, reason);
634 send_pdu(pdu_type::abort, encoded);
635}
static std::vector< uint8_t > encode_abort(uint8_t source, uint8_t reason)
Encodes an A-ABORT PDU.
void send_pdu(pdu_type type, const std::vector< uint8_t > &payload)
Send raw PDU data.

References kcenon::pacs::network::abort, kcenon::pacs::network::pdu_encoder::encode_abort(), and send_pdu().

Referenced by handle_p_data_tf(), process_buffer(), and process_pdu().

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

◆ send_associate_ac()

void kcenon::pacs::network::v2::dicom_association_handler::send_associate_ac ( )
private

Send A-ASSOCIATE-AC response.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 609 of file dicom_association_handler.cpp.

609 {
611 auto encoded = pdu_encoder::encode_associate_ac(ac);
613}
associate_ac build_associate_ac() const
Build A-ASSOCIATE-AC PDU for sending.
static std::vector< uint8_t > encode_associate_ac(const associate_ac &ac)
Encodes an A-ASSOCIATE-AC PDU.

References kcenon::pacs::network::associate_ac, association_, kcenon::pacs::network::association::build_associate_ac(), kcenon::pacs::network::pdu_encoder::encode_associate_ac(), and send_pdu().

Referenced by handle_associate_rq().

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

◆ send_associate_rj()

void kcenon::pacs::network::v2::dicom_association_handler::send_associate_rj ( reject_result result,
uint8_t source,
uint8_t reason )
private

Send A-ASSOCIATE-RJ response.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 615 of file dicom_association_handler.cpp.

616 {
617 associate_rj rj(result, source, reason);
618 auto encoded = pdu_encoder::encode_associate_rj(rj);
620}
static std::vector< uint8_t > encode_associate_rj(const associate_rj &rj)
Encodes an A-ASSOCIATE-RJ PDU.

References kcenon::pacs::network::associate_rj, kcenon::pacs::network::pdu_encoder::encode_associate_rj(), and send_pdu().

Referenced by handle_associate_rq().

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

◆ send_p_data_tf()

void kcenon::pacs::network::v2::dicom_association_handler::send_p_data_tf ( const std::vector< uint8_t > & payload)
private

Send P-DATA-TF PDU.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 622 of file dicom_association_handler.cpp.

622 {
623 // Payload should already be a complete P-DATA-TF PDU
625}

References kcenon::pacs::network::p_data_tf, and send_pdu().

Here is the call graph for this function:

◆ send_pdu()

void kcenon::pacs::network::v2::dicom_association_handler::send_pdu ( pdu_type type,
const std::vector< uint8_t > & payload )
private

Send raw PDU data.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 637 of file dicom_association_handler.cpp.

637 {
638#ifdef PACS_WITH_NETWORK_SYSTEM
639 if (session_ && session_->is_connected()) {
640 // encoded_pdu already includes header for most PDU types
641 // For encode_associate_ac, encode_associate_rj, etc., the encoder returns full PDU
642
643 // We need to make a copy for the async send
644 std::vector<uint8_t> data_copy = encoded_pdu;
645 (void)session_->send(std::move(data_copy));
646 pdus_sent_.fetch_add(1, std::memory_order_relaxed);
647 }
648#else
649 (void)encoded_pdu;
650#endif
651}

References pdus_sent_, and session_.

Referenced by send_abort(), send_associate_ac(), send_associate_rj(), send_p_data_tf(), and send_release_rp().

Here is the caller graph for this function:

◆ send_release_rp()

void kcenon::pacs::network::v2::dicom_association_handler::send_release_rp ( )
private

Send A-RELEASE-RP response.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 627 of file dicom_association_handler.cpp.

627 {
628 auto encoded = pdu_encoder::encode_release_rp();
630}
static std::vector< uint8_t > encode_release_rp()
Encodes an A-RELEASE-RP PDU.

References kcenon::pacs::network::pdu_encoder::encode_release_rp(), kcenon::pacs::network::release_rp, and send_pdu().

Referenced by handle_release_rq().

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

◆ session_id()

std::string kcenon::pacs::network::v2::dicom_association_handler::session_id ( ) const
nodiscard

Get the session identifier.

Returns
Unique session ID string
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 117 of file dicom_association_handler.cpp.

117 {
118#ifdef PACS_WITH_NETWORK_SYSTEM
119 std::lock_guard<std::mutex> lock(mutex_);
120 if (session_) {
121 return std::string(session_->id());
122 }
123#endif
124 return {};
125}

References mutex_, and session_.

Referenced by close_handler(), handle_associate_rq(), handle_disconnect(), and report_error().

Here is the caller graph for this function:

◆ set_access_control()

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

Set the access control manager for RBAC.

Parameters
acmShared pointer to access control manager
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 201 of file dicom_association_handler.cpp.

202 {
203 std::lock_guard<std::mutex> lock(mutex_);
204 access_control_ = std::move(acm);
205}

References access_control_, and mutex_.

◆ set_access_control_enabled()

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

Enable or disable access control enforcement.

Parameters
enabledIf true, access control is enforced
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 207 of file dicom_association_handler.cpp.

207 {
208 std::lock_guard<std::mutex> lock(mutex_);
209 access_control_enabled_ = enabled;
210}

References access_control_enabled_, and mutex_.

◆ set_closed_callback()

void kcenon::pacs::network::v2::dicom_association_handler::set_closed_callback ( association_closed_callback callback)

Set callback for association closed event.

Parameters
callbackFunction called when association is closed
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 170 of file dicom_association_handler.cpp.

171 {
172 std::lock_guard<std::mutex> lock(callback_mutex_);
173 closed_callback_ = std::move(callback);
174}

References callback_mutex_, and closed_callback_.

◆ set_error_callback()

void kcenon::pacs::network::v2::dicom_association_handler::set_error_callback ( handler_error_callback callback)

Set callback for error events.

Parameters
callbackFunction called on errors
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 176 of file dicom_association_handler.cpp.

176 {
177 std::lock_guard<std::mutex> lock(callback_mutex_);
178 error_callback_ = std::move(callback);
179}

References callback_mutex_, and error_callback_.

◆ set_established_callback()

void kcenon::pacs::network::v2::dicom_association_handler::set_established_callback ( association_established_callback callback)

Set callback for association established event.

Parameters
callbackFunction called when association is established
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 164 of file dicom_association_handler.cpp.

165 {
166 std::lock_guard<std::mutex> lock(callback_mutex_);
167 established_callback_ = std::move(callback);
168}

References callback_mutex_, and established_callback_.

◆ start()

void kcenon::pacs::network::v2::dicom_association_handler::start ( )

Start processing the session.

Sets up receive callbacks and begins handling incoming PDUs. The handler will automatically process association negotiation.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 62 of file dicom_association_handler.cpp.

62 {
63 std::lock_guard<std::mutex> lock(mutex_);
64
66 report_error("Cannot start: handler not in idle state");
67 return;
68 }
69
70 if (!session_) {
71 report_error("Cannot start: no session available");
72 return;
73 }
74
75#ifdef PACS_WITH_NETWORK_SYSTEM
76 // In the i_protocol_server architecture, the server forwards events
77 // to the handler via feed_data(), handle_disconnect(), and handle_error().
78 // No session-level callback setup needed.
79#endif
80
81 touch();
82}

References kcenon::pacs::network::v2::idle, mutex_, report_error(), session_, state_, and touch().

Here is the call graph for this function:

◆ state()

handler_state kcenon::pacs::network::v2::dicom_association_handler::state ( ) const
nodiscardnoexcept

Get current handler state.

Returns
Current state of the handler
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 105 of file dicom_association_handler.cpp.

105 {
106 return state_.load(std::memory_order_acquire);
107}

References state_.

◆ stop()

void kcenon::pacs::network::v2::dicom_association_handler::stop ( bool graceful = false)

Stop the handler and close the session.

Sends an A-ABORT if the association is established and forces immediate closure of the underlying network session.

Parameters
gracefulIf true, attempt graceful release before aborting
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 84 of file dicom_association_handler.cpp.

84 {
85 handler_state expected = state_.load();
86 if (expected == handler_state::closed) {
87 return; // Already closed
88 }
89
90 if (graceful && expected == handler_state::established) {
91 // Try graceful release first
93
94 // Send A-RELEASE-RQ would go here if we were initiating release
95 // For now, just close
96 }
97
98 close_handler(graceful);
99}

References close_handler(), kcenon::pacs::network::v2::closed, kcenon::pacs::network::v2::established, kcenon::pacs::network::v2::releasing, state_, and transition_to().

Referenced by ~dicom_association_handler().

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

◆ touch()

void kcenon::pacs::network::v2::dicom_association_handler::touch ( )
private

Update last activity timestamp.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 757 of file dicom_association_handler.cpp.

757 {
758 // Note: mutex_ should be held by caller
759 last_activity_ = clock::now();
760}

References last_activity_.

Referenced by on_data_received(), and start().

Here is the caller graph for this function:

◆ transition_to()

void kcenon::pacs::network::v2::dicom_association_handler::transition_to ( handler_state new_state)
private

Transition to new state.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 750 of file dicom_association_handler.cpp.

750 {
751 handler_state old_state = state_.exchange(new_state, std::memory_order_acq_rel);
752
753 // Log state transition if needed
754 (void)old_state;
755}

References state_.

Referenced by close_handler(), handle_associate_rq(), handle_release_rq(), and stop().

Here is the caller graph for this function:

Member Data Documentation

◆ access_control_

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

◆ access_control_enabled_

bool kcenon::pacs::network::v2::dicom_association_handler::access_control_enabled_ {false}
private

◆ association_

◆ callback_mutex_

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

◆ closed_callback_

association_closed_callback kcenon::pacs::network::v2::dicom_association_handler::closed_callback_
private

◆ config_

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

◆ current_pdu_type_

pdu_type kcenon::pacs::network::v2::dicom_association_handler::current_pdu_type_ {pdu_type::abort}
private

◆ error_callback_

handler_error_callback kcenon::pacs::network::v2::dicom_association_handler::error_callback_
private

◆ established_callback_

association_established_callback kcenon::pacs::network::v2::dicom_association_handler::established_callback_
private

◆ expected_pdu_length_

uint32_t kcenon::pacs::network::v2::dicom_association_handler::expected_pdu_length_ {0}
private

Expected PDU length (0 if waiting for header)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 470 of file dicom_association_handler.h.

470{0};

◆ last_activity_

time_point kcenon::pacs::network::v2::dicom_association_handler::last_activity_
private

◆ max_pdu_size

size_t kcenon::pacs::network::v2::dicom_association_handler::max_pdu_size = 64 * 1024 * 1024
staticconstexpr

◆ messages_processed_

std::atomic<uint64_t> kcenon::pacs::network::v2::dicom_association_handler::messages_processed_ {0}
private

◆ mutex_

◆ pdu_header_size

size_t kcenon::pacs::network::v2::dicom_association_handler::pdu_header_size = 6
staticconstexpr

◆ pdus_received_

std::atomic<uint64_t> kcenon::pacs::network::v2::dicom_association_handler::pdus_received_ {0}
private

◆ pdus_sent_

std::atomic<uint64_t> kcenon::pacs::network::v2::dicom_association_handler::pdus_sent_ {0}
private

◆ receive_buffer_

std::vector<uint8_t> kcenon::pacs::network::v2::dicom_association_handler::receive_buffer_
private

◆ services_

service_map kcenon::pacs::network::v2::dicom_association_handler::services_
private

◆ session_

session_ptr kcenon::pacs::network::v2::dicom_association_handler::session_
private

◆ state_

std::atomic<handler_state> kcenon::pacs::network::v2::dicom_association_handler::state_ {handler_state::idle}
private

◆ user_context_

std::optional<security::user_context> kcenon::pacs::network::v2::dicom_association_handler::user_context_
private

User context for this association (set after A-ASSOCIATE negotiation)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/v2/dicom_association_handler.h.

Definition at line 496 of file dicom_association_handler.h.

Referenced by dispatch_to_service(), and handle_associate_rq().


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