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

Job for encoding DIMSE responses into PDU bytes. More...

#include <response_encode_job.h>

Inheritance diagram for kcenon::pacs::network::pipeline::response_encode_job:
Inheritance graph
Collaboration diagram for kcenon::pacs::network::pipeline::response_encode_job:
Collaboration graph

Public Types

using encode_callback = std::function<void(const encoded_response& response)>
 Callback type for encoded response.
 
using error_callback
 Callback type for encoding errors.
 

Public Member Functions

 response_encode_job (service_result result, uint32_t max_pdu_size=16384, encode_callback on_encoded=nullptr, error_callback on_error=nullptr)
 Construct an encode job.
 
 ~response_encode_job () override=default
 
 response_encode_job (const response_encode_job &)=delete
 
response_encode_joboperator= (const response_encode_job &)=delete
 
 response_encode_job (response_encode_job &&)=default
 
response_encode_joboperator= (response_encode_job &&)=default
 
auto execute (pipeline_coordinator &coordinator) -> VoidResult override
 Execute the encode job.
 
auto get_context () const noexcept -> const job_context &override
 Get the job context.
 
auto get_context () noexcept -> job_context &override
 Get the job context (mutable)
 
auto get_name () const -> std::string override
 Get the job name.
 
auto get_result () const noexcept -> const service_result &
 Get the service result.
 
- Public Member Functions inherited from kcenon::pacs::network::pipeline::pipeline_job_base
virtual ~pipeline_job_base ()=default
 Virtual destructor.
 

Private Member Functions

auto encode_response () -> Result< std::vector< encoded_response > >
 Encode the response into PDU bytes.
 
auto encode_dimse_command () -> Result< std::vector< uint8_t > >
 Encode DIMSE command.
 
auto fragment_data (const std::vector< uint8_t > &data) -> std::vector< std::vector< uint8_t > >
 Fragment large data if needed.
 

Private Attributes

job_context context_
 
service_result result_
 
uint32_t max_pdu_size_
 
encode_callback on_encoded_
 
error_callback on_error_
 

Additional Inherited Members

- Protected Member Functions inherited from kcenon::pacs::network::pipeline::pipeline_job_base
 pipeline_job_base ()=default
 
 pipeline_job_base (const pipeline_job_base &)=delete
 
pipeline_job_baseoperator= (const pipeline_job_base &)=delete
 
 pipeline_job_base (pipeline_job_base &&)=default
 
pipeline_job_baseoperator= (pipeline_job_base &&)=default
 

Detailed Description

Job for encoding DIMSE responses into PDU bytes.

Stage 5 of the pipeline. Encodes service results into PDU format and submits to the network send stage.

Definition at line 62 of file response_encode_job.h.

Member Typedef Documentation

◆ encode_callback

◆ error_callback

Initial value:
std::function<void(uint64_t session_id,
const std::string& error)>

Callback type for encoding errors.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 68 of file response_encode_job.h.

Constructor & Destructor Documentation

◆ response_encode_job() [1/3]

kcenon::pacs::network::pipeline::response_encode_job::response_encode_job ( service_result result,
uint32_t max_pdu_size = 16384,
encode_callback on_encoded = nullptr,
error_callback on_error = nullptr )

Construct an encode job.

Parameters
resultThe service result to encode
max_pdu_sizeMaximum PDU size for fragmentation
on_encodedCallback for encoded response
on_errorCallback for errors
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 20 of file response_encode_job.cpp.

24 : result_(std::move(result))
25 , max_pdu_size_(max_pdu_size)
26 , on_encoded_(std::move(on_encoded))
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}
@ response_encode
Stage 5: Encode response into PDU bytes.
pipeline_stage stage
Current pipeline stage.
uint16_t message_id
Message ID from DIMSE command (if applicable)
uint64_t enqueue_time_ns
Timestamp when job entered the pipeline (nanoseconds since epoch)
uint64_t session_id
Session/association identifier.
uint64_t session_id
Session ID for routing response.

References context_, kcenon::pacs::network::pipeline::job_context::enqueue_time_ns, kcenon::pacs::network::pipeline::job_context::message_id, kcenon::pacs::network::pipeline::service_result::message_id, kcenon::pacs::network::pipeline::response_encode, result_, kcenon::pacs::network::pipeline::job_context::session_id, kcenon::pacs::network::pipeline::service_result::session_id, and kcenon::pacs::network::pipeline::job_context::stage.

