35 std::ostringstream oss;
36 oss <<
"Association rejected: ";
46 oss <<
"source=service-user, ";
49 oss <<
"reason=no reason given";
52 oss <<
"reason=application context not supported";
55 oss <<
"reason=calling AE title not recognized";
58 oss <<
"reason=called AE title not recognized";
61 oss <<
"reason=unknown (" <<
static_cast<int>(
reason) <<
")";
65 oss <<
"source=service-provider (ACSE), ";
68 oss <<
"reason=no reason given";
71 oss <<
"reason=protocol version not supported";
74 oss <<
"reason=unknown (" <<
static_cast<int>(
reason) <<
")";
78 oss <<
"source=service-provider (Presentation), ";
81 oss <<
"reason=temporary congestion";
84 oss <<
"reason=local limit exceeded";
87 oss <<
"reason=unknown (" <<
static_cast<int>(
reason) <<
")";
91 oss <<
"source=unknown (" <<
static_cast<int>(
source) <<
")";
104 std::lock_guard<std::mutex> lock(other.mutex_);
105 state_ = other.state_;
106 calling_ae_ = std::move(other.calling_ae_);
107 called_ae_ = std::move(other.called_ae_);
108 our_ae_ = std::move(other.our_ae_);
109 max_pdu_size_ = other.max_pdu_size_;
110 our_implementation_class_ = std::move(other.our_implementation_class_);
111 our_implementation_version_ = std::move(other.our_implementation_version_);
112 remote_implementation_class_ = std::move(other.remote_implementation_class_);
113 remote_implementation_version_ = std::move(other.remote_implementation_version_);
114 proposed_contexts_ = std::move(other.proposed_contexts_);
115 accepted_contexts_ = std::move(other.accepted_contexts_);
116 abstract_syntax_to_context_ = std::move(other.abstract_syntax_to_context_);
117 context_to_transfer_syntax_ = std::move(other.context_to_transfer_syntax_);
118 rejection_info_ = std::move(other.rejection_info_);
119 abort_source_ = other.abort_source_;
120 abort_reason_ = other.abort_reason_;
121 is_scu_ = other.is_scu_;
123 incoming_queue_ = std::move(other.incoming_queue_);
124 other.incoming_queue_ = std::make_unique<message_queue_type>();
127 other.peer_ =
nullptr;
130 peer_->update_peer(&other,
this);
135 if (
this != &other) {
136 std::scoped_lock lock(mutex_, other.mutex_);
137 state_ = other.state_;
138 calling_ae_ = std::move(other.calling_ae_);
139 called_ae_ = std::move(other.called_ae_);
140 our_ae_ = std::move(other.our_ae_);
141 max_pdu_size_ = other.max_pdu_size_;
142 our_implementation_class_ = std::move(other.our_implementation_class_);
143 our_implementation_version_ = std::move(other.our_implementation_version_);
144 remote_implementation_class_ = std::move(other.remote_implementation_class_);
145 remote_implementation_version_ = std::move(other.remote_implementation_version_);
146 proposed_contexts_ = std::move(other.proposed_contexts_);
147 accepted_contexts_ = std::move(other.accepted_contexts_);
148 abstract_syntax_to_context_ = std::move(other.abstract_syntax_to_context_);
149 context_to_transfer_syntax_ = std::move(other.context_to_transfer_syntax_);
150 rejection_info_ = std::move(other.rejection_info_);
151 abort_source_ = other.abort_source_;
152 abort_reason_ = other.abort_reason_;
153 is_scu_ = other.is_scu_;
155 incoming_queue_ = std::move(other.incoming_queue_);
156 other.incoming_queue_ = std::make_unique<message_queue_type>();
159 other.peer_ =
nullptr;
162 peer_->update_peer(&other,
this);
169 std::lock_guard<std::mutex> lock(
mutex_);
181 const std::string& host,
207 auto result = server->simulate_association_request(rq, &assoc);
208 if (result.is_ok()) {
215 return result.error();
251 bool has_accepted = std::any_of(
254 [](
const auto& ctx) { return ctx.is_accepted(); });
280 std::lock_guard<std::mutex> lock(
mutex_);
285 std::lock_guard<std::mutex> lock(
mutex_);
290 std::lock_guard<std::mutex> lock(
mutex_);
301 std::lock_guard<std::mutex> lock(
mutex_);
306 std::lock_guard<std::mutex> lock(
mutex_);
311 std::lock_guard<std::mutex> lock(
mutex_);
316 std::lock_guard<std::mutex> lock(
mutex_);
321 std::lock_guard<std::mutex> lock(
mutex_);
330 std::lock_guard<std::mutex> lock(
mutex_);
338 std::lock_guard<std::mutex> lock(
mutex_);
347 uint8_t pc_id)
const {
349 std::lock_guard<std::mutex> lock(
mutex_);
353 "Presentation context ID not found: " + std::to_string(pc_id),
359const std::vector<accepted_presentation_context>&
373 std::lock_guard<std::mutex> lock(
mutex_);
376 return error_info{invalid_association_state,
"Cannot send DIMSE: association not established",
"network"};
396 return std::monostate{};
399 return std::monostate{};
406 std::lock_guard<std::mutex> lock(
mutex_);
408 return error_info{invalid_association_state,
"Cannot receive DIMSE: association not established",
"network"};
417 return std::move(*item);
422 std::lock_guard<std::mutex> lock(
mutex_);
436 std::lock_guard<std::mutex> lock(
mutex_);
458 std::lock_guard<std::mutex> lock(
mutex_);
480 std::lock_guard<std::mutex> lock(
mutex_);
498 if (pc_rq.id == pc_ac.id) {
501 pc_rq.abstract_syntax,
502 pc_ac.transfer_syntax,
514 bool has_accepted = std::any_of(
517 [](
const auto& ctx) { return ctx.is_accepted(); });
528 std::lock_guard<std::mutex> lock(
mutex_);
535 std::lock_guard<std::mutex> lock(
mutex_);
544 std::lock_guard<std::mutex> lock(
mutex_);
551 return error_info{invalid_association_state,
"Cannot release: association not established",
"network"};
563 return std::monostate{};
567 std::lock_guard<std::mutex> lock(
mutex_);
575 std::lock_guard<std::mutex> lock(
mutex_);
583 std::lock_guard<std::mutex> lock(
mutex_);
594 std::lock_guard<std::mutex> lock(
mutex_);
605 std::lock_guard<std::mutex> lock(
mutex_);
610 std::lock_guard<std::mutex> lock(
mutex_);
620 std::lock_guard<std::mutex> lock(
mutex_);
621 if (
peer_ == old_peer) {
680 if (ctx.is_accepted()) {
704 if (!supports_abstract) {
707 pc_rq.abstract_syntax,
715 std::string accepted_ts;
716 for (
const auto& proposed_ts : pc_rq.transfer_syntaxes) {
723 accepted_ts = proposed_ts;
728 if (accepted_ts.empty()) {
731 pc_rq.abstract_syntax,
738 pc_rq.abstract_syntax,
DICOM Association management per PS3.8.
Represents a DICOM Transfer Syntax.
std::map< std::string, uint8_t > abstract_syntax_to_context_
Map from abstract syntax to accepted context ID.
std::string_view remote_implementation_version() const noexcept
Get remote implementation version name.
std::unique_ptr< message_queue_type > incoming_queue_
static association accept(const associate_rq &rq, const scp_config &config)
Accept an incoming SCP association.
std::string remote_implementation_class_
Remote implementation class UID.
std::vector< proposed_presentation_context > proposed_contexts_
Proposed presentation contexts (SCU)
uint8_t abort_reason_
Abort reason (if aborted)
bool process_associate_ac(const associate_ac &ac)
Process received A-ASSOCIATE-AC PDU.
void enqueue_message(uint8_t context_id, dimse::dimse_message msg)
Enqueue message from peer (for in-memory testing).
association()
Default constructor (creates idle association).
static Result< association > connect(const std::string &host, uint16_t port, const association_config &config, duration timeout=default_timeout)
Initiate an SCU association to a remote SCP.
std::optional< rejection_info > get_rejection_info() const
Get rejection info if association was rejected.
std::string called_ae_
Called AE Title.
std::string our_implementation_class_
Our implementation class UID.
std::string our_ae_
Our AE Title (may be calling or called depending on role)
~association()
Destructor (aborts if still established).
void process_release_rp()
Process received A-RELEASE-RP.
void build_context_map()
Build presentation context map from accepted contexts.
bool can_transition_to(association_state new_state) const
Validate state transition.
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.
bool is_established() const noexcept
Check if association is established and ready for DIMSE.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
std::string_view calling_ae() const noexcept
Get calling AE title.
std::string_view called_ae() const noexcept
Get called AE title.
void update_peer(association *old_peer, association *new_peer)
Update peer pointer (for in-memory testing).
uint32_t max_pdu_size() const noexcept
Get negotiated maximum PDU size.
void process_release_rq()
Process received A-RELEASE-RQ.
bool is_scu_
Is this an SCU (true) or SCP (false)?
std::mutex mutex_
Thread safety mutex.
association & operator=(association &&other) noexcept
Move assignment operator.
associate_ac build_associate_ac() const
Build A-ASSOCIATE-AC PDU for sending.
std::string calling_ae_
Calling AE Title.
association_state state_
Current state.
associate_rq build_associate_rq() const
Build A-ASSOCIATE-RQ PDU for sending.
association_state state() const noexcept
Get current association state.
std::string our_implementation_version_
Our implementation version name.
Result< encoding::transfer_syntax > context_transfer_syntax(uint8_t pc_id) const
Get the transfer syntax for an accepted context.
void transition(association_state new_state)
Perform state transition.
void set_peer(association *peer)
Set peer association for in-memory testing.
std::chrono::milliseconds duration
std::map< uint8_t, encoding::transfer_syntax > context_to_transfer_syntax_
Map from context ID to transfer syntax.
Result< std::monostate > release(duration timeout=default_timeout)
Gracefully release the association.
association * peer_
Peer association for in-memory testing.
bool has_accepted_context(std::string_view abstract_syntax) const
Check if a presentation context for the abstract syntax was accepted.
uint8_t abort_source_
Abort source (if aborted)
Result< std::pair< uint8_t, dimse::dimse_message > > receive_dimse(duration timeout=default_timeout)
Receive a DIMSE message.
uint32_t max_pdu_size_
Negotiated maximum PDU size.
bool is_closed() const noexcept
Check if association has been released or aborted.
std::string remote_implementation_version_
Remote implementation version name.
void abort(uint8_t source=0, uint8_t reason=0)
Abort the association immediately.
std::vector< accepted_presentation_context > accepted_contexts_
Accepted presentation contexts.
void process_associate_rj(const associate_rj &rj)
Process received A-ASSOCIATE-RJ PDU.
void negotiate_contexts(const associate_rq &rq, const scp_config &config)
Negotiate presentation contexts for SCP.
static associate_rj reject(reject_result result, uint8_t source, uint8_t reason)
Reject an incoming association request.
std::optional< rejection_info > rejection_info_
Rejection information (if rejected)
std::string_view remote_implementation_class() const noexcept
Get remote implementation class UID.
void process_abort(const abort_source &source, const abort_reason &reason)
Process received A-ABORT PDU.
std::optional< uint8_t > accepted_context_id(std::string_view abstract_syntax) const
Get the presentation context ID for an abstract syntax.
static dicom_server * get_server_on_port(uint16_t port)
Get server instance listening on port (for in-memory testing).
auto is_valid() const noexcept -> bool
Check if the message is valid.
Multi-threaded DICOM server for handling multiple associations.
constexpr int no_acceptable_context
constexpr int association_aborted
constexpr int receive_timeout
constexpr int release_failed
constexpr int dimse_error
constexpr int invalid_association_state
constexpr int association_rejected
constexpr int already_released
reject_result
Reject result values.
@ rejected_permanent
Rejected-permanent.
@ abstract_syntax
Abstract Syntax Sub-item.
constexpr const char * DICOM_APPLICATION_CONTEXT
Default DICOM Application Context Name (PS3.7)
reject_reason_provider_acse
Reject reason values when source is service-provider (ACSE).
@ no_reason
No reason given.
@ protocol_version_not_supported
Protocol-version not supported.
abort_reason
Abort reason values when source is service-provider.
reject_source
Reject source values.
@ service_provider_acse
DICOM UL service-provider (ACSE)
@ service_user
DICOM UL service-user.
@ service_provider_presentation
DICOM UL service-provider (Presentation)
abort_source
Abort source values.
reject_reason_provider_presentation
Reject reason values when source is service-provider (Presentation).
@ local_limit_exceeded
Local limit exceeded.
@ temporary_congestion
Temporary congestion.
@ abstract_syntax_not_supported
Abstract-syntax-not-supported.
@ transfer_syntaxes_not_supported
Transfer-syntaxes-not-supported.
association_state
DICOM Association state machine states per PS3.8.
@ released
Association gracefully released.
@ awaiting_release_rp
Sta7: Awaiting A-RELEASE response (initiator)
@ awaiting_associate_ac
Sta5: Awaiting A-ASSOCIATE response (SCU)
@ established
Sta6: Association established, ready for DIMSE.
@ aborted
Association aborted (error condition)
@ awaiting_release_rq
Sta8: Awaiting potential A-RELEASE request.
@ awaiting_associate_rq
Sta2: Awaiting A-ASSOCIATE request (SCP)
@ idle
Sta1: No TCP connection, waiting for transport.
reject_reason_user
Reject reason values when source is service-user.
@ called_ae_not_recognized
Called-AE-title not recognized.
@ application_context_not_supported
Application-context-name not supported.
@ no_reason
No reason given.
@ calling_ae_not_recognized
Calling-AE-title not recognized.
kcenon::common::error_info error_info
Error information type.
Accepted presentation context after negotiation.
std::string calling_ae_title
Calling AE Title (16 chars max)
user_information user_info
User Information.
std::string called_ae_title
Called AE Title (16 chars max)
std::string application_context
Application Context Name UID.
std::vector< presentation_context_ac > presentation_contexts
Presentation Contexts.
uint8_t reason
Reason/Diagnostic.
reject_result result
Result (1=permanent, 2=transient)
std::string called_ae_title
Called AE Title (16 chars max)
std::string application_context
Application Context Name UID.
user_information user_info
User Information.
std::string calling_ae_title
Calling AE Title (16 chars max)
std::vector< presentation_context_rq > presentation_contexts
Presentation Contexts.
Configuration for SCU association request.
std::string called_ae_title
Remote AE Title (16 chars max)
std::string calling_ae_title
Our AE Title (16 chars max)
std::string implementation_class_uid
std::string implementation_version_name
std::vector< proposed_presentation_context > proposed_contexts
Presentation Context for A-ASSOCIATE-AC.
Presentation Context for A-ASSOCIATE-RQ.
Information about an association rejection.
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