16constexpr size_t PDU_HEADER_SIZE = 6;
19constexpr size_t FIXED_PDU_SIZE = 10;
23constexpr size_t ASSOCIATE_HEADER_SIZE = 68;
49 full_msg +=
": " + msg;
57 return kcenon::pacs::ok(std::move(value));
67 return static_cast<uint16_t
>(
68 (
static_cast<uint16_t
>(data[offset]) << 8) |
69 static_cast<uint16_t
>(data[offset + 1]));
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]);
80 std::string ae_title(
reinterpret_cast<const char*
>(data.data() + offset),
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) {
94 std::string
uid(
reinterpret_cast<const char*
>(data.data() + offset), length);
96 while (!
uid.empty() && (
uid.back() ==
'\0' ||
uid.back() ==
' ')) {
103 std::span<const uint8_t> data, uint8_t expected_type) {
105 if (data.size() < PDU_HEADER_SIZE) {
109 if (expected_type != 0 && data[0] != expected_type) {
111 "Expected type " + std::to_string(expected_type) +
112 ", got " + std::to_string(data[0]));
118 constexpr uint32_t MAX_PDU_LENGTH = 16 * 1024 * 1024;
122 " exceeds maximum allowed " + std::to_string(MAX_PDU_LENGTH));
125 const size_t total_length = PDU_HEADER_SIZE +
pdu_length;
127 if (data.size() < total_length) {
129 "Need " + std::to_string(total_length) +
130 " bytes, have " + std::to_string(data.size()));
141 if (data.size() < PDU_HEADER_SIZE) {
146 const size_t total = PDU_HEADER_SIZE + length;
148 if (data.size() < total) {
168 default:
return std::nullopt;
173 if (data.size() < PDU_HEADER_SIZE) {
177 const uint8_t type = data[0];
182 if (result.is_ok()) {
183 return make_ok<pdu>(std::move(result.value()));
185 return result.error();
189 if (result.is_ok()) {
190 return make_ok<pdu>(std::move(result.value()));
192 return result.error();
196 if (result.is_ok()) {
197 return make_ok<pdu>(std::move(result.value()));
199 return result.error();
203 if (result.is_ok()) {
204 return make_ok<pdu>(std::move(result.value()));
206 return result.error();
210 if (result.is_ok()) {
211 return make_ok<pdu>(std::move(result.value()));
213 return result.error();
217 if (result.is_ok()) {
218 return make_ok<pdu>(std::move(result.value()));
220 return result.error();
224 if (result.is_ok()) {
225 return make_ok<pdu>(std::move(result.value()));
227 return result.error();
231 "Unknown PDU type: " + std::to_string(type));
240 std::span<const uint8_t> data) {
243 if (header_result.is_err()) {
244 return header_result.error();
248 if (data.size() < FIXED_PDU_SIZE) {
257 return make_ok(std::move(rj));
265 std::span<const uint8_t> data) {
268 if (header_result.is_err()) {
269 return header_result.error();
280 std::span<const uint8_t> data) {
283 if (header_result.is_err()) {
284 return header_result.error();
296 if (header_result.is_err()) {
297 return header_result.error();
300 if (data.size() < FIXED_PDU_SIZE) {
308 return make_ok(std::move(
abort));
316 std::span<const uint8_t> data) {
319 if (header_result.is_err()) {
320 return header_result.error();
323 const uint32_t
pdu_length = header_result.value();
324 const size_t pdu_end = PDU_HEADER_SIZE +
pdu_length;
327 size_t pos = PDU_HEADER_SIZE;
329 while (pos < pdu_end) {
336 if (pos + 4 > pdu_end) {
338 "Incomplete PDV item length");
344 if (pdv_item_length < 2) {
346 "PDV item length too small");
349 if (pos + pdv_item_length > pdu_end) {
351 "PDV item exceeds PDU bounds");
358 const uint8_t control = data[pos];
362 pdv.
is_last = (control & 0x02) != 0;
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));
370 result.
pdvs.push_back(std::move(pdv));
373 return make_ok(std::move(result));
382 std::vector<presentation_context_rq>,
383 std::vector<presentation_context_ac>,
387 std::vector<presentation_context_rq> pcs_rq;
388 std::vector<presentation_context_ac> pcs_ac;
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");
402 const uint8_t item_type_byte = data[pos];
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");
415 switch (item_type_byte) {
427 size_t sub_pos = pos + 4;
428 const size_t pc_end = pos + item_length;
430 while (sub_pos < pc_end) {
431 if (sub_pos + 4 > pc_end)
break;
433 const uint8_t sub_type = data[sub_pos];
437 if (sub_pos + sub_length > pc_end)
break;
439 if (sub_type == 0x30) {
441 }
else if (sub_type == 0x40) {
443 read_uid(data, sub_pos, sub_length));
445 sub_pos += sub_length;
447 pcs_rq.push_back(std::move(pc));
459 size_t sub_pos = pos + 4;
460 const size_t pc_end = pos + item_length;
462 while (sub_pos < pc_end) {
463 if (sub_pos + 4 > pc_end)
break;
465 const uint8_t sub_type = data[sub_pos];
469 if (sub_pos + sub_length > pc_end)
break;
471 if (sub_type == 0x40) {
474 sub_pos += sub_length;
476 pcs_ac.push_back(std::move(pc));
482 data.subspan(pos, item_length));
483 if (ui_result.is_ok()) {
484 user_info = std::move(ui_result.value());
497 return make_ok(std::make_tuple(
501 std::move(user_info)));
509 std::span<const uint8_t> data) {
514 while (pos < data.size()) {
515 if (pos + 4 > data.size())
break;
517 const uint8_t sub_type = data[pos];
522 if (pos + sub_length > data.size())
break;
526 if (sub_length >= 4) {
540 if (sub_length >= 4) {
543 const uint16_t padded_uid_length = uid_length + (uid_length % 2);
544 if (pos + 2 + padded_uid_length + 2 <= pos + sub_length) {
547 role.scu_role = (data[pos + 2 + padded_uid_length] != 0);
548 role.scp_role = (data[pos + 2 + padded_uid_length + 1] != 0);
563 return make_ok(std::move(ui));
571 std::span<const uint8_t> data) {
574 if (header_result.is_err()) {
575 return header_result.error();
578 const uint32_t
pdu_length = header_result.value();
583 "PDU too short for ASSOCIATE-RQ");
590 "Expected version 1, got " + std::to_string(protocol_version));
604 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
605 const size_t variable_length =
pdu_length - ASSOCIATE_HEADER_SIZE;
607 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
609 data.subspan(variable_start, variable_length),
true);
611 if (var_result.is_ok()) {
612 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
619 return make_ok(std::move(rq));
627 std::span<const uint8_t> data) {
630 if (header_result.is_err()) {
631 return header_result.error();
634 const uint32_t
pdu_length = header_result.value();
639 "PDU too short for ASSOCIATE-AC");
646 "Expected version 1, got " + std::to_string(protocol_version));
658 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
659 const size_t variable_length =
pdu_length - ASSOCIATE_HEADER_SIZE;
661 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
663 data.subspan(variable_start, variable_length),
false);
665 if (var_result.is_ok()) {
666 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
673 return make_ok(std::move(ac));
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
constexpr int malformed_pdu
constexpr int invalid_pdu_type
constexpr int incomplete_pdu
reject_result
Reject result values.
@ 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.
abort_reason
Abort reason values when source is service-provider.
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.
constexpr size_t AE_TITLE_LENGTH
AE Title length (fixed 16 characters, space-padded)
abort_source
Abort source values.
pdu_decode_error
Error codes for PDU decoding errors.
@ 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.
kcenon::common::error_info error_info
Error information type.
abort_source source
Source of abort.
std::string calling_ae_title
Calling AE Title (16 chars max)
user_information user_info
User Information.
std::string called_ae_title
Called AE Title (16 chars max)
std::string application_context
Application Context Name UID.
std::vector< presentation_context_ac > presentation_contexts
Presentation Contexts.
uint8_t reason
Reason/Diagnostic.
reject_result result
Result (1=permanent, 2=transient)
std::string called_ae_title
Called AE Title (16 chars max)
std::string application_context
Application Context Name UID.
user_information user_info
User Information.
std::string calling_ae_title
Calling AE Title (16 chars max)
std::vector< presentation_context_rq > presentation_contexts
Presentation Contexts.
std::vector< presentation_data_value > pdvs
Presentation Data Values.
Presentation Context for A-ASSOCIATE-AC.
presentation_context_result result
Result/Reason.
uint8_t id
Presentation Context ID.
std::string transfer_syntax
Accepted Transfer Syntax UID.
Presentation Context for A-ASSOCIATE-RQ.
uint8_t id
Presentation Context ID (odd number 1-255)
std::string abstract_syntax
Abstract Syntax UID (SOP Class)
std::vector< std::string > transfer_syntaxes
Proposed Transfer Syntaxes.
Presentation Data Value (PDV) item for P-DATA-TF.
bool is_last
true if last fragment
bool is_command
true if Command message, false if Data
uint8_t context_id
Presentation Context ID (odd number 1-255)
std::vector< uint8_t > data
Fragment data.
A-RELEASE-RP has no data fields.
PDU variant type representing any DICOM Upper Layer PDU.A-RELEASE-RQ has no data fields.
SCP/SCU Role Selection Sub-item.
std::string sop_class_uid
SOP Class UID.