◆ ~response_encode_job()

kcenon::pacs::network::pipeline::response_encode_job::~response_encode_job ( )
overridedefault

◆ response_encode_job() [2/3]

kcenon::pacs::network::pipeline::response_encode_job::response_encode_job ( const response_encode_job & )
delete

◆ response_encode_job() [3/3]

kcenon::pacs::network::pipeline::response_encode_job::response_encode_job ( response_encode_job && )
default

Member Function Documentation

◆ encode_dimse_command()

auto kcenon::pacs::network::pipeline::response_encode_job::encode_dimse_command ( ) -> Result<std::vector<uint8_t>>
nodiscardprivate

Encode DIMSE command.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 216 of file response_encode_job.cpp.

217 {
218
219 std::vector<uint8_t> command;
220 command.reserve(256); // Typical command size
221
222 // Helper to append element (Implicit VR Little Endian)
223 auto append_element = [&command](uint16_t group, uint16_t element,
224 const void* data, uint32_t length) {
225 // Tag
226 command.push_back(static_cast<uint8_t>(group & 0xFF));
227 command.push_back(static_cast<uint8_t>((group >> 8) & 0xFF));
228 command.push_back(static_cast<uint8_t>(element & 0xFF));
229 command.push_back(static_cast<uint8_t>((element >> 8) & 0xFF));
230 // Length
231 command.push_back(static_cast<uint8_t>(length & 0xFF));
232 command.push_back(static_cast<uint8_t>((length >> 8) & 0xFF));
233 command.push_back(static_cast<uint8_t>((length >> 16) & 0xFF));
234 command.push_back(static_cast<uint8_t>((length >> 24) & 0xFF));
235 // Data
236 const auto* bytes = static_cast<const uint8_t*>(data);
237 command.insert(command.end(), bytes, bytes + length);
238 };
239
240 auto append_uint16 = [&append_element](uint16_t group, uint16_t element,
241 uint16_t value) {
242 append_element(group, element, &value, 2);
243 };
244
245 auto append_string = [&append_element](uint16_t group, uint16_t element,
246 const std::string& value) {
247 std::string padded = value;
248 if (padded.size() % 2 != 0) {
249 padded.push_back(' '); // Pad to even length
250 }
251 append_element(group, element, padded.data(),
252 static_cast<uint32_t>(padded.size()));
253 };
254
255 // Placeholder for CommandGroupLength (will be filled after encoding)
256 size_t group_length_offset = command.size();
257 uint32_t placeholder = 0;
258 append_element(0x0000, 0x0000, &placeholder, 4);
259
260 // AffectedSOPClassUID (0000,0002) or RequestedSOPClassUID (0000,0003)
261 if (!result_.sop_class_uid.empty()) {
262 append_string(0x0000, 0x0002, result_.sop_class_uid);
263 }
264
265 // CommandField (0000,0100)
266 append_uint16(0x0000, 0x0100, static_cast<uint16_t>(result_.response_type));
267
268 // MessageIDBeingRespondedTo (0000,0120)
269 append_uint16(0x0000, 0x0120, result_.message_id);
270
271 // CommandDataSetType (0000,0800)
272 // 0x0101 = no data set, 0x0001 = data set present
273 uint16_t data_set_type = result_.data_set.empty() ? 0x0101 : 0x0001;
274 append_uint16(0x0000, 0x0800, data_set_type);
275
276 // Status (0000,0900)
277 append_uint16(0x0000, 0x0900, static_cast<uint16_t>(result_.status));
278
279 // AffectedSOPInstanceUID (0000,1000) if present
280 if (!result_.sop_instance_uid.empty()) {
281 append_string(0x0000, 0x1000, result_.sop_instance_uid);
282 }
283
284 // For C-GET/C-MOVE responses, include sub-operation counts
289 append_uint16(0x0000, 0x1020, result_.remaining_sub_ops);
290 append_uint16(0x0000, 0x1021, result_.completed_sub_ops);
291 append_uint16(0x0000, 0x1022, result_.failed_sub_ops);
292 append_uint16(0x0000, 0x1023, result_.warning_sub_ops);
293 }
294 }
295
296 // Error comment if present
297 if (!result_.error_comment.empty()) {
298 append_string(0x0000, 0x0902, result_.error_comment);
299 }
300
301 // Update CommandGroupLength
302 uint32_t group_length = static_cast<uint32_t>(command.size() - group_length_offset - 8);
303 command[group_length_offset + 4] = static_cast<uint8_t>(group_length & 0xFF);
304 command[group_length_offset + 5] = static_cast<uint8_t>((group_length >> 8) & 0xFF);
305 command[group_length_offset + 6] = static_cast<uint8_t>((group_length >> 16) & 0xFF);
306 command[group_length_offset + 7] = static_cast<uint8_t>((group_length >> 24) & 0xFF);
307
308 return ok(std::move(command));
309}
@ length
Linear distance measurement.
uint16_t failed_sub_ops
Number of failed sub-operations.
std::vector< uint8_t > data_set
Response data set (if any)
uint16_t remaining_sub_ops
Number of remaining sub-operations (for C-GET/C-MOVE)
uint16_t warning_sub_ops
Number of warning sub-operations.
uint16_t completed_sub_ops
Number of completed sub-operations.
dimse_command_type response_type
Response command type.
std::string sop_class_uid
SOP Class UID (echoed back)
std::string sop_instance_uid
SOP Instance UID (echoed back)

