16#include <kcenon/common/patterns/event_bus.h>
22#ifndef KCENON_HAS_COMMON_SYSTEM
23#define KCENON_HAS_COMMON_SYSTEM 0
26#if KCENON_HAS_COMMON_SYSTEM
27using kcenon::common::error_info;
43 std::make_shared<integration::thread_pool_adapter>(
44 integration::thread_pool_config{})) {
49 std::shared_ptr<integration::thread_pool_interface> thread_pool)
50 : thread_pool_(std::move(thread_pool))
79 for (
const auto& sop_class : service->supported_sop_classes()) {
89 std::vector<std::string> sop_classes;
93 sop_classes.push_back(
uid);
111 return error_info(
"AE Title cannot be empty");
116 return error_info(
"AE Title exceeds 16 characters");
155 check_idle_timeouts();
158 accept_worker_->set_wake_interval(std::chrono::milliseconds(100));
161 if (start_result.is_err()) {
163 return error_info(
"Failed to start accept worker: " +
164 start_result.error().message);
173 return std::monostate{};
201 info->cancel_token.cancel();
210 auto deadline = clock::now() + timeout;
216 std::this_thread::sleep_for(std::chrono::milliseconds{100});
229 if (info->assoc.is_established()) {
236 info->processing =
false;
319 info.id = session_id;
320 info.assoc = std::move(assoc);
321 info.connected_at = clock::now();
322 info.last_activity = info.connected_at;
333 kcenon::common::get_event_bus().publish(
335 std::string(info.assoc.calling_ae()),
336 std::string(info.assoc.called_ae()),
339 info.assoc.max_pdu_size()
356 while (
running_ && !info.cancel_token.is_cancelled() && info.assoc.is_established()) {
359 auto receive_timeout = (std::min)(
364 auto result = info.assoc.receive_dimse(receive_timeout);
367 if (info.cancel_token.is_cancelled()) {
371 if (result.is_err()) {
373 if (info.assoc.is_established()) {
375 auto now = clock::now();
376 auto idle_duration = std::chrono::duration_cast<std::chrono::seconds>(
377 now - info.last_activity);
391 info.last_activity = clock::now();
395 auto& [context_id, msg] = result.value();
398 if (dispatch_result.is_err()) {
411 if (info.cancel_token.is_cancelled() && info.assoc.is_established()) {
414 (void)info.assoc.release();
418 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
419 clock::now() - info.connected_at);
434 kcenon::common::get_event_bus().publish(
436 std::string(info.assoc.calling_ae()),
437 std::string(info.assoc.called_ae()),
438 "Association aborted"
442 kcenon::common::get_event_bus().publish(
444 std::string(info.assoc.calling_ae()),
445 std::string(info.assoc.called_ae()),
464 if (sop_class_uid.empty()) {
465 return error_info(
"Cannot determine SOP Class UID from message");
471 return error_info(
"No service registered for SOP Class: " + sop_class_uid);
475 return service->handle_message(assoc, context_id, msg);
497 const std::string& sop_class_uid)
const {
510 associations_.emplace(
id, std::make_unique<association_info>(std::move(info)));
520 it->second->processing =
false;
529 it->second->last_activity = clock::now();
538 auto now = clock::now();
539 std::vector<uint64_t> timed_out;
544 auto idle_duration = std::chrono::duration_cast<std::chrono::seconds>(
545 now - info->last_activity);
548 timed_out.push_back(
id);
553 for (
auto id : timed_out) {
556 it->second->assoc.abort(
584 return error_info(
"Called AE Title not recognized");
589 return error_info(
"Calling AE Title not recognized");
601 "1.2.840.10008.1.2.4.201",
602 "1.2.840.10008.1.2.4.202",
603 "1.2.840.10008.1.2.4.203",
604 "1.2.840.10008.1.2.4.90",
605 "1.2.840.10008.1.2.4.91",
606 "1.2.840.10008.1.2.4.80",
607 "1.2.840.10008.1.2.4.81",
608 "1.2.840.10008.1.2.4.70",
609 "1.2.840.10008.1.2.4.50",
610 "1.2.840.10008.1.2.5",
611 "1.2.840.10008.1.2.1",
618 if (!assoc.is_established()) {
619 return error_info(
"Association rejected: no acceptable presentation contexts");
623 auto ac = assoc.build_associate_ac();
635 it->second->assoc.set_peer(client_peer);
636 client_peer->
set_peer(&it->second->assoc);
641 auto* info_ptr = it->second.get();
645 info_ptr->processing =
true;
static association accept(const associate_rq &rq, const scp_config &config)
Accept an incoming SCP association.
void set_peer(association *peer)
Set peer association for in-memory testing.
std::chrono::milliseconds duration
void abort(uint8_t source=0, uint8_t reason=0)
Abort the association immediately.
Result< std::monostate > dispatch_to_service(association &assoc, uint8_t context_id, const dimse::dimse_message &msg)
Dispatch message to appropriate service.
bool validate_called_ae(const std::string &called_ae) const
Validate called AE title.
uint64_t next_association_id()
Generate unique association ID.
std::unordered_map< std::string, services::scp_service * > sop_class_to_service_
Map from SOP Class UID to service.
std::mutex stats_mutex_
Statistics mutex.
void stop(duration timeout=std::chrono::seconds{30})
Stop the server gracefully.
void report_error(const std::string &error)
Report error through callback.
std::vector< std::string > supported_sop_classes() const
Get list of supported SOP Class UIDs.
void register_service(services::scp_service_ptr service)
Register an SCP service.
std::unique_ptr< detail::accept_worker > accept_worker_
Accept worker (replaces std::thread accept_thread_)
std::mutex services_mutex_
Service registry mutex.
services::scp_service * find_service(const std::string &sop_class_uid) const
Get service for SOP Class.
association_callback on_released_cb_
Association released callback.
bool validate_calling_ae(const std::string &calling_ae) const
Validate calling AE title.
Result< associate_ac > simulate_association_request(const associate_rq &rq, association *client_peer)
Simulate an incoming association request (for in-memory testing).
std::shared_ptr< integration::thread_pool_interface > thread_pool_
Thread pool for asynchronous task execution.
void on_error(error_callback callback)
Set callback for error events.
server_statistics get_statistics() const
Get server statistics.
void message_loop(association_info &info)
Process incoming DIMSE messages.
void remove_association(uint64_t id)
Remove association from pool.
void touch_association(uint64_t id)
Update association activity timestamp.
void wait_for_shutdown()
Wait for server shutdown.
std::function< void(const std::string &)> error_callback
Callback type for error events.
std::mutex associations_mutex_
Association mutex.
bool is_running() const noexcept
Check if server is running.
std::atomic< uint64_t > association_id_counter_
Association ID counter.
void on_association_released(association_callback callback)
Set callback for association released events.
std::vector< services::scp_service_ptr > services_
Registered SCP services.
std::mutex shutdown_mutex_
Shutdown mutex.
void handle_association(uint64_t session_id, association assoc)
Handle a single association (runs in worker thread)
dicom_server(const server_config &config)
Construct server with configuration.
static dicom_server * get_server_on_port(uint16_t port)
Get server instance listening on port (for in-memory testing).
std::unordered_map< uint64_t, std::unique_ptr< association_info > > associations_
Active associations.
size_t active_associations() const noexcept
Get number of active associations.
std::mutex callback_mutex_
Callback mutex.
error_callback on_error_cb_
Error callback.
std::atomic< bool > running_
Running flag.
~dicom_server()
Destructor (stops server if running)
association_callback on_established_cb_
Association established callback.
std::function< void(const association &)> association_callback
Callback type for association events.
void on_association_established(association_callback callback)
Set callback for association established events.
void check_idle_timeouts()
Check for idle timeout.
std::chrono::milliseconds duration
const server_config & config() const noexcept
Get server configuration.
server_config config_
Server configuration.
Result< std::monostate > start()
Start the server.
void add_association(association_info info)
Add association to pool.
std::condition_variable shutdown_cv_
Shutdown condition variable.
server_statistics stats_
Server statistics.
auto affected_sop_class_uid() const -> std::string
Get the Affected SOP Class UID.
Multi-threaded DICOM server for handling multiple associations.
DICOM event definitions for event-based communication.
static std::map< uint16_t, dicom_server * > server_registry_
@ not_specified
Reason not specified.
constexpr size_t AE_TITLE_LENGTH
AE Title length (fixed 16 characters, space-padded)
static std::mutex registry_mutex_
@ service_provider
DICOM UL service-provider (ACSE)
@ aborted
Association aborted (error condition)
std::shared_ptr< scp_service > scp_service_ptr
Shared pointer type for SCP services.
kcenon::common::error_info error_info
Error information type.
Event published when a DICOM association is aborted.
Event published when a DICOM association is successfully established.
Event published when a DICOM association is gracefully released.
std::string called_ae_title
Called AE Title (16 chars max)
std::string calling_ae_title
Calling AE Title (16 chars max)
Internal association info for tracking.
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
size_t max_associations
Maximum concurrent associations (0 = unlimited)
std::chrono::seconds idle_timeout
Idle timeout for associations (0 = no timeout)
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.
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)
std::string implementation_class_uid
Implementation Class UID.
bool accept_unknown_calling_ae
Accept unknown calling AE titles (when whitelist is non-empty)
Statistics for server monitoring.
std::chrono::steady_clock::time_point last_activity
Time of last activity.
uint64_t total_associations
Total associations since server start.
uint64_t messages_processed
Total DIMSE messages processed.
size_t active_associations
Currently active associations.
uint64_t bytes_sent
Total bytes sent.
std::chrono::steady_clock::time_point start_time
Server start time.
uint64_t bytes_received
Total bytes received.
uint64_t rejected_associations
Total associations rejected due to limit.
Concrete implementation of thread_pool_interface using kcenon::thread.