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

Decoder for DICOM PDU (Protocol Data Unit) messages. More...

#include <pdu_decoder.h>

Collaboration diagram for kcenon::pacs::network::pdu_decoder:
Collaboration graph

Static Public Member Functions

General Decoding
static DecodeResult< pdudecode (std::span< const uint8_t > data)
 Decode any PDU from bytes.
 
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_typepeek_pdu_type (std::span< const uint8_t > data)
 Get the PDU type from buffer without full decoding.
 
Specific Decoders
static DecodeResult< associate_rqdecode_associate_rq (std::span< const uint8_t > data)
 Decode an A-ASSOCIATE-RQ PDU.
 
static DecodeResult< associate_acdecode_associate_ac (std::span< const uint8_t > data)
 Decode an A-ASSOCIATE-AC PDU.
 
static DecodeResult< associate_rjdecode_associate_rj (std::span< const uint8_t > data)
 Decode an A-ASSOCIATE-RJ PDU.
 
static DecodeResult< p_data_tf_pdudecode_p_data_tf (std::span< const uint8_t > data)
 Decode a P-DATA-TF PDU.
 
static DecodeResult< release_rq_pdudecode_release_rq (std::span< const uint8_t > data)
 Decode an A-RELEASE-RQ PDU.
 
static DecodeResult< release_rp_pdudecode_release_rp (std::span< const uint8_t > data)
 Decode an A-RELEASE-RP PDU.
 
static DecodeResult< abort_pdudecode_abort (std::span< const uint8_t > data)
 Decode an A-ABORT PDU.
 

Static Private Member Functions

Helper Functions
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 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_ae_title (std::span< const uint8_t > data, size_t offset)
 Read an AE Title (16 bytes, space-trimmed).
 
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< 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_informationdecode_user_info_item (std::span< const uint8_t > data)
 Decode User Information sub-items.
 

Detailed Description

Decoder for DICOM PDU (Protocol Data Unit) messages.

This class provides static methods to decode various PDU types according to DICOM PS3.8 Upper Layer Protocol.

PDU Structure:

┌─────────────────────────────────────┐
│ PDU Header │
├───────────┬───────────┬─────────────┤
│ Type │ Reserved │ Length │
│ (1 byte) │ (1 byte) │ (4 bytes) │
└───────────┴───────────┴─────────────┘
│ PDU Data (variable) │
└─────────────────────────────────────┘
See also
DICOM PS3.8 Section 9 - Upper Layer Protocol

Definition at line 127 of file pdu_decoder.h.

Member Function Documentation

◆ decode()

DecodeResult< pdu > kcenon::pacs::network::pdu_decoder::decode ( std::span< const uint8_t > data)
staticnodiscard

Decode any PDU from bytes.

Parameters
dataInput byte buffer
Returns
Result containing decoded PDU or error

Automatically detects PDU type from the first byte and dispatches to the appropriate specific decoder.

Definition at line 172 of file pdu_decoder.cpp.

172 {
173 if (data.size() < PDU_HEADER_SIZE) {
174 return make_error<pdu>(pdu_decode_error::incomplete_header);
175 }
176
177 const uint8_t type = data[0];
178
179 switch (type) {
180 case 0x01: {
181 auto result = decode_associate_rq(data);
182 if (result.is_ok()) {
183 return make_ok<pdu>(std::move(result.value()));
184 }
185 return result.error();
186 }
187 case 0x02: {
188 auto result = decode_associate_ac(data);
189 if (result.is_ok()) {
190 return make_ok<pdu>(std::move(result.value()));
191 }
192 return result.error();
193 }
194 case 0x03: {
195 auto result = decode_associate_rj(data);
196 if (result.is_ok()) {
197 return make_ok<pdu>(std::move(result.value()));
198 }
199 return result.error();
200 }
201 case 0x04: {
202 auto result = decode_p_data_tf(data);
203 if (result.is_ok()) {
204 return make_ok<pdu>(std::move(result.value()));
205 }
206 return result.error();
207 }
208 case 0x05: {
209 auto result = decode_release_rq(data);
210 if (result.is_ok()) {
211 return make_ok<pdu>(std::move(result.value()));
212 }
213 return result.error();
214 }
215 case 0x06: {
216 auto result = decode_release_rp(data);
217 if (result.is_ok()) {
218 return make_ok<pdu>(std::move(result.value()));
219 }
220 return result.error();
221 }
222 case 0x07: {
223 auto result = decode_abort(data);
224 if (result.is_ok()) {
225 return make_ok<pdu>(std::move(result.value()));
226 }
227 return result.error();
228 }
229 default:
230 return make_error<pdu>(pdu_decode_error::invalid_pdu_type,
231 "Unknown PDU type: " + std::to_string(type));
232 }
233}
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< abort_pdu > decode_abort(std::span< const uint8_t > data)
Decode an A-ABORT PDU.
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 DecodeResult< p_data_tf_pdu > decode_p_data_tf(std::span< const uint8_t > data)
Decode a P-DATA-TF PDU.
@ incomplete_header
Less than 6 bytes available.
@ invalid_pdu_type
Unknown PDU type byte.

