PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
pdu_encoder.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
6
7#include <algorithm>
8#include <stdexcept>
9
10namespace kcenon::pacs::network {
11
12// ============================================================================
13// Helper Functions
14// ============================================================================
15
16void pdu_encoder::write_uint16_be(std::vector<uint8_t>& buffer, uint16_t value) {
17 buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
18 buffer.push_back(static_cast<uint8_t>(value & 0xFF));
19}
20
21void pdu_encoder::write_uint32_be(std::vector<uint8_t>& buffer, uint32_t value) {
22 buffer.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
23 buffer.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
24 buffer.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
25 buffer.push_back(static_cast<uint8_t>(value & 0xFF));
26}
27
28void pdu_encoder::write_ae_title(std::vector<uint8_t>& buffer,
29 const std::string& ae_title) {
30 // AE Title is exactly 16 bytes, space-padded
31 std::string padded = ae_title;
32 if (padded.size() > AE_TITLE_LENGTH) {
33 padded = padded.substr(0, AE_TITLE_LENGTH);
34 }
35 padded.resize(AE_TITLE_LENGTH, ' ');
36
37 buffer.insert(buffer.end(), padded.begin(), padded.end());
38}
39
40void pdu_encoder::write_uid(std::vector<uint8_t>& buffer, const std::string& uid) {
41 buffer.insert(buffer.end(), uid.begin(), uid.end());
42 // Pad to even length if necessary
43 if (uid.size() % 2 != 0) {
44 buffer.push_back(0x00);
45 }
46}
47
48void pdu_encoder::update_pdu_length(std::vector<uint8_t>& buffer) {
49 if (buffer.size() < 6) {
50 return;
51 }
52 // PDU length is bytes 2-5 (0-indexed), and covers data after the 6-byte header
53 const uint32_t length = static_cast<uint32_t>(buffer.size() - 6);
54 buffer[2] = static_cast<uint8_t>((length >> 24) & 0xFF);
55 buffer[3] = static_cast<uint8_t>((length >> 16) & 0xFF);
56 buffer[4] = static_cast<uint8_t>((length >> 8) & 0xFF);
57 buffer[5] = static_cast<uint8_t>(length & 0xFF);
58}
59
60// ============================================================================
61// Item Encoding
62// ============================================================================
63
64void pdu_encoder::encode_application_context(std::vector<uint8_t>& buffer,
65 const std::string& context_name) {
66 // Application Context Item structure:
67 // - Item type (1 byte) = 0x10
68 // - Reserved (1 byte)
69 // - Item length (2 bytes)
70 // - Application Context Name (variable)
71
72 buffer.push_back(static_cast<uint8_t>(item_type::application_context));
73 buffer.push_back(0x00); // Reserved
74
75 // Calculate padded length
76 size_t uid_length = context_name.size();
77 if (uid_length % 2 != 0) {
78 uid_length++;
79 }
80
81 write_uint16_be(buffer, static_cast<uint16_t>(uid_length));
82 write_uid(buffer, context_name);
83}
84
85void pdu_encoder::encode_presentation_context_rq(std::vector<uint8_t>& buffer,
86 const presentation_context_rq& pc) {
87 // Presentation Context Item (RQ) structure:
88 // - Item type (1 byte) = 0x20
89 // - Reserved (1 byte)
90 // - Item length (2 bytes)
91 // - Presentation Context ID (1 byte)
92 // - Reserved (1 byte)
93 // - Reserved (1 byte)
94 // - Reserved (1 byte)
95 // - Abstract Syntax sub-item
96 // - Transfer Syntax sub-items
97
98 const size_t start_pos = buffer.size();
99
100 buffer.push_back(static_cast<uint8_t>(item_type::presentation_context_rq));
101 buffer.push_back(0x00); // Reserved
102
103 // Placeholder for item length
104 const size_t length_pos = buffer.size();
105 write_uint16_be(buffer, 0x0000);
106
107 // Presentation Context ID
108 buffer.push_back(pc.id);
109 buffer.push_back(0x00); // Reserved
110 buffer.push_back(0x00); // Reserved
111 buffer.push_back(0x00); // Reserved
112
113 // Abstract Syntax sub-item
114 buffer.push_back(static_cast<uint8_t>(item_type::abstract_syntax));
115 buffer.push_back(0x00); // Reserved
116
117 size_t abs_length = pc.abstract_syntax.size();
118 if (abs_length % 2 != 0) {
119 abs_length++;
120 }
121 write_uint16_be(buffer, static_cast<uint16_t>(abs_length));
122 write_uid(buffer, pc.abstract_syntax);
123
124 // Transfer Syntax sub-items
125 for (const auto& ts : pc.transfer_syntaxes) {
126 buffer.push_back(static_cast<uint8_t>(item_type::transfer_syntax));
127 buffer.push_back(0x00); // Reserved
128
129 size_t ts_length = ts.size();
130 if (ts_length % 2 != 0) {
131 ts_length++;
132 }
133 write_uint16_be(buffer, static_cast<uint16_t>(ts_length));
134 write_uid(buffer, ts);
135 }
136
137 // Update item length
138 const uint16_t item_length = static_cast<uint16_t>(buffer.size() - start_pos - 4);
139 buffer[length_pos] = static_cast<uint8_t>((item_length >> 8) & 0xFF);
140 buffer[length_pos + 1] = static_cast<uint8_t>(item_length & 0xFF);
141}
142
143void pdu_encoder::encode_presentation_context_ac(std::vector<uint8_t>& buffer,
144 const presentation_context_ac& pc) {
145 // Presentation Context Item (AC) structure:
146 // - Item type (1 byte) = 0x21
147 // - Reserved (1 byte)
148 // - Item length (2 bytes)
149 // - Presentation Context ID (1 byte)
150 // - Reserved (1 byte)
151 // - Result/Reason (1 byte)
152 // - Reserved (1 byte)
153 // - Transfer Syntax sub-item
154
155 const size_t start_pos = buffer.size();
156
157 buffer.push_back(static_cast<uint8_t>(item_type::presentation_context_ac));
158 buffer.push_back(0x00); // Reserved
159
160 // Placeholder for item length
161 const size_t length_pos = buffer.size();
162 write_uint16_be(buffer, 0x0000);
163
164 buffer.push_back(pc.id);
165 buffer.push_back(0x00); // Reserved
166 buffer.push_back(static_cast<uint8_t>(pc.result));
167 buffer.push_back(0x00); // Reserved
168
169 // Transfer Syntax sub-item (only if accepted)
171 !pc.transfer_syntax.empty()) {
172 buffer.push_back(static_cast<uint8_t>(item_type::transfer_syntax));
173 buffer.push_back(0x00); // Reserved
174
175 size_t ts_length = pc.transfer_syntax.size();
176 if (ts_length % 2 != 0) {
177 ts_length++;
178 }
179 write_uint16_be(buffer, static_cast<uint16_t>(ts_length));
180 write_uid(buffer, pc.transfer_syntax);
181 }
182
183 // Update item length
184 const uint16_t item_length = static_cast<uint16_t>(buffer.size() - start_pos - 4);
185 buffer[length_pos] = static_cast<uint8_t>((item_length >> 8) & 0xFF);
186 buffer[length_pos + 1] = static_cast<uint8_t>(item_length & 0xFF);
187}
188
189void pdu_encoder::encode_user_information(std::vector<uint8_t>& buffer,
190 const user_information& user_info) {
191 // User Information Item structure:
192 // - Item type (1 byte) = 0x50
193 // - Reserved (1 byte)
194 // - Item length (2 bytes)
195 // - User data sub-items
196
197 const size_t start_pos = buffer.size();
198
199 buffer.push_back(static_cast<uint8_t>(item_type::user_information));
200 buffer.push_back(0x00); // Reserved
201
202 // Placeholder for item length
203 const size_t length_pos = buffer.size();
204 write_uint16_be(buffer, 0x0000);
205
206 // Maximum Length sub-item (required)
207 buffer.push_back(static_cast<uint8_t>(item_type::maximum_length));
208 buffer.push_back(0x00); // Reserved
209 write_uint16_be(buffer, 0x0004); // Length is always 4
210 write_uint32_be(buffer, user_info.max_pdu_length);
211
212 // Implementation Class UID sub-item (required)
213 if (!user_info.implementation_class_uid.empty()) {
214 buffer.push_back(static_cast<uint8_t>(item_type::implementation_class_uid));
215 buffer.push_back(0x00); // Reserved
216
217 size_t uid_length = user_info.implementation_class_uid.size();
218 if (uid_length % 2 != 0) {
219 uid_length++;
220 }
221 write_uint16_be(buffer, static_cast<uint16_t>(uid_length));
222 write_uid(buffer, user_info.implementation_class_uid);
223 }
224
225 // Implementation Version Name sub-item (optional)
226 if (!user_info.implementation_version_name.empty()) {
227 buffer.push_back(static_cast<uint8_t>(item_type::implementation_version_name));
228 buffer.push_back(0x00); // Reserved
229
230 size_t name_length = user_info.implementation_version_name.size();
231 if (name_length % 2 != 0) {
232 name_length++;
233 }
234 write_uint16_be(buffer, static_cast<uint16_t>(name_length));
235
236 buffer.insert(buffer.end(),
237 user_info.implementation_version_name.begin(),
238 user_info.implementation_version_name.end());
239 if (user_info.implementation_version_name.size() % 2 != 0) {
240 buffer.push_back(' '); // Pad with space
241 }
242 }
243
244 // SCP/SCU Role Selection sub-items (optional)
245 for (const auto& role : user_info.role_selections) {
246 buffer.push_back(static_cast<uint8_t>(item_type::scp_scu_role_selection));
247 buffer.push_back(0x00); // Reserved
248
249 size_t uid_len = role.sop_class_uid.size();
250 if (uid_len % 2 != 0) {
251 uid_len++;
252 }
253 // Item length = UID length field (2) + UID + SCU role (1) + SCP role (1)
254 write_uint16_be(buffer, static_cast<uint16_t>(2 + uid_len + 2));
255
256 // UID length
257 write_uint16_be(buffer, static_cast<uint16_t>(role.sop_class_uid.size()));
258 write_uid(buffer, role.sop_class_uid);
259
260 buffer.push_back(role.scu_role ? 0x01 : 0x00);
261 buffer.push_back(role.scp_role ? 0x01 : 0x00);
262 }
263
264 // Update item length
265 const uint16_t item_length = static_cast<uint16_t>(buffer.size() - start_pos - 4);
266 buffer[length_pos] = static_cast<uint8_t>((item_length >> 8) & 0xFF);
267 buffer[length_pos + 1] = static_cast<uint8_t>(item_length & 0xFF);
268}
269
270void pdu_encoder::encode_associate_header(std::vector<uint8_t>& buffer,
271 pdu_type type,
272 const std::string& called_ae,
273 const std::string& calling_ae) {
274 // PDU Header
275 buffer.push_back(static_cast<uint8_t>(type));
276 buffer.push_back(0x00); // Reserved
277
278 // PDU Length placeholder (4 bytes)
279 write_uint32_be(buffer, 0x00000000);
280
281 // Protocol Version (2 bytes)
283
284 // Reserved (2 bytes)
285 write_uint16_be(buffer, 0x0000);
286
287 // Called AE Title (16 bytes)
288 write_ae_title(buffer, called_ae);
289
290 // Calling AE Title (16 bytes)
291 write_ae_title(buffer, calling_ae);
292
293 // Reserved (32 bytes)
294 buffer.insert(buffer.end(), 32, 0x00);
295}
296
297// ============================================================================
298// PDU Encoding Functions
299// ============================================================================
300
301std::vector<uint8_t> pdu_encoder::encode_associate_rq(const associate_rq& rq) {
302 std::vector<uint8_t> buffer;
303 buffer.reserve(512); // Pre-allocate reasonable size
304
307
308 // Variable Items
312
313 for (const auto& pc : rq.presentation_contexts) {
315 }
316
318
319 update_pdu_length(buffer);
320 return buffer;
321}
322
323std::vector<uint8_t> pdu_encoder::encode_associate_ac(const associate_ac& ac) {
324 std::vector<uint8_t> buffer;
325 buffer.reserve(512);
326
329
330 // Variable Items
334
335 for (const auto& pc : ac.presentation_contexts) {
337 }
338
340
341 update_pdu_length(buffer);
342 return buffer;
343}
344
345std::vector<uint8_t> pdu_encoder::encode_associate_rj(const associate_rj& rj) {
346 // A-ASSOCIATE-RJ PDU structure (10 bytes total):
347 // - PDU Type (1 byte) = 0x03
348 // - Reserved (1 byte)
349 // - PDU Length (4 bytes) = 0x00000004
350 // - Reserved (1 byte)
351 // - Result (1 byte)
352 // - Source (1 byte)
353 // - Reason/Diag (1 byte)
354
355 std::vector<uint8_t> buffer;
356 buffer.reserve(10);
357
358 buffer.push_back(static_cast<uint8_t>(pdu_type::associate_rj));
359 buffer.push_back(0x00); // Reserved
360 write_uint32_be(buffer, 0x00000004); // PDU Length
361
362 buffer.push_back(0x00); // Reserved
363 buffer.push_back(static_cast<uint8_t>(rj.result));
364 buffer.push_back(rj.source);
365 buffer.push_back(rj.reason);
366
367 return buffer;
368}
369
370std::vector<uint8_t> pdu_encoder::encode_release_rq() {
371 // A-RELEASE-RQ PDU structure (10 bytes total):
372 // - PDU Type (1 byte) = 0x05
373 // - Reserved (1 byte)
374 // - PDU Length (4 bytes) = 0x00000004
375 // - Reserved (4 bytes)
376
377 std::vector<uint8_t> buffer;
378 buffer.reserve(10);
379
380 buffer.push_back(static_cast<uint8_t>(pdu_type::release_rq));
381 buffer.push_back(0x00); // Reserved
382 write_uint32_be(buffer, 0x00000004); // PDU Length
383 write_uint32_be(buffer, 0x00000000); // Reserved
384
385 return buffer;
386}
387
388std::vector<uint8_t> pdu_encoder::encode_release_rp() {
389 // A-RELEASE-RP PDU structure (10 bytes total):
390 // - PDU Type (1 byte) = 0x06
391 // - Reserved (1 byte)
392 // - PDU Length (4 bytes) = 0x00000004
393 // - Reserved (4 bytes)
394
395 std::vector<uint8_t> buffer;
396 buffer.reserve(10);
397
398 buffer.push_back(static_cast<uint8_t>(pdu_type::release_rp));
399 buffer.push_back(0x00); // Reserved
400 write_uint32_be(buffer, 0x00000004); // PDU Length
401 write_uint32_be(buffer, 0x00000000); // Reserved
402
403 return buffer;
404}
405
406std::vector<uint8_t> pdu_encoder::encode_abort(uint8_t source, uint8_t reason) {
407 // A-ABORT PDU structure (10 bytes total):
408 // - PDU Type (1 byte) = 0x07
409 // - Reserved (1 byte)
410 // - PDU Length (4 bytes) = 0x00000004
411 // - Reserved (1 byte)
412 // - Reserved (1 byte)
413 // - Source (1 byte)
414 // - Reason/Diag (1 byte)
415
416 std::vector<uint8_t> buffer;
417 buffer.reserve(10);
418
419 buffer.push_back(static_cast<uint8_t>(pdu_type::abort));
420 buffer.push_back(0x00); // Reserved
421 write_uint32_be(buffer, 0x00000004); // PDU Length
422 buffer.push_back(0x00); // Reserved
423 buffer.push_back(0x00); // Reserved
424 buffer.push_back(source);
425 buffer.push_back(reason);
426
427 return buffer;
428}
429
430std::vector<uint8_t> pdu_encoder::encode_abort(abort_source source,
431 abort_reason reason) {
432 return encode_abort(static_cast<uint8_t>(source),
433 static_cast<uint8_t>(reason));
434}
435
436std::vector<uint8_t> pdu_encoder::encode_p_data_tf(
437 const std::vector<presentation_data_value>& pdvs) {
438
439 std::vector<uint8_t> buffer;
440 buffer.reserve(256);
441
442 // PDU Header
443 buffer.push_back(static_cast<uint8_t>(pdu_type::p_data_tf));
444 buffer.push_back(0x00); // Reserved
445
446 // PDU Length placeholder
447 write_uint32_be(buffer, 0x00000000);
448
449 // PDV Items
450 for (const auto& pdv : pdvs) {
451 // PDV Item structure:
452 // - Item length (4 bytes) = context_id (1) + control (1) + data
453 // - Presentation Context ID (1 byte)
454 // - Message Control Header (1 byte)
455 // - Presentation Data Value (variable)
456
457 const uint32_t pdv_length = static_cast<uint32_t>(2 + pdv.data.size());
458 write_uint32_be(buffer, pdv_length);
459
460 buffer.push_back(pdv.context_id);
461
462 // Message Control Header:
463 // - Bit 0: 0 = Data, 1 = Command
464 // - Bit 1: 0 = Not last, 1 = Last
465 uint8_t control = 0;
466 if (pdv.is_command) {
467 control |= 0x01;
468 }
469 if (pdv.is_last) {
470 control |= 0x02;
471 }
472 buffer.push_back(control);
473
474 buffer.insert(buffer.end(), pdv.data.begin(), pdv.data.end());
475 }
476
477 update_pdu_length(buffer);
478 return buffer;
479}
480
481std::vector<uint8_t> pdu_encoder::encode_p_data_tf(
482 const presentation_data_value& pdv) {
483 return encode_p_data_tf(std::vector<presentation_data_value>{pdv});
484}
485
486} // namespace kcenon::pacs::network
static void write_uint32_be(std::vector< uint8_t > &buffer, uint32_t value)
Writes a 32-bit unsigned integer in big-endian format.
static std::vector< uint8_t > encode_associate_rq(const associate_rq &rq)
Encodes an A-ASSOCIATE-RQ PDU.
static std::vector< uint8_t > encode_release_rp()
Encodes an A-RELEASE-RP PDU.
static void encode_associate_header(std::vector< uint8_t > &buffer, pdu_type type, const std::string &called_ae, const std::string &calling_ae)
Encodes the common header portion for ASSOCIATE-RQ/AC PDUs.
static std::vector< uint8_t > encode_p_data_tf(const std::vector< presentation_data_value > &pdvs)
Encodes a P-DATA-TF PDU.
static void encode_user_information(std::vector< uint8_t > &buffer, const user_information &user_info)
Encodes a User Information item.
static std::vector< uint8_t > encode_associate_ac(const associate_ac &ac)
Encodes an A-ASSOCIATE-AC PDU.
static void encode_presentation_context_rq(std::vector< uint8_t > &buffer, const presentation_context_rq &pc)
Encodes a Presentation Context item for A-ASSOCIATE-RQ.
static void update_pdu_length(std::vector< uint8_t > &buffer)
Updates the PDU length field at position 2-5.
static void encode_presentation_context_ac(std::vector< uint8_t > &buffer, const presentation_context_ac &pc)
Encodes a Presentation Context item for A-ASSOCIATE-AC.
static void write_ae_title(std::vector< uint8_t > &buffer, const std::string &ae_title)
Writes an AE Title (16 bytes, space-padded).
static std::vector< uint8_t > encode_associate_rj(const associate_rj &rj)
Encodes an A-ASSOCIATE-RJ PDU.
static std::vector< uint8_t > encode_release_rq()
Encodes an A-RELEASE-RQ PDU.
static std::vector< uint8_t > encode_abort(uint8_t source, uint8_t reason)
Encodes an A-ABORT PDU.
static void write_uint16_be(std::vector< uint8_t > &buffer, uint16_t value)
Writes a 16-bit unsigned integer in big-endian format.
static void write_uid(std::vector< uint8_t > &buffer, const std::string &uid)
Writes a UID string.
static void encode_application_context(std::vector< uint8_t > &buffer, const std::string &context_name)
Encodes an Application Context item.
pdu_type
PDU (Protocol Data Unit) types as defined in DICOM PS3.8.
Definition pdu_types.h:25
@ 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)
@ maximum_length
Maximum Length Sub-item.
@ transfer_syntax
Transfer Syntax Sub-item.
@ presentation_context_rq
Presentation Context Item (RQ)
@ presentation_context_ac
Presentation Context Item (AC)
@ scp_scu_role_selection
SCP/SCU Role Selection Sub-item.
@ implementation_class_uid
Implementation Class UID Sub-item.
@ abstract_syntax
Abstract Syntax Sub-item.
@ implementation_version_name
Implementation Version Name Sub-item.
@ application_context
Application Context Item.
@ user_information
User Information Item.
constexpr uint16_t DICOM_PROTOCOL_VERSION
DICOM Protocol Version.
Definition pdu_types.h:270
constexpr const char * DICOM_APPLICATION_CONTEXT
Default DICOM Application Context Name (PS3.7)
Definition pdu_types.h:267
abort_reason
Abort reason values when source is service-provider.
Definition pdu_types.h:79
constexpr size_t AE_TITLE_LENGTH
AE Title length (fixed 16 characters, space-padded)
Definition pdu_types.h:273
abort_source
Abort source values.
Definition pdu_types.h:70
Transfer Syntax UIDs.
Definition main.cpp:78
A-ASSOCIATE-AC PDU data.
Definition pdu_types.h:218
std::string calling_ae_title
Calling AE Title (16 chars max)
Definition pdu_types.h:220
user_information user_info
User Information.
Definition pdu_types.h:223
std::string called_ae_title
Called AE Title (16 chars max)
Definition pdu_types.h:219
std::string application_context
Application Context Name UID.
Definition pdu_types.h:221
std::vector< presentation_context_ac > presentation_contexts
Presentation Contexts.
Definition pdu_types.h:222
A-ASSOCIATE-RJ PDU data.
Definition pdu_types.h:231
uint8_t reason
Reason/Diagnostic.
Definition pdu_types.h:234
reject_result result
Result (1=permanent, 2=transient)
Definition pdu_types.h:232
A-ASSOCIATE-RQ PDU data.
Definition pdu_types.h:205
std::string called_ae_title
Called AE Title (16 chars max)
Definition pdu_types.h:206
std::string application_context
Application Context Name UID.
Definition pdu_types.h:208
user_information user_info
User Information.
Definition pdu_types.h:210
std::string calling_ae_title
Calling AE Title (16 chars max)
Definition pdu_types.h:207
std::vector< presentation_context_rq > presentation_contexts
Presentation Contexts.
Definition pdu_types.h:209
Presentation Context for A-ASSOCIATE-AC.
Definition pdu_types.h:165
presentation_context_result result
Result/Reason.
Definition pdu_types.h:167
uint8_t id
Presentation Context ID.
Definition pdu_types.h:166
std::string transfer_syntax
Accepted Transfer Syntax UID.
Definition pdu_types.h:168
Presentation Context for A-ASSOCIATE-RQ.
Definition pdu_types.h:149
uint8_t id
Presentation Context ID (odd number 1-255)
Definition pdu_types.h:150
std::string abstract_syntax
Abstract Syntax UID (SOP Class)
Definition pdu_types.h:151
std::vector< std::string > transfer_syntaxes
Proposed Transfer Syntaxes.
Definition pdu_types.h:152
Presentation Data Value (PDV) item for P-DATA-TF.
Definition pdu_types.h:135
User Information for A-ASSOCIATE-RQ/AC.
Definition pdu_types.h:193
std::string implementation_class_uid
Implementation Class UID.
Definition pdu_types.h:195
std::string implementation_version_name
Implementation Version Name (optional)
Definition pdu_types.h:196
std::vector< scp_scu_role_selection > role_selections
Role selections (optional)
Definition pdu_types.h:197
uint32_t max_pdu_length
Maximum Length of P-DATA-TF PDUs.
Definition pdu_types.h:194
std::string_view uid