PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dimse_process_job.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
15
16#include <chrono>
17
19
21 request_callback on_request,
22 association_callback on_association,
23 error_callback on_error)
24 : pdu_(std::move(pdu))
25 , on_request_(std::move(on_request))
26 , on_association_(std::move(on_association))
27 , on_error_(std::move(on_error)) {
28
32 context_.enqueue_time_ns = static_cast<uint64_t>(
33 std::chrono::duration_cast<std::chrono::nanoseconds>(
34 std::chrono::steady_clock::now().time_since_epoch()
35 ).count()
36 );
37}
38
40 // Handle association PDUs differently
42 return process_association_pdu();
43 }
44
45 // Process P-DATA-TF (DIMSE message)
46 auto request_result = process_p_data();
47 if (!request_result.is_ok()) {
48 if (on_error_) {
49 auto err = request_result.error();
50 on_error_(context_.session_id, err.message);
51 }
52 return VoidResult(request_result.error());
53 }
54
55 auto request = request_result.value();
56
57 // Update context with message ID and category
58 context_.message_id = request.message_id;
59
60 // Determine category from command type
61 switch (request.command_type) {
64 context_.category = job_category::echo;
65 break;
68 context_.category = job_category::store;
69 break;
72 context_.category = job_category::find;
73 break;
76 context_.category = job_category::get;
77 break;
80 context_.category = job_category::move;
81 break;
82 default:
83 context_.category = job_category::other;
84 break;
85 }
86
87 // Invoke callback if set
88 if (on_request_) {
89 on_request_(request);
90 }
91
92 // For response messages, skip execution stage
93 uint16_t cmd_type = static_cast<uint16_t>(request.command_type);
94 if (cmd_type & 0x8000) {
95 // This is a response, not a request - don't submit to execution
96 return ok();
97 }
98
99 // Create execution job for request
100 // Note: The actual service handler would be set by the adapter layer
101 auto exec_job = std::make_unique<storage_query_exec_job>(
102 std::move(request),
103 nullptr // Handler will be set by adapter
104 );
105
106 // Copy context to execution job
107 exec_job->get_context() = context_;
108 exec_job->get_context().stage = pipeline_stage::storage_query_exec;
109
110 return coordinator.submit_to_stage(
112 std::move(exec_job)
113 );
114}
115
116auto dimse_process_job::get_context() const noexcept -> const job_context& {
117 return context_;
118}
119
121 return context_;
122}
123
124auto dimse_process_job::get_name() const -> std::string {
125 return "dimse_process_job[session=" +
126 std::to_string(context_.session_id) +
127 ", pdu_type=" + std::to_string(static_cast<int>(pdu_.type)) + "]";
128}
129
130auto dimse_process_job::get_pdu() const noexcept -> const decoded_pdu& {
131 return pdu_;
132}
133
135 dimse_request request;
136 request.session_id = pdu_.session_id;
137 request.presentation_context_id = pdu_.presentation_context_id;
138
139 // PDV data starts after length (4) + context ID (1) + control header (1)
140 if (pdu_.data.size() < 6) {
142 "PDV data too short");
143 }
144
145 // Extract command from PDV data
146 // The command is DICOM dataset starting at offset 6
147 const auto& data = pdu_.data;
148 size_t offset = 6;
149
150 // Parse DICOM command elements
151 // Minimum: (0000,0000) CommandGroupLength + (0000,0100) CommandField
152 while (offset + 8 <= data.size()) {
153 // Tag: group (2) + element (2)
154 uint16_t group = static_cast<uint16_t>(data[offset]) |
155 (static_cast<uint16_t>(data[offset + 1]) << 8);
156 uint16_t element = static_cast<uint16_t>(data[offset + 2]) |
157 (static_cast<uint16_t>(data[offset + 3]) << 8);
158 offset += 4;
159
160 // VR is implicit for command set (Implicit VR Little Endian)
161 // Length (4 bytes)
162 if (offset + 4 > data.size()) break;
163 uint32_t length = static_cast<uint32_t>(data[offset]) |
164 (static_cast<uint32_t>(data[offset + 1]) << 8) |
165 (static_cast<uint32_t>(data[offset + 2]) << 16) |
166 (static_cast<uint32_t>(data[offset + 3]) << 24);
167 offset += 4;
168
169 if (offset + length > data.size()) break;
170
171 // Extract key elements
172 if (group == 0x0000) {
173 switch (element) {
174 case 0x0100: // CommandField
175 if (length >= 2) {
176 request.command_type = static_cast<dimse_command_type>(
177 static_cast<uint16_t>(data[offset]) |
178 (static_cast<uint16_t>(data[offset + 1]) << 8)
179 );
180 }
181 break;
182 case 0x0110: // MessageID
183 if (length >= 2) {
184 request.message_id = static_cast<uint16_t>(data[offset]) |
185 (static_cast<uint16_t>(data[offset + 1]) << 8);
186 }
187 break;
188 case 0x0002: // AffectedSOPClassUID
189 case 0x0003: // RequestedSOPClassUID
190 request.sop_class_uid = std::string(
191 reinterpret_cast<const char*>(&data[offset]), length);
192 // Trim trailing nulls/spaces
193 while (!request.sop_class_uid.empty() &&
194 (request.sop_class_uid.back() == '\0' ||
195 request.sop_class_uid.back() == ' ')) {
196 request.sop_class_uid.pop_back();
197 }
198 break;
199 case 0x1000: // AffectedSOPInstanceUID
200 case 0x1001: // RequestedSOPInstanceUID
201 request.sop_instance_uid = std::string(
202 reinterpret_cast<const char*>(&data[offset]), length);
203 while (!request.sop_instance_uid.empty() &&
204 (request.sop_instance_uid.back() == '\0' ||
205 request.sop_instance_uid.back() == ' ')) {
206 request.sop_instance_uid.pop_back();
207 }
208 break;
209 case 0x0700: // Priority
210 if (length >= 2) {
211 request.priority = static_cast<uint16_t>(data[offset]) |
212 (static_cast<uint16_t>(data[offset + 1]) << 8);
213 }
214 break;
215 }
216 }
217
218 offset += length;
219 }
220
221 // Store full command data for later use
222 request.command_data = pdu_.data;
223
224 return ok(std::move(request));
225}
226
228 // Invoke association callback
229 if (on_association_) {
230 on_association_(pdu_.session_id, pdu_.type, pdu_.data);
231 }
232
233 // Association PDUs don't go through the execution pipeline
234 // They are handled directly by the association state machine
235 return ok();
236}
237
238} // namespace kcenon::pacs::network::pipeline
std::function< void(uint64_t session_id, const std::string &error)> error_callback
Callback type for processing errors.
auto get_name() const -> std::string override
Get the job name.
std::function< void(uint64_t session_id, kcenon::pacs::network::pdu_type type, const std::vector< uint8_t > &data)> association_callback
Callback type for association handling.
auto process_association_pdu() -> VoidResult
Process association PDUs (A-ASSOCIATE, A-RELEASE, A-ABORT)
auto get_context() const noexcept -> const job_context &override
Get the job context.
auto get_pdu() const noexcept -> const decoded_pdu &
Get the decoded PDU.
dimse_process_job(decoded_pdu pdu, request_callback on_request=nullptr, association_callback on_association=nullptr, error_callback on_error=nullptr)
Construct a DIMSE process job from decoded PDU.
auto process_p_data() -> Result< dimse_request >
Process P-DATA-TF PDU.
auto execute(pipeline_coordinator &coordinator) -> VoidResult override
Execute the DIMSE process job.
std::function< void(const dimse_request &request)> request_callback
Callback type for processed request.
Coordinates the 6-stage DICOM I/O pipeline.
DIMSE processing job for Stage 3 of the pipeline.
constexpr int insufficient_data
Definition result.h:82
@ move
C-MOVE move request/response.
@ store
C-STORE storage request/response.
@ get
C-GET retrieve request/response.
@ echo
C-ECHO verification request/response.
@ find
C-FIND query request/response.
dimse_command_type
DICOM DIMSE command types.
@ storage_query_exec
Stage 4: Execute storage/query operations (blocking allowed)
@ dimse_process
Stage 3: Process DIMSE messages and route requests.
@ p_data_tf
P-DATA-TF (Data Transfer)
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
kcenon::pacs::VoidResult VoidResult
VoidResult type alias for operations without return value.
Definition association.h:59
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234
Storage and query execution job for Stage 4 of the pipeline.
Result of PDU decoding containing the PDU type and data.
uint64_t session_id
Session this PDU belongs to.
kcenon::pacs::network::pdu_type type
The type of PDU that was decoded.
Parsed DIMSE request for service execution.
uint16_t priority
Priority (0=medium, 1=high, 2=low)
uint8_t presentation_context_id
Presentation context ID.
dimse_command_type command_type
The DIMSE command type.
std::vector< uint8_t > command_data
Command data set (serialized)
std::string sop_instance_uid
Affected/Requested SOP Instance UID.
uint16_t message_id
Message ID for correlation.
std::string sop_class_uid
Affected/Requested SOP Class UID.
Context information attached to pipeline jobs for tracking.
pipeline_stage stage
Current pipeline stage.
job_category category
Job category for metrics.
uint64_t enqueue_time_ns
Timestamp when job entered the pipeline (nanoseconds since epoch)
uint64_t session_id
Session/association identifier.