References decode_abort(), decode_associate_ac(), decode_associate_rj(), decode_associate_rq(), decode_p_data_tf(), decode_release_rp(), decode_release_rq(), kcenon::pacs::network::incomplete_header, and kcenon::pacs::network::invalid_pdu_type.

Here is the call graph for this function:

◆ decode_abort()

DecodeResult< abort_pdu > kcenon::pacs::network::pdu_decoder::decode_abort ( std::span< const uint8_t > data)
staticnodiscard

Decode an A-ABORT PDU.

Parameters
dataInput byte buffer
Returns
Result containing abort_pdu or error

Definition at line 294 of file pdu_decoder.cpp.

294 {
295 auto header_result = validate_pdu_header(data, 0x07);
296 if (header_result.is_err()) {
297 return header_result.error();
298 }
299
300 if (data.size() < FIXED_PDU_SIZE) {
301 return make_error<abort_pdu>(pdu_decode_error::incomplete_pdu);
302 }
303
304 abort_pdu abort;
305 abort.source = static_cast<abort_source>(data[8]);
306 abort.reason = static_cast<abort_reason>(data[9]);
307
308 return make_ok(std::move(abort));
309}
static DecodeResult< uint32_t > validate_pdu_header(std::span< const uint8_t > data, uint8_t expected_type=0)
Validate PDU header and extract length.
@ incomplete_pdu
PDU length exceeds available data.

References kcenon::pacs::network::abort, kcenon::pacs::network::incomplete_pdu, kcenon::pacs::network::abort_pdu::source, and validate_pdu_header().

Referenced by decode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_associate_ac()

DecodeResult< associate_ac > kcenon::pacs::network::pdu_decoder::decode_associate_ac ( std::span< const uint8_t > data)
staticnodiscard

Decode an A-ASSOCIATE-AC PDU.

Parameters
dataInput byte buffer
Returns
Result containing associate_ac or error

Definition at line 626 of file pdu_decoder.cpp.

627 {
628
629 auto header_result = validate_pdu_header(data, 0x02);
630 if (header_result.is_err()) {
631 return header_result.error();
632 }
633
634 const uint32_t pdu_length = header_result.value();
635
636 // Check minimum size
637 if (pdu_length < ASSOCIATE_HEADER_SIZE) {
638 return make_error<associate_ac>(pdu_decode_error::malformed_pdu,
639 "PDU too short for ASSOCIATE-AC");
640 }
641
642 // Check protocol version
643 const uint16_t protocol_version = read_uint16_be(data, 6);
644 if (protocol_version != DICOM_PROTOCOL_VERSION) {
645 return make_error<associate_ac>(pdu_decode_error::invalid_protocol_version,
646 "Expected version 1, got " + std::to_string(protocol_version));
647 }
648
649 associate_ac ac;
650
651 // Called AE Title
652 ac.called_ae_title = read_ae_title(data, 10);
653
654 // Calling AE Title
655 ac.calling_ae_title = read_ae_title(data, 26);
656
657 // Variable items
658 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
659 const size_t variable_length = pdu_length - ASSOCIATE_HEADER_SIZE;
660
661 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
662 auto var_result = decode_variable_items(
663 data.subspan(variable_start, variable_length), false);
664
665 if (var_result.is_ok()) {
666 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
667 ac.application_context = std::move(app_ctx);
668 ac.presentation_contexts = std::move(pcs_ac);
669 ac.user_info = std::move(user_info);
670 }
671 }
672
673 return make_ok(std::move(ac));
674}
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 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 std::optional< size_t > pdu_length(std::span< const uint8_t > data)
Check if a complete PDU is available in the buffer.
@ associate_ac
A-ASSOCIATE-AC (Association Accept)
constexpr uint16_t DICOM_PROTOCOL_VERSION
DICOM Protocol Version.
Definition pdu_types.h:270
@ malformed_pdu
PDU structure is invalid.
@ invalid_protocol_version
Unsupported protocol version.