References kcenon::pacs::network::pipeline::c_get_rsp, kcenon::pacs::network::pipeline::c_move_rsp, kcenon::pacs::network::pipeline::pending, and kcenon::pacs::network::pipeline::pending_warning.

◆ encode_response()

auto kcenon::pacs::network::pipeline::response_encode_job::encode_response ( ) -> Result<std::vector<encoded_response>>
nodiscardprivate

Encode the response into PDU bytes.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 102 of file response_encode_job.cpp.

103 {
104
105 std::vector<encoded_response> responses;
106
107 // Encode DIMSE command
108 auto command_result = encode_dimse_command();
109 if (!command_result.is_ok()) {
110 return Result<std::vector<encoded_response>>(command_result.error());
111 }
112
113 auto command_data = command_result.value();
114
115 // Fragment the command data if needed
116 auto command_fragments = fragment_data(command_data);
117
118 // Create P-DATA-TF PDUs for command
119 for (size_t i = 0; i < command_fragments.size(); ++i) {
120 encoded_response response;
121 response.session_id = result_.session_id;
122 response.message_id = result_.message_id;
123 response.is_final = (i == command_fragments.size() - 1) && result_.data_set.empty();
124
125 // Build P-DATA-TF PDU
126 // PDU: Type (1) + Reserved (1) + Length (4) + PDV Items
127 // PDV: Length (4) + Presentation Context ID (1) + Control Header (1) + Data
128
129 const auto& frag = command_fragments[i];
130 size_t pdv_length = 2 + frag.size(); // Context ID + Control + Data
131 size_t pdu_length = 4 + pdv_length; // PDV Length + PDV
132
133 response.pdu_data.resize(6 + pdu_length);
134
135 // PDU header
136 response.pdu_data[0] = 0x04; // P-DATA-TF
137 response.pdu_data[1] = 0x00; // Reserved
138
139 // PDU length (big-endian)
140 response.pdu_data[2] = static_cast<uint8_t>((pdu_length >> 24) & 0xFF);
141 response.pdu_data[3] = static_cast<uint8_t>((pdu_length >> 16) & 0xFF);
142 response.pdu_data[4] = static_cast<uint8_t>((pdu_length >> 8) & 0xFF);
143 response.pdu_data[5] = static_cast<uint8_t>(pdu_length & 0xFF);
144
145 // PDV length (big-endian)
146 size_t offset = 6;
147 response.pdu_data[offset++] = static_cast<uint8_t>((pdv_length >> 24) & 0xFF);
148 response.pdu_data[offset++] = static_cast<uint8_t>((pdv_length >> 16) & 0xFF);
149 response.pdu_data[offset++] = static_cast<uint8_t>((pdv_length >> 8) & 0xFF);
150 response.pdu_data[offset++] = static_cast<uint8_t>(pdv_length & 0xFF);
151
152 // Presentation context ID
153 response.pdu_data[offset++] = result_.presentation_context_id;
154
155 // Control header: command (bit 0) + last fragment (bit 1)
156 uint8_t control = 0x01; // Command fragment
157 if (i == command_fragments.size() - 1) {
158 control |= 0x02; // Last command fragment
159 }
160 response.pdu_data[offset++] = control;
161
162 // Copy fragment data
163 std::copy(frag.begin(), frag.end(), response.pdu_data.begin() + offset);
164
165 responses.push_back(std::move(response));
166 }
167
168 // Encode data set if present
169 if (!result_.data_set.empty()) {
170 auto data_fragments = fragment_data(result_.data_set);
171
172 for (size_t i = 0; i < data_fragments.size(); ++i) {
173 encoded_response response;
174 response.session_id = result_.session_id;
175 response.message_id = result_.message_id;
176 response.is_final = (i == data_fragments.size() - 1);
177
178 const auto& frag = data_fragments[i];
179 size_t pdv_length = 2 + frag.size();
180 size_t pdu_length = 4 + pdv_length;
181
182 response.pdu_data.resize(6 + pdu_length);
183
184 // PDU header
185 response.pdu_data[0] = 0x04;
186 response.pdu_data[1] = 0x00;
187
188 response.pdu_data[2] = static_cast<uint8_t>((pdu_length >> 24) & 0xFF);
189 response.pdu_data[3] = static_cast<uint8_t>((pdu_length >> 16) & 0xFF);
190 response.pdu_data[4] = static_cast<uint8_t>((pdu_length >> 8) & 0xFF);
191 response.pdu_data[5] = static_cast<uint8_t>(pdu_length & 0xFF);
192
193 size_t offset = 6;
194 response.pdu_data[offset++] = static_cast<uint8_t>((pdv_length >> 24) & 0xFF);
195 response.pdu_data[offset++] = static_cast<uint8_t>((pdv_length >> 16) & 0xFF);
196 response.pdu_data[offset++] = static_cast<uint8_t>((pdv_length >> 8) & 0xFF);
197 response.pdu_data[offset++] = static_cast<uint8_t>(pdv_length & 0xFF);
198
199 response.pdu_data[offset++] = result_.presentation_context_id;
200
201 uint8_t control = 0x00; // Data fragment
202 if (i == data_fragments.size() - 1) {
203 control |= 0x02; // Last data fragment
204 }
205 response.pdu_data[offset++] = control;
206
207 std::copy(frag.begin(), frag.end(), response.pdu_data.begin() + offset);
208
209 responses.push_back(std::move(response));
210 }
211 }
212
213 return ok(std::move(responses));
214}
auto fragment_data(const std::vector< uint8_t > &data) -> std::vector< std::vector< uint8_t > >
Fragment large data if needed.
auto encode_dimse_command() -> Result< std::vector< uint8_t > >
Encode DIMSE command.
@ control
Internal pipeline control messages.
kcenon::pacs::Result< T > Result
Result type alias using standardized kcenon::pacs::Result<T>
Definition association.h:56
uint8_t presentation_context_id
Presentation context ID.

