PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
pdu_decoder.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 <cstring>
9#include <tuple>
10
11namespace kcenon::pacs::network {
12
13namespace {
14
16constexpr size_t PDU_HEADER_SIZE = 6;
17
19constexpr size_t FIXED_PDU_SIZE = 10;
20
23constexpr size_t ASSOCIATE_HEADER_SIZE = 68; // 2 + 2 + 16 + 16 + 32
24
26inline int to_error_code(pdu_decode_error err) {
27 switch (err) {
39 default:
41 }
42}
43
45template<typename T>
46DecodeResult<T> make_error(pdu_decode_error err, const std::string& msg = "") {
47 std::string full_msg = to_string(err);
48 if (!msg.empty()) {
49 full_msg += ": " + msg;
50 }
51 return kcenon::pacs::error_info{to_error_code(err), full_msg, "network"};
52}
53
55template<typename T>
56DecodeResult<T> make_ok(T value) {
57 return kcenon::pacs::ok(std::move(value));
58}
59
60} // namespace
61
62// ============================================================================
63// Helper Functions
64// ============================================================================
65
66uint16_t pdu_decoder::read_uint16_be(std::span<const uint8_t> data, size_t offset) {
67 return static_cast<uint16_t>(
68 (static_cast<uint16_t>(data[offset]) << 8) |
69 static_cast<uint16_t>(data[offset + 1]));
70}
71
72uint32_t pdu_decoder::read_uint32_be(std::span<const uint8_t> data, size_t offset) {
73 return (static_cast<uint32_t>(data[offset]) << 24) |
74 (static_cast<uint32_t>(data[offset + 1]) << 16) |
75 (static_cast<uint32_t>(data[offset + 2]) << 8) |
76 static_cast<uint32_t>(data[offset + 3]);
77}
78
79std::string pdu_decoder::read_ae_title(std::span<const uint8_t> data, size_t offset) {
80 if (offset + AE_TITLE_LENGTH > data.size()) {
81 return {};
82 }
83 std::string ae_title(reinterpret_cast<const char*>(data.data() + offset),
85 // Trim trailing spaces
86 auto end = ae_title.find_last_not_of(' ');
87 if (end != std::string::npos) {
88 ae_title.resize(end + 1);
89 } else if (ae_title.find_first_not_of(' ') == std::string::npos) {
90 ae_title.clear(); // All spaces
91 }
92 return ae_title;
93}
94
95std::string pdu_decoder::read_uid(std::span<const uint8_t> data, size_t offset,
96 size_t length) {
97 if (offset + length > data.size()) {
98 return {};
99 }
100 std::string uid(reinterpret_cast<const char*>(data.data() + offset), length);
101 // Trim trailing nulls and spaces
102 while (!uid.empty() && (uid.back() == '\0' || uid.back() == ' ')) {
103 uid.pop_back();
104 }
105 return uid;
106}
107
109 std::span<const uint8_t> data, uint8_t expected_type) {
110
111 if (data.size() < PDU_HEADER_SIZE) {
112 return make_error<uint32_t>(pdu_decode_error::incomplete_header);
113 }
114
115 if (expected_type != 0 && data[0] != expected_type) {
116 return make_error<uint32_t>(pdu_decode_error::invalid_pdu_type,
117 "Expected type " + std::to_string(expected_type) +
118 ", got " + std::to_string(data[0]));
119 }
120
121 const uint32_t pdu_length = read_uint32_be(data, 2);
122
123 // Reject unreasonably large PDUs to prevent memory exhaustion attacks
124 constexpr uint32_t MAX_PDU_LENGTH = 16 * 1024 * 1024; // 16 MB
125 if (pdu_length > MAX_PDU_LENGTH) {
126 return make_error<uint32_t>(pdu_decode_error::malformed_pdu,
127 "PDU length " + std::to_string(pdu_length) +
128 " exceeds maximum allowed " + std::to_string(MAX_PDU_LENGTH));
129 }
130
131 const size_t total_length = PDU_HEADER_SIZE + pdu_length;
132
133 if (data.size() < total_length) {
134 return make_error<uint32_t>(pdu_decode_error::incomplete_pdu,
135 "Need " + std::to_string(total_length) +
136 " bytes, have " + std::to_string(data.size()));
137 }
138
139 return make_ok(pdu_length);
140}
141
142// ============================================================================
143// General Decoding
144// ============================================================================
145
146std::optional<size_t> pdu_decoder::pdu_length(std::span<const uint8_t> data) {
147 if (data.size() < PDU_HEADER_SIZE) {
148 return std::nullopt;
149 }
150
151 const uint32_t length = read_uint32_be(data, 2);
152 const size_t total = PDU_HEADER_SIZE + length;
153
154 if (data.size() < total) {
155 return std::nullopt;
156 }
157
158 return total;
159}
160
161std::optional<pdu_type> pdu_decoder::peek_pdu_type(std::span<const uint8_t> data) {
162 if (data.empty()) {
163 return std::nullopt;
164 }
165
166 switch (data[0]) {
167 case 0x01: return pdu_type::associate_rq;
168 case 0x02: return pdu_type::associate_ac;
169 case 0x03: return pdu_type::associate_rj;
170 case 0x04: return pdu_type::p_data_tf;
171 case 0x05: return pdu_type::release_rq;
172 case 0x06: return pdu_type::release_rp;
173 case 0x07: return pdu_type::abort;
174 default: return std::nullopt;
175 }
176}
177
178DecodeResult<pdu> pdu_decoder::decode(std::span<const uint8_t> data) {
179 if (data.size() < PDU_HEADER_SIZE) {
180 return make_error<pdu>(pdu_decode_error::incomplete_header);
181 }
182
183 const uint8_t type = data[0];
184
185 switch (type) {
186 case 0x01: {
187 auto result = decode_associate_rq(data);
188 if (result.is_ok()) {
189 return make_ok<pdu>(std::move(result.value()));
190 }
191 return result.error();
192 }
193 case 0x02: {
194 auto result = decode_associate_ac(data);
195 if (result.is_ok()) {
196 return make_ok<pdu>(std::move(result.value()));
197 }
198 return result.error();
199 }
200 case 0x03: {
201 auto result = decode_associate_rj(data);
202 if (result.is_ok()) {
203 return make_ok<pdu>(std::move(result.value()));
204 }
205 return result.error();
206 }
207 case 0x04: {
208 auto result = decode_p_data_tf(data);
209 if (result.is_ok()) {
210 return make_ok<pdu>(std::move(result.value()));
211 }
212 return result.error();
213 }
214 case 0x05: {
215 auto result = decode_release_rq(data);
216 if (result.is_ok()) {
217 return make_ok<pdu>(std::move(result.value()));
218 }
219 return result.error();
220 }
221 case 0x06: {
222 auto result = decode_release_rp(data);
223 if (result.is_ok()) {
224 return make_ok<pdu>(std::move(result.value()));
225 }
226 return result.error();
227 }
228 case 0x07: {
229 auto result = decode_abort(data);
230 if (result.is_ok()) {
231 return make_ok<pdu>(std::move(result.value()));
232 }
233 return result.error();
234 }
235 default:
236 return make_error<pdu>(pdu_decode_error::invalid_pdu_type,
237 "Unknown PDU type: " + std::to_string(type));
238 }
239}
240
241// ============================================================================
242// A-ASSOCIATE-RJ Decoder
243// ============================================================================
244
246 std::span<const uint8_t> data) {
247
248 auto header_result = validate_pdu_header(data, 0x03);
249 if (header_result.is_err()) {
250 return header_result.error();
251 }
252
253 // A-ASSOCIATE-RJ is always 10 bytes
254 if (data.size() < FIXED_PDU_SIZE) {
255 return make_error<associate_rj>(pdu_decode_error::incomplete_pdu);
256 }
257
258 associate_rj rj;
259 rj.result = static_cast<reject_result>(data[7]);
260 rj.source = data[8];
261 rj.reason = data[9];
262
263 return make_ok(std::move(rj));
264}
265
266// ============================================================================
267// A-RELEASE-RQ Decoder
268// ============================================================================
269
271 std::span<const uint8_t> data) {
272
273 auto header_result = validate_pdu_header(data, 0x05);
274 if (header_result.is_err()) {
275 return header_result.error();
276 }
277
278 return make_ok(release_rq_pdu{});
279}
280
281// ============================================================================
282// A-RELEASE-RP Decoder
283// ============================================================================
284
286 std::span<const uint8_t> data) {
287
288 auto header_result = validate_pdu_header(data, 0x06);
289 if (header_result.is_err()) {
290 return header_result.error();
291 }
292
293 return make_ok(release_rp_pdu{});
294}
295
296// ============================================================================
297// A-ABORT Decoder
298// ============================================================================
299
301 auto header_result = validate_pdu_header(data, 0x07);
302 if (header_result.is_err()) {
303 return header_result.error();
304 }
305
306 if (data.size() < FIXED_PDU_SIZE) {
307 return make_error<abort_pdu>(pdu_decode_error::incomplete_pdu);
308 }
309
311 abort.source = static_cast<abort_source>(data[8]);
312 abort.reason = static_cast<abort_reason>(data[9]);
313
314 return make_ok(std::move(abort));
315}
316
317// ============================================================================
318// P-DATA-TF Decoder
319// ============================================================================
320
322 std::span<const uint8_t> data) {
323
324 auto header_result = validate_pdu_header(data, 0x04);
325 if (header_result.is_err()) {
326 return header_result.error();
327 }
328
329 const uint32_t pdu_length = header_result.value();
330 const size_t pdu_end = PDU_HEADER_SIZE + pdu_length;
331
332 p_data_tf_pdu result;
333 size_t pos = PDU_HEADER_SIZE;
334
335 while (pos < pdu_end) {
336 // Each PDV item has:
337 // - 4-byte length
338 // - 1-byte presentation context ID
339 // - 1-byte message control header
340 // - variable data
341
342 if (pos + 4 > pdu_end) {
343 return make_error<p_data_tf_pdu>(pdu_decode_error::malformed_pdu,
344 "Incomplete PDV item length");
345 }
346
347 const uint32_t pdv_item_length = read_uint32_be(data, pos);
348 pos += 4;
349
350 if (pdv_item_length < 2) {
351 return make_error<p_data_tf_pdu>(pdu_decode_error::malformed_pdu,
352 "PDV item length too small");
353 }
354
355 // Subtract first to avoid pos + pdv_item_length wrapping on 32-bit
356 // platforms when pdv_item_length is attacker-controlled.
357 if (pdv_item_length > pdu_end - pos) {
358 return make_error<p_data_tf_pdu>(pdu_decode_error::buffer_overflow,
359 "PDV item exceeds PDU bounds");
360 }
361
363 pdv.context_id = data[pos];
364 pos += 1;
365
366 const uint8_t control = data[pos];
367 pos += 1;
368
369 pdv.is_command = (control & 0x01) != 0;
370 pdv.is_last = (control & 0x02) != 0;
371
372 // Data length = item length - context_id (1) - control (1)
373 const size_t data_length = pdv_item_length - 2;
374 pdv.data.assign(data.begin() + static_cast<ptrdiff_t>(pos),
375 data.begin() + static_cast<ptrdiff_t>(pos + data_length));
376 pos += data_length;
377
378 result.pdvs.push_back(std::move(pdv));
379 }
380
381 return make_ok(std::move(result));
382}
383
384// ============================================================================
385// Variable Items Decoder (for ASSOCIATE-RQ/AC)
386// ============================================================================
387
388DecodeResult<std::tuple<
389 std::string,
390 std::vector<presentation_context_rq>,
391 std::vector<presentation_context_ac>,
393>> pdu_decoder::decode_variable_items(std::span<const uint8_t> data, bool is_rq) {
394 std::string application_context;
395 std::vector<presentation_context_rq> pcs_rq;
396 std::vector<presentation_context_ac> pcs_ac;
397 user_information user_info;
398
399 size_t pos = 0;
400
401 while (pos < data.size()) {
402 if (pos + 4 > data.size()) {
403 return make_error<std::tuple<std::string,
404 std::vector<presentation_context_rq>,
405 std::vector<presentation_context_ac>,
407 "Incomplete item header");
408 }
409
410 const uint8_t item_type_byte = data[pos];
411 // Reserved byte at pos + 1
412 const uint16_t item_length = read_uint16_be(data, pos + 2);
413 pos += 4;
414
415 if (pos + item_length > data.size()) {
416 return make_error<std::tuple<std::string,
417 std::vector<presentation_context_rq>,
418 std::vector<presentation_context_ac>,
420 "Item length exceeds buffer");
421 }
422
423 switch (item_type_byte) {
424 case 0x10: // Application Context
425 application_context = read_uid(data, pos, item_length);
426 break;
427
428 case 0x20: // Presentation Context (RQ)
429 if (is_rq) {
430 // Presentation Context RQ item body is:
431 // context_id (1) + reserved (3) + sub-items
432 // Require at least the 4-byte fixed header before reading.
433 if (item_length < 4) {
434 return make_error<std::tuple<std::string,
435 std::vector<presentation_context_rq>,
436 std::vector<presentation_context_ac>,
438 "Presentation Context RQ item too small");
439 }
440
442 pc.id = data[pos];
443 // 3 reserved bytes
444
445 size_t sub_pos = pos + 4;
446 const size_t pc_end = pos + item_length;
447
448 while (sub_pos < pc_end) {
449 if (sub_pos + 4 > pc_end) break;
450
451 const uint8_t sub_type = data[sub_pos];
452 const uint16_t sub_length = read_uint16_be(data, sub_pos + 2);
453 sub_pos += 4;
454
455 if (sub_pos + sub_length > pc_end) break;
456
457 if (sub_type == 0x30) { // Abstract Syntax
458 pc.abstract_syntax = read_uid(data, sub_pos, sub_length);
459 } else if (sub_type == 0x40) { // Transfer Syntax
460 pc.transfer_syntaxes.push_back(
461 read_uid(data, sub_pos, sub_length));
462 }
463 sub_pos += sub_length;
464 }
465 pcs_rq.push_back(std::move(pc));
466 }
467 break;
468
469 case 0x21: // Presentation Context (AC)
470 if (!is_rq) {
471 // Presentation Context AC item body is:
472 // context_id (1) + reserved (1) + result (1) + reserved (1)
473 // + sub-items
474 // Require at least the 4-byte fixed header before reading.
475 if (item_length < 4) {
476 return make_error<std::tuple<std::string,
477 std::vector<presentation_context_rq>,
478 std::vector<presentation_context_ac>,
480 "Presentation Context AC item too small");
481 }
482
484 pc.id = data[pos];
485 // Reserved byte at pos + 1
486 pc.result = static_cast<presentation_context_result>(data[pos + 2]);
487 // Reserved byte at pos + 3
488
489 size_t sub_pos = pos + 4;
490 const size_t pc_end = pos + item_length;
491
492 while (sub_pos < pc_end) {
493 if (sub_pos + 4 > pc_end) break;
494
495 const uint8_t sub_type = data[sub_pos];
496 const uint16_t sub_length = read_uint16_be(data, sub_pos + 2);
497 sub_pos += 4;
498
499 if (sub_pos + sub_length > pc_end) break;
500
501 if (sub_type == 0x40) { // Transfer Syntax
502 pc.transfer_syntax = read_uid(data, sub_pos, sub_length);
503 }
504 sub_pos += sub_length;
505 }
506 pcs_ac.push_back(std::move(pc));
507 }
508 break;
509
510 case 0x50: { // User Information
511 auto ui_result = decode_user_info_item(
512 data.subspan(pos, item_length));
513 if (ui_result.is_ok()) {
514 user_info = std::move(ui_result.value());
515 }
516 break;
517 }
518
519 default:
520 // Skip unknown item types
521 break;
522 }
523
524 pos += item_length;
525 }
526
527 return make_ok(std::make_tuple(
528 std::move(application_context),
529 std::move(pcs_rq),
530 std::move(pcs_ac),
531 std::move(user_info)));
532}
533
534// ============================================================================
535// User Information Decoder
536// ============================================================================
537
539 std::span<const uint8_t> data) {
540
542 size_t pos = 0;
543
544 while (pos < data.size()) {
545 if (pos + 4 > data.size()) break;
546
547 const uint8_t sub_type = data[pos];
548 // Reserved byte at pos + 1
549 const uint16_t sub_length = read_uint16_be(data, pos + 2);
550 pos += 4;
551
552 if (pos + sub_length > data.size()) break;
553
554 switch (sub_type) {
555 case 0x51: // Maximum Length
556 if (sub_length >= 4) {
557 ui.max_pdu_length = read_uint32_be(data, pos);
558 }
559 break;
560
561 case 0x52: // Implementation Class UID
562 ui.implementation_class_uid = read_uid(data, pos, sub_length);
563 break;
564
565 case 0x55: // Implementation Version Name
566 ui.implementation_version_name = read_uid(data, pos, sub_length);
567 break;
568
569 case 0x54: { // SCP/SCU Role Selection
570 if (sub_length >= 4) {
571 const uint16_t uid_length = read_uint16_be(data, pos);
572 // UID is padded to even length in encoder. Compute in a
573 // wider type to avoid uint16_t overflow when uid_length is
574 // odd and near its maximum.
575 const size_t padded_uid_length =
576 static_cast<size_t>(uid_length) +
577 (static_cast<size_t>(uid_length) % 2);
578 // Bounds check: UID length field (2) + padded UID bytes +
579 // SCU role (1) + SCP role (1) must fit inside sub_length.
580 if (padded_uid_length + 4 <= sub_length) {
582 role.sop_class_uid = read_uid(data, pos + 2, uid_length);
583 role.scu_role = (data[pos + 2 + padded_uid_length] != 0);
584 role.scp_role = (data[pos + 2 + padded_uid_length + 1] != 0);
585 ui.role_selections.push_back(std::move(role));
586 }
587 }
588 break;
589 }
590
591 default:
592 // Skip unknown sub-items
593 break;
594 }
595
596 pos += sub_length;
597 }
598
599 return make_ok(std::move(ui));
600}
601
602// ============================================================================
603// A-ASSOCIATE-RQ Decoder
604// ============================================================================
605
607 std::span<const uint8_t> data) {
608
609 auto header_result = validate_pdu_header(data, 0x01);
610 if (header_result.is_err()) {
611 return header_result.error();
612 }
613
614 const uint32_t pdu_length = header_result.value();
615
616 // Check minimum size for ASSOCIATE header
617 if (pdu_length < ASSOCIATE_HEADER_SIZE) {
618 return make_error<associate_rq>(pdu_decode_error::malformed_pdu,
619 "PDU too short for ASSOCIATE-RQ");
620 }
621
622 // Check protocol version (bytes 6-7)
623 const uint16_t protocol_version = read_uint16_be(data, 6);
624 if (protocol_version != DICOM_PROTOCOL_VERSION) {
625 return make_error<associate_rq>(pdu_decode_error::invalid_protocol_version,
626 "Expected version 1, got " + std::to_string(protocol_version));
627 }
628
629 associate_rq rq;
630
631 // Called AE Title (bytes 10-25)
632 rq.called_ae_title = read_ae_title(data, 10);
633
634 // Calling AE Title (bytes 26-41)
635 rq.calling_ae_title = read_ae_title(data, 26);
636
637 // Reserved bytes 42-73 (32 bytes)
638 // Variable items start at byte 74
639
640 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
641 const size_t variable_length = pdu_length - ASSOCIATE_HEADER_SIZE;
642
643 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
644 auto var_result = decode_variable_items(
645 data.subspan(variable_start, variable_length), true);
646
647 if (var_result.is_err()) {
648 return var_result.error();
649 }
650
651 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
652 rq.application_context = std::move(app_ctx);
653 rq.presentation_contexts = std::move(pcs_rq);
654 rq.user_info = std::move(user_info);
655 }
656
657 return make_ok(std::move(rq));
658}
659
660// ============================================================================
661// A-ASSOCIATE-AC Decoder
662// ============================================================================
663
665 std::span<const uint8_t> data) {
666
667 auto header_result = validate_pdu_header(data, 0x02);
668 if (header_result.is_err()) {
669 return header_result.error();
670 }
671
672 const uint32_t pdu_length = header_result.value();
673
674 // Check minimum size
675 if (pdu_length < ASSOCIATE_HEADER_SIZE) {
676 return make_error<associate_ac>(pdu_decode_error::malformed_pdu,
677 "PDU too short for ASSOCIATE-AC");
678 }
679
680 // Check protocol version
681 const uint16_t protocol_version = read_uint16_be(data, 6);
682 if (protocol_version != DICOM_PROTOCOL_VERSION) {
683 return make_error<associate_ac>(pdu_decode_error::invalid_protocol_version,
684 "Expected version 1, got " + std::to_string(protocol_version));
685 }
686
687 associate_ac ac;
688
689 // Called AE Title
690 ac.called_ae_title = read_ae_title(data, 10);
691
692 // Calling AE Title
693 ac.calling_ae_title = read_ae_title(data, 26);
694
695 // Variable items
696 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
697 const size_t variable_length = pdu_length - ASSOCIATE_HEADER_SIZE;
698
699 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
700 auto var_result = decode_variable_items(
701 data.subspan(variable_start, variable_length), false);
702
703 if (var_result.is_err()) {
704 return var_result.error();
705 }
706
707 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
708 ac.application_context = std::move(app_ctx);
709 ac.presentation_contexts = std::move(pcs_ac);
710 ac.user_info = std::move(user_info);
711 }
712
713 return make_ok(std::move(ac));
714}
715
716} // namespace kcenon::pacs::network
static DecodeResult< std::tuple< std::string, std::vector< presentation_context_rq >, std::vector< presentation_context_ac >, user_information > > decode_variable_items(std::span< const uint8_t > data, bool is_rq)
Decode variable items from ASSOCIATE-RQ/AC PDUs.
static DecodeResult< user_information > decode_user_info_item(std::span< const uint8_t > data)
Decode User Information sub-items.
static DecodeResult< associate_ac > decode_associate_ac(std::span< const uint8_t > data)
Decode an A-ASSOCIATE-AC PDU.
static DecodeResult< associate_rq > decode_associate_rq(std::span< const uint8_t > data)
Decode an A-ASSOCIATE-RQ PDU.
static DecodeResult< release_rp_pdu > decode_release_rp(std::span< const uint8_t > data)
Decode an A-RELEASE-RP PDU.
static DecodeResult< pdu > decode(std::span< const uint8_t > data)
Decode any PDU from bytes.
static uint16_t read_uint16_be(std::span< const uint8_t > data, size_t offset)
Read a 16-bit unsigned integer in big-endian format.
static std::string read_ae_title(std::span< const uint8_t > data, size_t offset)
Read an AE Title (16 bytes, space-trimmed).
static DecodeResult< abort_pdu > decode_abort(std::span< const uint8_t > data)
Decode an A-ABORT PDU.
static uint32_t read_uint32_be(std::span< const uint8_t > data, size_t offset)
Read a 32-bit unsigned integer in big-endian format.
static std::string read_uid(std::span< const uint8_t > data, size_t offset, size_t length)
Read a UID string (trim trailing null/space padding).
static DecodeResult< uint32_t > validate_pdu_header(std::span< const uint8_t > data, uint8_t expected_type=0)
Validate PDU header and extract length.
static DecodeResult< release_rq_pdu > decode_release_rq(std::span< const uint8_t > data)
Decode an A-RELEASE-RQ PDU.
static DecodeResult< associate_rj > decode_associate_rj(std::span< const uint8_t > data)
Decode an A-ASSOCIATE-RJ PDU.
static std::optional< size_t > pdu_length(std::span< const uint8_t > data)
Check if a complete PDU is available in the buffer.
static std::optional< pdu_type > peek_pdu_type(std::span< const uint8_t > data)
Get the PDU type from buffer without full decoding.
static DecodeResult< p_data_tf_pdu > decode_p_data_tf(std::span< const uint8_t > data)
Decode a P-DATA-TF PDU.
constexpr int pdu_decoding_error
Definition result.h:109
constexpr int malformed_pdu
Definition result.h:112
constexpr int pdu_error
Definition result.h:91
constexpr int invalid_pdu_type
Definition result.h:111
constexpr int incomplete_pdu
Definition result.h:110
reject_result
Reject result values.
Definition pdu_types.h:92
@ 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)
@ application_context
Application Context Item.
constexpr uint16_t DICOM_PROTOCOL_VERSION
DICOM Protocol Version.
Definition pdu_types.h:270
abort_reason
Abort reason values when source is service-provider.
Definition pdu_types.h:79
kcenon::pacs::Result< T > DecodeResult
Result type alias for PDU decoding operations using standardized kcenon::pacs::Result<T>
constexpr const char * to_string(association_state state) noexcept
Convert association_state to string representation.
Definition association.h:86
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
pdu_decode_error
Error codes for PDU decoding errors.
Definition pdu_decoder.h:75
@ incomplete_header
Less than 6 bytes available.
@ buffer_overflow
Item length exceeds PDU bounds.
@ malformed_pdu
PDU structure is invalid.
@ incomplete_pdu
PDU length exceeds available data.
@ invalid_item_type
Unknown item type in variable items.
@ invalid_protocol_version
Unsupported protocol version.
@ invalid_pdu_type
Unknown PDU type byte.
presentation_context_result
Result values for A-ASSOCIATE-AC presentation context.
Definition pdu_types.h:59
kcenon::common::error_info error_info
Error information type.
Definition result.h:40
abort_source source
Source of abort.
Definition pdu_decoder.h:39
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
std::vector< presentation_data_value > pdvs
Presentation Data Values.
Definition pdu_decoder.h:52
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
bool is_command
true if Command message, false if Data
Definition pdu_types.h:137
uint8_t context_id
Presentation Context ID (odd number 1-255)
Definition pdu_types.h:136
std::vector< uint8_t > data
Fragment data.
Definition pdu_types.h:139
A-RELEASE-RP has no data fields.
Definition pdu_decoder.h:33
PDU variant type representing any DICOM Upper Layer PDU.A-RELEASE-RQ has no data fields.
Definition pdu_decoder.h:32
SCP/SCU Role Selection Sub-item.
Definition pdu_types.h:180
std::string sop_class_uid
SOP Class UID.
Definition pdu_types.h:181
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