References kcenon::pacs::network::associate_ac::application_context, kcenon::pacs::network::associate_ac::called_ae_title, kcenon::pacs::network::associate_ac::calling_ae_title, decode_variable_items(), kcenon::pacs::network::DICOM_PROTOCOL_VERSION, kcenon::pacs::network::invalid_protocol_version, kcenon::pacs::network::malformed_pdu, pdu_length(), kcenon::pacs::network::associate_ac::presentation_contexts, read_ae_title(), read_uint16_be(), kcenon::pacs::network::associate_ac::user_info, and validate_pdu_header().

Referenced by decode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_associate_rj()

DecodeResult< associate_rj > kcenon::pacs::network::pdu_decoder::decode_associate_rj ( std::span< const uint8_t > data)
staticnodiscard

Decode an A-ASSOCIATE-RJ PDU.

Parameters
dataInput byte buffer
Returns
Result containing associate_rj or error

Definition at line 239 of file pdu_decoder.cpp.

240 {
241
242 auto header_result = validate_pdu_header(data, 0x03);
243 if (header_result.is_err()) {
244 return header_result.error();
245 }
246
247 // A-ASSOCIATE-RJ is always 10 bytes
248 if (data.size() < FIXED_PDU_SIZE) {
249 return make_error<associate_rj>(pdu_decode_error::incomplete_pdu);
250 }
251
252 associate_rj rj;
253 rj.result = static_cast<reject_result>(data[7]);
254 rj.source = data[8];
255 rj.reason = data[9];
256
257 return make_ok(std::move(rj));
258}
reject_result
Reject result values.
Definition pdu_types.h:92
@ associate_rj
A-ASSOCIATE-RJ (Association Reject)

References kcenon::pacs::network::incomplete_pdu, kcenon::pacs::network::associate_rj::reason, kcenon::pacs::network::associate_rj::result, kcenon::pacs::network::associate_rj::source, and validate_pdu_header().

Referenced by decode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_associate_rq()

DecodeResult< associate_rq > kcenon::pacs::network::pdu_decoder::decode_associate_rq ( std::span< const uint8_t > data)
staticnodiscard

Decode an A-ASSOCIATE-RQ PDU.

Parameters
dataInput byte buffer
Returns
Result containing associate_rq or error

Definition at line 570 of file pdu_decoder.cpp.

571 {
572
573 auto header_result = validate_pdu_header(data, 0x01);
574 if (header_result.is_err()) {
575 return header_result.error();
576 }
577
578 const uint32_t pdu_length = header_result.value();
579
580 // Check minimum size for ASSOCIATE header
581 if (pdu_length < ASSOCIATE_HEADER_SIZE) {
582 return make_error<associate_rq>(pdu_decode_error::malformed_pdu,
583 "PDU too short for ASSOCIATE-RQ");
584 }
585
586 // Check protocol version (bytes 6-7)
587 const uint16_t protocol_version = read_uint16_be(data, 6);
588 if (protocol_version != DICOM_PROTOCOL_VERSION) {
589 return make_error<associate_rq>(pdu_decode_error::invalid_protocol_version,
590 "Expected version 1, got " + std::to_string(protocol_version));
591 }
592
593 associate_rq rq;
594
595 // Called AE Title (bytes 10-25)
596 rq.called_ae_title = read_ae_title(data, 10);
597
598 // Calling AE Title (bytes 26-41)
599 rq.calling_ae_title = read_ae_title(data, 26);
600
601 // Reserved bytes 42-73 (32 bytes)
602 // Variable items start at byte 74
603
604 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
605 const size_t variable_length = pdu_length - ASSOCIATE_HEADER_SIZE;
606
607 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
608 auto var_result = decode_variable_items(
609 data.subspan(variable_start, variable_length), true);
610
611 if (var_result.is_ok()) {
612 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
613 rq.application_context = std::move(app_ctx);
614 rq.presentation_contexts = std::move(pcs_rq);
615 rq.user_info = std::move(user_info);
616 }
617 }
618
619 return make_ok(std::move(rq));
620}
@ associate_rq
A-ASSOCIATE-RQ (Association Request)