References kcenon::pacs::network::pipeline::control, kcenon::pacs::network::pipeline::encoded_response::is_final, kcenon::pacs::network::pipeline::encoded_response::message_id, kcenon::pacs::network::pipeline::encoded_response::pdu_data, and kcenon::pacs::network::pipeline::encoded_response::session_id.

◆ execute()

auto kcenon::pacs::network::pipeline::response_encode_job::execute ( pipeline_coordinator & coordinator) -> VoidResult
nodiscardoverridevirtual

Execute the encode job.

Encodes the result and submits to the network send stage.

Parameters
coordinatorPipeline coordinator for stage submission
Returns
VoidResult indicating success or error

Implements kcenon::pacs::network::pipeline::pipeline_job_base.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 39 of file response_encode_job.cpp.

39 {
40 // Encode the response
41 auto encode_result = encode_response();
42 if (!encode_result.is_ok()) {
43 if (on_error_) {
44 auto err = encode_result.error();
45 on_error_(result_.session_id, err.message);
46 }
47 return VoidResult(encode_result.error());
48 }
49
50 auto responses = encode_result.value();
51
52 // Submit each encoded response to the send stage
53 for (auto& response : responses) {
54 // Invoke callback if set
55 if (on_encoded_) {
56 on_encoded_(response);
57 }
58
59 // Create send job
60 // Note: send_fn will be set by the adapter layer
61 auto send_job = std::make_unique<send_network_io_job>(
62 response.session_id,
63 std::move(response.pdu_data),
64 nullptr // send_fn set by adapter
65 );
66
67 send_job->get_context() = context_;
68 send_job->get_context().stage = pipeline_stage::network_send;
69
70 auto submit_result = coordinator.submit_to_stage(
72 std::move(send_job)
73 );
74
75 if (!submit_result.is_ok()) {
76 return submit_result;
77 }
78 }
79
80 return ok();
81}
auto encode_response() -> Result< std::vector< encoded_response > >
Encode the response into PDU bytes.
@ network_send
Stage 6: Send PDU bytes to network.
kcenon::pacs::VoidResult VoidResult
VoidResult type alias for operations without return value.
Definition association.h:59

