19#ifndef KCENON_HAS_COMMON_SYSTEM
20#define KCENON_HAS_COMMON_SYSTEM 0
23#ifdef PACS_WITH_NETWORK_SYSTEM
24#include <kcenon/network/interfaces/i_session.h>
27#if KCENON_HAS_COMMON_SYSTEM
28using kcenon::common::error_info;
41 : session_(std::move(session))
44 , last_activity_(
clock::now()) {
63 std::lock_guard<std::mutex> lock(
mutex_);
75#ifdef PACS_WITH_NETWORK_SYSTEM
106 return state_.load(std::memory_order_acquire);
118#ifdef PACS_WITH_NETWORK_SYSTEM
119 std::lock_guard<std::mutex> lock(
mutex_);
128 std::lock_guard<std::mutex> lock(
mutex_);
133 std::lock_guard<std::mutex> lock(
mutex_);
138 std::lock_guard<std::mutex> lock(
mutex_);
141 "Association not established",
"network"};
147 std::lock_guard<std::mutex> lock(
mutex_);
150 "Association not established",
"network"};
156 std::lock_guard<std::mutex> lock(
mutex_);
190 return pdus_sent_.load(std::memory_order_relaxed);
202 std::shared_ptr<security::access_control_manager> acm) {
203 std::lock_guard<std::mutex> lock(
mutex_);
208 std::lock_guard<std::mutex> lock(
mutex_);
238 std::lock_guard<std::mutex> lock(
mutex_);
253 std::ostringstream oss;
254 oss <<
"Network error: " << ec.message() <<
" (" << ec.value() <<
")";
265 std::lock_guard<std::mutex> lock(
mutex_);
275 size_t pdu_total_length = *length_opt;
282 std::vector<uint8_t> pdu_data(
284 receive_buffer_.begin() +
static_cast<std::ptrdiff_t
>(pdu_total_length));
289 receive_buffer_.begin() +
static_cast<std::ptrdiff_t
>(pdu_total_length));
303 std::vector<uint8_t> payload(
321 report_error(
"Unexpected A-ASSOCIATE-RQ in current state");
341 report_error(
"Unexpected A-RELEASE-RQ in current state");
370 std::vector<uint8_t> full_pdu;
376 full_pdu.push_back(0x00);
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));
384 full_pdu.insert(full_pdu.end(), payload.begin(), payload.end());
388 if (!result.is_ok()) {
443 "1.2.840.10008.1.2.4.201",
444 "1.2.840.10008.1.2.4.202",
445 "1.2.840.10008.1.2.4.203",
446 "1.2.840.10008.1.2.4.90",
447 "1.2.840.10008.1.2.4.91",
448 "1.2.840.10008.1.2.4.80",
449 "1.2.840.10008.1.2.4.81",
450 "1.2.840.10008.1.2.4.70",
451 "1.2.840.10008.1.2.4.50",
452 "1.2.840.10008.1.2.5",
453 "1.2.840.10008.1.2.1",
496 std::vector<uint8_t> full_pdu;
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());
510 if (!result.is_ok()) {
520 for (
const auto& pdv : p_data.
pdvs) {
524 if (ctx.id == pdv.context_id) {
532 std::to_string(pdv.context_id));
540 if (pdv.is_last && pdv.is_command) {
542 std::vector<uint8_t> empty_dataset;
546 if (ts_result.is_err()) {
548 ts_result.error().message);
554 std::span<const uint8_t>(pdv.data),
555 std::span<const uint8_t>(empty_dataset),
558 if (dimse_result.is_err()) {
560 dimse_result.error().message);
568 if (dispatch_result.is_err()) {
569 report_error(
"Service dispatch failed: " + dispatch_result.error().message);
593 if (payload.size() >= 4) {
638#ifdef PACS_WITH_NETWORK_SYSTEM
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);
664 if (ctx.id == context_id) {
671 return error_info(
"Unknown presentation context ID");
682 return error_info(
"Access denied: unregistered AE title");
729 return error_info(
"Access denied: " + check_result.reason);
734 return service->handle_message(
association_, context_id, msg);
738 const std::string& sop_class_uid)
const {
777#ifdef PACS_WITH_NETWORK_SYSTEM
780 std::lock_guard<std::mutex> lock(
mutex_);
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).
const std::vector< accepted_presentation_context > & accepted_contexts() const noexcept
Get all accepted presentation contexts.
std::string_view calling_ae() const noexcept
Get calling AE title.
std::string_view called_ae() const noexcept
Get called AE title.
void process_release_rq()
Process received A-RELEASE-RQ.
associate_ac build_associate_ac() const
Build A-ASSOCIATE-AC PDU for sending.
Result< encoding::transfer_syntax > context_transfer_syntax(uint8_t pc_id) const
Get the transfer syntax for an accepted context.
void process_abort(const abort_source &source, const abort_reason &reason)
Process received A-ABORT PDU.
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.
auto command() const noexcept -> command_field
Get the command field.
static DecodeResult< associate_rq > decode_associate_rq(std::span< const uint8_t > data)
Decode an A-ASSOCIATE-RQ PDU.
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.
static DecodeResult< p_data_tf_pdu > decode_p_data_tf(std::span< const uint8_t > data)
Decode a P-DATA-TF PDU.
static std::vector< uint8_t > encode_release_rp()
Encodes an A-RELEASE-RP PDU.
static std::vector< uint8_t > encode_associate_ac(const associate_ac &ac)
Encodes an A-ASSOCIATE-AC PDU.
static std::vector< uint8_t > encode_associate_rj(const associate_rj &rj)
Encodes an A-ASSOCIATE-RJ PDU.
static std::vector< uint8_t > encode_abort(uint8_t source, uint8_t reason)
Encodes an A-ABORT PDU.
uint64_t pdus_received() const noexcept
Get number of PDUs received.
Result< std::monostate > dispatch_to_service(uint8_t context_id, const dimse::dimse_message &msg)
Dispatch DIMSE message to appropriate service.
void start()
Start processing the session.
void handle_disconnect()
Handle session disconnection.
void transition_to(handler_state new_state)
Transition to new state.
void send_abort(abort_source source, abort_reason reason)
Send A-ABORT PDU.
void process_buffer()
Process accumulated buffer for complete PDUs.
session_ptr session_
Network session.
void feed_data(const std::vector< uint8_t > &data)
Feed received data to the handler.
std::atomic< uint64_t > pdus_sent_
void on_disconnected(const std::string &session_id)
Handle session disconnection.
void set_access_control_enabled(bool enabled)
Enable or disable access control enforcement.
handler_state state() const noexcept
Get current handler state.
void send_associate_rj(reject_result result, uint8_t source, uint8_t reason)
Send A-ASSOCIATE-RJ response.
void send_release_rp()
Send A-RELEASE-RP response.
void touch()
Update last activity timestamp.
uint64_t pdus_sent() const noexcept
Get number of PDUs sent.
services::scp_service * find_service(const std::string &sop_class_uid) const
Find service for SOP Class UID.
association_established_callback established_callback_
Callbacks.
void send_associate_ac()
Send A-ASSOCIATE-AC response.
association association_
DICOM association.
time_point last_activity_
Last activity timestamp.
bool is_established() const noexcept
Check if the association is established.
std::map< std::string, services::scp_service * > service_map
std::shared_ptr< security::access_control_manager > access_control_
Access control manager for RBAC.
void send_p_data_tf(const std::vector< uint8_t > &payload)
Send P-DATA-TF PDU.
service_map services_
Service registry (non-owning pointers)
bool access_control_enabled_
Whether access control is enabled.
std::chrono::steady_clock clock
std::string session_id() const
Get the session identifier.
void handle_associate_rq(const std::vector< uint8_t > &payload)
Handle A-ASSOCIATE-RQ PDU.
dicom_association_handler(session_ptr session, const server_config &config, const service_map &services)
Construct a handler for a network session.
Result< std::reference_wrapper< association > > get_association()
Get the underlying association object.
void on_data_received(const std::vector< uint8_t > &data)
Handle data received from session.
static constexpr size_t pdu_header_size
PDU header size (type + reserved + length)
void close_handler(bool graceful)
Close and cleanup.
std::atomic< handler_state > state_
Current handler state.
std::atomic< uint64_t > pdus_received_
Statistics.
std::atomic< uint64_t > messages_processed_
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.
association_closed_callback closed_callback_
time_point last_activity() const noexcept
Get time of last activity.
bool is_closed() const noexcept
Check if the handler is closed.
std::mutex mutex_
Thread safety.
clock::time_point time_point
std::string calling_ae() const
Get the calling AE title.
~dicom_association_handler()
Destructor (stops handler if still running).
void handle_error(std::error_code ec)
Handle session error.
std::shared_ptr< kcenon::network::interfaces::i_session > session_ptr
void set_access_control(std::shared_ptr< security::access_control_manager > acm)
Set the access control manager for RBAC.
void stop(bool graceful=false)
Stop the handler and close the session.
void handle_release_rq()
Handle A-RELEASE-RQ PDU.
void send_pdu(pdu_type type, const std::vector< uint8_t > &payload)
Send raw PDU data.
std::mutex callback_mutex_
uint64_t messages_processed() const noexcept
Get number of DIMSE messages processed.
std::optional< security::user_context > user_context_
User context for this association (set after A-ASSOCIATE negotiation)
void report_error(const std::string &error)
Report error through callback.
void set_error_callback(handler_error_callback callback)
Set callback for error events.
void handle_p_data_tf(const std::vector< uint8_t > &payload)
Handle P-DATA-TF PDU.
void on_error(std::error_code ec)
Handle session error.
std::vector< uint8_t > receive_buffer_
PDU receive buffer.
server_config config_
Server configuration.
std::string called_ae() const
Get the called AE title.
handler_error_callback error_callback_
void handle_abort(const std::vector< uint8_t > &payload)
Handle A-ABORT PDU.
void process_pdu(const integration::pdu_data &pdu)
Process a complete PDU.
DICOM association handler for network_system integration.
constexpr int invalid_association_state
@ 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.
constexpr const char * to_string(handler_state state) noexcept
Convert handler_state to string representation.
std::function< void(const std::string &session_id, bool graceful)> association_closed_callback
Callback type for association closed events.
handler_state
State machine states for the association handler.
@ closed
Association closed (released or aborted)
@ releasing
Graceful release in progress.
@ established
Association established, processing DIMSE.
@ idle
Initial state, waiting for A-ASSOCIATE-RQ.
std::function< void(const std::string &session_id, const std::string &error)> handler_error_callback
Callback type for error events.
std::function< void(const std::string &session_id, const std::string &calling_ae, const std::string &called_ae)> association_established_callback
Callback type for association established events.
reject_result
Reject result values.
@ rejected_permanent
Rejected-permanent.
pdu_type
PDU (Protocol Data Unit) types as defined in DICOM PS3.8.
@ associate_rj
A-ASSOCIATE-RJ (Association Reject)
@ associate_ac
A-ASSOCIATE-AC (Association Accept)
@ p_data_tf
P-DATA-TF (Data Transfer)
@ release_rq
A-RELEASE-RQ (Release Request)
@ release_rp
A-RELEASE-RP (Release Response)
@ associate_rq
A-ASSOCIATE-RQ (Association Request)
@ abstract_syntax
Abstract Syntax Sub-item.
@ no_reason
No reason given.
abort_reason
Abort reason values when source is service-provider.
@ unexpected_pdu
Unexpected PDU.
@ not_specified
Reason not specified.
@ invalid_pdu_parameter
Invalid PDU parameter value.
@ unrecognized_pdu
Unrecognized 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.
@ service_provider_acse
DICOM UL service-provider (ACSE)
@ service_user
DICOM UL service-user.
abort_source
Abort source values.
@ service_user
DICOM UL service-user.
@ service_provider
DICOM UL service-provider (ACSE)
@ established
Sta6: Association established, ready for DIMSE.
@ called_ae_not_recognized
Called-AE-title not recognized.
@ no_reason
No reason given.
@ calling_ae_not_recognized
Calling-AE-title not recognized.
DicomOperation
DICOM operation types for permission checking.
@ NEventReport
N-EVENT-REPORT.
@ CMove
C-MOVE (retrieve/move)
@ CStore
C-STORE (storage)
@ CEcho
C-ECHO (verification)
kcenon::common::error_info error_info
Error information type.
Container for received PDU data.
std::string called_ae_title
Called AE Title (16 chars max)
std::string calling_ae_title
Calling AE Title (16 chars max)
std::vector< presentation_data_value > pdvs
Presentation Data Values.
Configuration for SCP to accept associations.
std::string implementation_class_uid
std::string ae_title
Our AE Title.
std::vector< std::string > supported_abstract_syntaxes
std::vector< std::string > supported_transfer_syntaxes
std::string implementation_version_name
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)