References kcenon::pacs::network::associate_rq::application_context, kcenon::pacs::network::associate_rq::called_ae_title, kcenon::pacs::network::associate_rq::calling_ae_title, decode_variable_items(), kcenon::pacs::network::DICOM_PROTOCOL_VERSION, kcenon::pacs::network::invalid_protocol_version, kcenon::pacs::network::malformed_pdu, pdu_length(), kcenon::pacs::network::associate_rq::presentation_contexts, read_ae_title(), read_uint16_be(), kcenon::pacs::network::associate_rq::user_info, and validate_pdu_header().

Referenced by decode(), and kcenon::pacs::network::v2::dicom_association_handler::handle_associate_rq().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_p_data_tf()

DecodeResult< p_data_tf_pdu > kcenon::pacs::network::pdu_decoder::decode_p_data_tf ( std::span< const uint8_t > data)
staticnodiscard

Decode a P-DATA-TF PDU.

Parameters
dataInput byte buffer
Returns
Result containing p_data_tf_pdu or error

Definition at line 315 of file pdu_decoder.cpp.

316 {
317
318 auto header_result = validate_pdu_header(data, 0x04);
319 if (header_result.is_err()) {
320 return header_result.error();
321 }
322
323 const uint32_t pdu_length = header_result.value();
324 const size_t pdu_end = PDU_HEADER_SIZE + pdu_length;
325
326 p_data_tf_pdu result;
327 size_t pos = PDU_HEADER_SIZE;
328
329 while (pos < pdu_end) {
330 // Each PDV item has:
331 // - 4-byte length
332 // - 1-byte presentation context ID
333 // - 1-byte message control header
334 // - variable data
335
336 if (pos + 4 > pdu_end) {
337 return make_error<p_data_tf_pdu>(pdu_decode_error::malformed_pdu,
338 "Incomplete PDV item length");
339 }
340
341 const uint32_t pdv_item_length = read_uint32_be(data, pos);
342 pos += 4;
343
344 if (pdv_item_length < 2) {
345 return make_error<p_data_tf_pdu>(pdu_decode_error::malformed_pdu,
346 "PDV item length too small");
347 }
348
349 if (pos + pdv_item_length > pdu_end) {
350 return make_error<p_data_tf_pdu>(pdu_decode_error::buffer_overflow,
351 "PDV item exceeds PDU bounds");
352 }
353
354 presentation_data_value pdv;
355 pdv.context_id = data[pos];
356 pos += 1;
357
358 const uint8_t control = data[pos];
359 pos += 1;
360
361 pdv.is_command = (control & 0x01) != 0;
362 pdv.is_last = (control & 0x02) != 0;
363
364 // Data length = item length - context_id (1) - control (1)
365 const size_t data_length = pdv_item_length - 2;
366 pdv.data.assign(data.begin() + static_cast<ptrdiff_t>(pos),
367 data.begin() + static_cast<ptrdiff_t>(pos + data_length));
368 pos += data_length;
369
370 result.pdvs.push_back(std::move(pdv));
371 }
372
373 return make_ok(std::move(result));
374}
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.
@ control
Internal pipeline control messages.
@ buffer_overflow
Item length exceeds PDU bounds.

References kcenon::pacs::network::buffer_overflow, kcenon::pacs::network::presentation_data_value::context_id, kcenon::pacs::network::presentation_data_value::data, kcenon::pacs::network::presentation_data_value::is_command, kcenon::pacs::network::presentation_data_value::is_last, kcenon::pacs::network::malformed_pdu, pdu_length(), kcenon::pacs::network::p_data_tf_pdu::pdvs, read_uint32_be(), and validate_pdu_header().