References kcenon::pacs::network::pipeline::network_send.

◆ fragment_data()

auto kcenon::pacs::network::pipeline::response_encode_job::fragment_data ( const std::vector< uint8_t > & data) -> std::vector<std::vector<uint8_t>>
nodiscardprivate

Fragment large data if needed.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 311 of file response_encode_job.cpp.

312 {
313
314 std::vector<std::vector<uint8_t>> fragments;
315
316 // Max fragment size = max_pdu_size - PDU header (6) - PDV header (6)
317 size_t max_fragment_size = max_pdu_size_ - 12;
318 if (max_fragment_size > 16376) {
319 max_fragment_size = 16376; // Reasonable default
320 }
321
322 size_t offset = 0;
323 while (offset < data.size()) {
324 size_t fragment_size = std::min(max_fragment_size, data.size() - offset);
325 fragments.emplace_back(data.begin() + offset,
326 data.begin() + offset + fragment_size);
327 offset += fragment_size;
328 }
329
330 if (fragments.empty()) {
331 fragments.emplace_back(); // Empty fragment for empty data
332 }
333
334 return fragments;
335}

◆ get_context() [1/2]

auto kcenon::pacs::network::pipeline::response_encode_job::get_context ( ) const -> const job_context&
nodiscardoverridevirtualnoexcept

◆ get_context() [2/2]

auto kcenon::pacs::network::pipeline::response_encode_job::get_context ( ) -> job_context &override
nodiscardoverridevirtualnoexcept

Get the job context (mutable)

Returns
Reference to the job context

Implements kcenon::pacs::network::pipeline::pipeline_job_base.

Definition at line 87 of file response_encode_job.cpp.

87 {
88 return context_;
89}

References context_.

◆ get_name()

auto kcenon::pacs::network::pipeline::response_encode_job::get_name ( ) const -> std::string
nodiscardoverridevirtual

◆ get_result()

auto kcenon::pacs::network::pipeline::response_encode_job::get_result ( ) const -> const service_result&
nodiscardnoexcept

Get the service result.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/network/pipeline/jobs/response_encode_job.h.

Definition at line 98 of file response_encode_job.cpp.

98 {
99 return result_;
100}

References result_.

◆ operator=() [1/2]

response_encode_job & kcenon::pacs::network::pipeline::response_encode_job::operator= ( const response_encode_job & )
delete

◆ operator=() [2/2]

response_encode_job & kcenon::pacs::network::pipeline::response_encode_job::operator= ( response_encode_job && )
default

Member Data Documentation

◆ context_

job_context kcenon::pacs::network::pipeline::response_encode_job::context_
private

◆ max_pdu_size_

uint32_t kcenon::pacs::network::pipeline::response_encode_job::max_pdu_size_
private

◆ on_encoded_

encode_callback kcenon::pacs::network::pipeline::response_encode_job::on_encoded_
private

◆ on_error_

error_callback kcenon::pacs::network::pipeline::response_encode_job::on_error_
private

◆ result_

service_result kcenon::pacs::network::pipeline::response_encode_job::result_
private

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