Referenced by decode(), and kcenon::pacs::network::v2::dicom_association_handler::handle_p_data_tf().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_release_rp()

DecodeResult< release_rp_pdu > kcenon::pacs::network::pdu_decoder::decode_release_rp ( std::span< const uint8_t > data)
staticnodiscard

Decode an A-RELEASE-RP PDU.

Parameters
dataInput byte buffer
Returns
Result containing release_rp_pdu or error

Definition at line 279 of file pdu_decoder.cpp.

280 {
281
282 auto header_result = validate_pdu_header(data, 0x06);
283 if (header_result.is_err()) {
284 return header_result.error();
285 }
286
287 return make_ok(release_rp_pdu{});
288}

References validate_pdu_header().

Referenced by decode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_release_rq()

DecodeResult< release_rq_pdu > kcenon::pacs::network::pdu_decoder::decode_release_rq ( std::span< const uint8_t > data)
staticnodiscard

Decode an A-RELEASE-RQ PDU.

Parameters
dataInput byte buffer
Returns
Result containing release_rq_pdu or error

Definition at line 264 of file pdu_decoder.cpp.

265 {
266
267 auto header_result = validate_pdu_header(data, 0x05);
268 if (header_result.is_err()) {
269 return header_result.error();
270 }
271
272 return make_ok(release_rq_pdu{});
273}

References validate_pdu_header().

Referenced by decode().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_user_info_item()

DecodeResult< user_information > kcenon::pacs::network::pdu_decoder::decode_user_info_item ( std::span< const uint8_t > data)
staticnodiscardprivate

Decode User Information sub-items.

Definition at line 508 of file pdu_decoder.cpp.

509 {
510
512 size_t pos = 0;
513
514 while (pos < data.size()) {
515 if (pos + 4 > data.size()) break;
516
517 const uint8_t sub_type = data[pos];
518 // Reserved byte at pos + 1
519 const uint16_t sub_length = read_uint16_be(data, pos + 2);
520 pos += 4;
521
522 if (pos + sub_length > data.size()) break;
523
524 switch (sub_type) {
525 case 0x51: // Maximum Length
526 if (sub_length >= 4) {
527 ui.max_pdu_length = read_uint32_be(data, pos);
528 }
529 break;
530
531 case 0x52: // Implementation Class UID
532 ui.implementation_class_uid = read_uid(data, pos, sub_length);
533 break;
534
535 case 0x55: // Implementation Version Name
536 ui.implementation_version_name = read_uid(data, pos, sub_length);
537 break;
538
539 case 0x54: { // SCP/SCU Role Selection
540 if (sub_length >= 4) {
541 const uint16_t uid_length = read_uint16_be(data, pos);
542 // UID is padded to even length in encoder
543 const uint16_t padded_uid_length = uid_length + (uid_length % 2);
544 if (pos + 2 + padded_uid_length + 2 <= pos + sub_length) {
546 role.sop_class_uid = read_uid(data, pos + 2, uid_length);
547 role.scu_role = (data[pos + 2 + padded_uid_length] != 0);
548 role.scp_role = (data[pos + 2 + padded_uid_length + 1] != 0);
549 ui.role_selections.push_back(std::move(role));
550 }
551 }
552 break;
553 }
554
555 default:
556 // Skip unknown sub-items
557 break;
558 }
559
560 pos += sub_length;
561 }
562
563 return make_ok(std::move(ui));
564}
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).
@ scp_scu_role_selection
SCP/SCU Role Selection Sub-item.
@ user_information
User Information Item.

References kcenon::pacs::network::user_information::implementation_class_uid, kcenon::pacs::network::user_information::implementation_version_name, kcenon::pacs::network::user_information::max_pdu_length, read_uid(), read_uint16_be(), read_uint32_be(), kcenon::pacs::network::user_information::role_selections, and kcenon::pacs::network::scp_scu_role_selection::sop_class_uid.

Referenced by decode_variable_items().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ decode_variable_items()

DecodeResult< std::tuple< std::string, std::vector< presentation_context_rq >, std::vector< presentation_context_ac >, user_information > > kcenon::pacs::network::pdu_decoder::decode_variable_items ( std::span< const uint8_t > data,
bool is_rq )
staticnodiscardprivate

Decode variable items from ASSOCIATE-RQ/AC PDUs.

Definition at line 385 of file pdu_decoder.cpp.

385 {
386 std::string application_context;
387 std::vector<presentation_context_rq> pcs_rq;
388 std::vector<presentation_context_ac> pcs_ac;
389 user_information user_info;
390
391 size_t pos = 0;
392
393 while (pos < data.size()) {
394 if (pos + 4 > data.size()) {
395 return make_error<std::tuple<std::string,
396 std::vector<presentation_context_rq>,
397 std::vector<presentation_context_ac>,
399 "Incomplete item header");
400 }
401
402 const uint8_t item_type_byte = data[pos];
403 // Reserved byte at pos + 1
404 const uint16_t item_length = read_uint16_be(data, pos + 2);
405 pos += 4;
406
407 if (pos + item_length > data.size()) {
408 return make_error<std::tuple<std::string,
409 std::vector<presentation_context_rq>,
410 std::vector<presentation_context_ac>,
412 "Item length exceeds buffer");
413 }
414
415 switch (item_type_byte) {
416 case 0x10: // Application Context
417 application_context = read_uid(data, pos, item_length);
418 break;
419
420 case 0x20: // Presentation Context (RQ)
421 if (is_rq) {
422 // Parse presentation context RQ
424 pc.id = data[pos];
425 // 3 reserved bytes
426
427 size_t sub_pos = pos + 4;
428 const size_t pc_end = pos + item_length;
429
430 while (sub_pos < pc_end) {
431 if (sub_pos + 4 > pc_end) break;
432
433 const uint8_t sub_type = data[sub_pos];
434 const uint16_t sub_length = read_uint16_be(data, sub_pos + 2);
435 sub_pos += 4;
436
437 if (sub_pos + sub_length > pc_end) break;
438
439 if (sub_type == 0x30) { // Abstract Syntax
440 pc.abstract_syntax = read_uid(data, sub_pos, sub_length);
441 } else if (sub_type == 0x40) { // Transfer Syntax
442 pc.transfer_syntaxes.push_back(
443 read_uid(data, sub_pos, sub_length));
444 }
445 sub_pos += sub_length;
446 }
447 pcs_rq.push_back(std::move(pc));
448 }
449 break;
450
451 case 0x21: // Presentation Context (AC)
452 if (!is_rq) {
454 pc.id = data[pos];
455 // Reserved byte at pos + 1
456 pc.result = static_cast<presentation_context_result>(data[pos + 2]);
457 // Reserved byte at pos + 3
458
459 size_t sub_pos = pos + 4;
460 const size_t pc_end = pos + item_length;
461
462 while (sub_pos < pc_end) {
463 if (sub_pos + 4 > pc_end) break;
464
465 const uint8_t sub_type = data[sub_pos];
466 const uint16_t sub_length = read_uint16_be(data, sub_pos + 2);
467 sub_pos += 4;
468
469 if (sub_pos + sub_length > pc_end) break;
470
471 if (sub_type == 0x40) { // Transfer Syntax
472 pc.transfer_syntax = read_uid(data, sub_pos, sub_length);
473 }
474 sub_pos += sub_length;
475 }
476 pcs_ac.push_back(std::move(pc));
477 }
478 break;
479
480 case 0x50: { // User Information
481 auto ui_result = decode_user_info_item(
482 data.subspan(pos, item_length));
483 if (ui_result.is_ok()) {
484 user_info = std::move(ui_result.value());
485 }
486 break;
487 }
488
489 default:
490 // Skip unknown item types
491 break;
492 }
493
494 pos += item_length;
495 }
496
497 return make_ok(std::make_tuple(
498 std::move(application_context),
499 std::move(pcs_rq),
500 std::move(pcs_ac),
501 std::move(user_info)));
502}
static DecodeResult< user_information > decode_user_info_item(std::span< const uint8_t > data)
Decode User Information sub-items.
@ presentation_context_rq
Presentation Context Item (RQ)
@ presentation_context_ac
Presentation Context Item (AC)
@ application_context
Application Context Item.
presentation_context_result
Result values for A-ASSOCIATE-AC presentation context.
Definition pdu_types.h:59

References kcenon::pacs::network::presentation_context_rq::abstract_syntax, kcenon::pacs::network::application_context, kcenon::pacs::network::buffer_overflow, decode_user_info_item(), kcenon::pacs::network::presentation_context_ac::id, kcenon::pacs::network::presentation_context_rq::id, kcenon::pacs::network::malformed_pdu, read_uid(), read_uint16_be(), kcenon::pacs::network::presentation_context_ac::result, kcenon::pacs::network::presentation_context_ac::transfer_syntax, and kcenon::pacs::network::presentation_context_rq::transfer_syntaxes.

Referenced by decode_associate_ac(), and decode_associate_rq().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pdu_length()

std::optional< size_t > kcenon::pacs::network::pdu_decoder::pdu_length ( std::span< const uint8_t > data)
staticnodiscard

Check if a complete PDU is available in the buffer.

Parameters
dataInput byte buffer
Returns
PDU length if complete PDU available, std::nullopt otherwise

Returns the total PDU length (header + data) if at least one complete PDU is present in the buffer. Useful for streaming protocols where data arrives in chunks.

Definition at line 140 of file pdu_decoder.cpp.

140 {
141 if (data.size() < PDU_HEADER_SIZE) {
142 return std::nullopt;
143 }
144
145 const uint32_t length = read_uint32_be(data, 2);
146 const size_t total = PDU_HEADER_SIZE + length;
147
148 if (data.size() < total) {
149 return std::nullopt;
150 }
151
152 return total;
153}
@ length
Linear distance measurement.

References read_uint32_be().

Referenced by decode_associate_ac(), decode_associate_rq(), decode_p_data_tf(), kcenon::pacs::network::v2::dicom_association_handler::process_buffer(), and validate_pdu_header().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ peek_pdu_type()

std::optional< pdu_type > kcenon::pacs::network::pdu_decoder::peek_pdu_type ( std::span< const uint8_t > data)
staticnodiscard

Get the PDU type from buffer without full decoding.

Parameters
dataInput byte buffer (must have at least 1 byte)
Returns
PDU type if valid, std::nullopt if invalid or insufficient data

Definition at line 155 of file pdu_decoder.cpp.

155 {
156 if (data.empty()) {
157 return std::nullopt;
158 }
159
160 switch (data[0]) {
161 case 0x01: return pdu_type::associate_rq;
162 case 0x02: return pdu_type::associate_ac;
163 case 0x03: return pdu_type::associate_rj;
164 case 0x04: return pdu_type::p_data_tf;
165 case 0x05: return pdu_type::release_rq;
166 case 0x06: return pdu_type::release_rp;
167 case 0x07: return pdu_type::abort;
168 default: return std::nullopt;
169 }
170}
@ p_data_tf
P-DATA-TF (Data Transfer)
@ release_rq
A-RELEASE-RQ (Release Request)
@ release_rp
A-RELEASE-RP (Release Response)

References kcenon::pacs::network::abort, kcenon::pacs::network::associate_ac, kcenon::pacs::network::associate_rj, kcenon::pacs::network::associate_rq, kcenon::pacs::network::p_data_tf, kcenon::pacs::network::release_rp, and kcenon::pacs::network::release_rq.

Referenced by kcenon::pacs::network::v2::dicom_association_handler::process_buffer().

Here is the caller graph for this function:

◆ read_ae_title()

std::string kcenon::pacs::network::pdu_decoder::read_ae_title ( std::span< const uint8_t > data,
size_t offset )
staticnodiscardprivate

Read an AE Title (16 bytes, space-trimmed).

Definition at line 79 of file pdu_decoder.cpp.

79 {
80 std::string ae_title(reinterpret_cast<const char*>(data.data() + offset),
82 // Trim trailing spaces
83 auto end = ae_title.find_last_not_of(' ');
84 if (end != std::string::npos) {
85 ae_title.resize(end + 1);
86 } else if (ae_title.find_first_not_of(' ') == std::string::npos) {
87 ae_title.clear(); // All spaces
88 }
89 return ae_title;
90}
constexpr size_t AE_TITLE_LENGTH
AE Title length (fixed 16 characters, space-padded)
Definition pdu_types.h:273

References kcenon::pacs::network::AE_TITLE_LENGTH.

Referenced by decode_associate_ac(), and decode_associate_rq().

Here is the caller graph for this function:

◆ read_uid()

std::string kcenon::pacs::network::pdu_decoder::read_uid ( std::span< const uint8_t > data,
size_t offset,
size_t length )
staticnodiscardprivate

Read a UID string (trim trailing null/space padding).

Definition at line 92 of file pdu_decoder.cpp.

93 {
94 std::string uid(reinterpret_cast<const char*>(data.data() + offset), length);
95 // Trim trailing nulls and spaces
96 while (!uid.empty() && (uid.back() == '\0' || uid.back() == ' ')) {
97 uid.pop_back();
98 }
99 return uid;
100}
std::string_view uid

References uid.

Referenced by decode_user_info_item(), and decode_variable_items().

Here is the caller graph for this function:

◆ read_uint16_be()

uint16_t kcenon::pacs::network::pdu_decoder::read_uint16_be ( std::span< const uint8_t > data,
size_t offset )
staticnodiscardprivate

Read a 16-bit unsigned integer in big-endian format.

Definition at line 66 of file pdu_decoder.cpp.

66 {
67 return static_cast<uint16_t>(
68 (static_cast<uint16_t>(data[offset]) << 8) |
69 static_cast<uint16_t>(data[offset + 1]));
70}

Referenced by decode_associate_ac(), decode_associate_rq(), decode_user_info_item(), and decode_variable_items().

Here is the caller graph for this function:

◆ read_uint32_be()

uint32_t kcenon::pacs::network::pdu_decoder::read_uint32_be ( std::span< const uint8_t > data,
size_t offset )
staticnodiscardprivate

Read a 32-bit unsigned integer in big-endian format.

Definition at line 72 of file pdu_decoder.cpp.

72 {
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}

Referenced by decode_p_data_tf(), decode_user_info_item(), pdu_length(), and validate_pdu_header().

Here is the caller graph for this function:

◆ validate_pdu_header()

DecodeResult< uint32_t > kcenon::pacs::network::pdu_decoder::validate_pdu_header ( std::span< const uint8_t > data,
uint8_t expected_type = 0 )
staticnodiscardprivate

Validate PDU header and extract length.

Parameters
dataInput data
expected_typeExpected PDU type (0 for any)
Returns
Pair of (is_valid, pdu_length) or error

Definition at line 102 of file pdu_decoder.cpp.

103 {
104
105 if (data.size() < PDU_HEADER_SIZE) {
106 return make_error<uint32_t>(pdu_decode_error::incomplete_header);
107 }
108
109 if (expected_type != 0 && data[0] != expected_type) {
110 return make_error<uint32_t>(pdu_decode_error::invalid_pdu_type,
111 "Expected type " + std::to_string(expected_type) +
112 ", got " + std::to_string(data[0]));
113 }
114
115 const uint32_t pdu_length = read_uint32_be(data, 2);
116
117 // Reject unreasonably large PDUs to prevent memory exhaustion attacks
118 constexpr uint32_t MAX_PDU_LENGTH = 16 * 1024 * 1024; // 16 MB
119 if (pdu_length > MAX_PDU_LENGTH) {
120 return make_error<uint32_t>(pdu_decode_error::malformed_pdu,
121 "PDU length " + std::to_string(pdu_length) +
122 " exceeds maximum allowed " + std::to_string(MAX_PDU_LENGTH));
123 }
124
125 const size_t total_length = PDU_HEADER_SIZE + pdu_length;
126
127 if (data.size() < total_length) {
128 return make_error<uint32_t>(pdu_decode_error::incomplete_pdu,
129 "Need " + std::to_string(total_length) +
130 " bytes, have " + std::to_string(data.size()));
131 }
132
133 return make_ok(pdu_length);
134}

References kcenon::pacs::network::incomplete_header, kcenon::pacs::network::incomplete_pdu, kcenon::pacs::network::invalid_pdu_type, kcenon::pacs::network::malformed_pdu, pdu_length(), and read_uint32_be().

Referenced by decode_abort(), decode_associate_ac(), decode_associate_rj(), decode_associate_rq(), decode_p_data_tf(), decode_release_rp(), and decode_release_rq().

Here is the call graph for this function:
Here is the caller graph for this function:

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