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]);
83 std::string ae_title(
reinterpret_cast<const char*
>(data.data() + offset),
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) {
97 if (offset + length > data.size()) {
100 std::string
uid(
reinterpret_cast<const char*
>(data.data() + offset), length);
102 while (!
uid.empty() && (
uid.back() ==
'\0' ||
uid.back() ==
' ')) {
109 std::span<const uint8_t> data, uint8_t expected_type) {
111 if (data.size() < PDU_HEADER_SIZE) {
115 if (expected_type != 0 && data[0] != expected_type) {
117 "Expected type " + std::to_string(expected_type) +
118 ", got " + std::to_string(data[0]));
124 constexpr uint32_t MAX_PDU_LENGTH = 16 * 1024 * 1024;
128 " exceeds maximum allowed " + std::to_string(MAX_PDU_LENGTH));
131 const size_t total_length = PDU_HEADER_SIZE +
pdu_length;
133 if (data.size() < total_length) {
135 "Need " + std::to_string(total_length) +
136 " bytes, have " + std::to_string(data.size()));
147 if (data.size() < PDU_HEADER_SIZE) {
152 const size_t total = PDU_HEADER_SIZE + length;
154 if (data.size() < total) {
174 default:
return std::nullopt;
179 if (data.size() < PDU_HEADER_SIZE) {
183 const uint8_t type = data[0];
188 if (result.is_ok()) {
189 return make_ok<pdu>(std::move(result.value()));
191 return result.error();
195 if (result.is_ok()) {
196 return make_ok<pdu>(std::move(result.value()));
198 return result.error();
202 if (result.is_ok()) {
203 return make_ok<pdu>(std::move(result.value()));
205 return result.error();
209 if (result.is_ok()) {
210 return make_ok<pdu>(std::move(result.value()));
212 return result.error();
216 if (result.is_ok()) {
217 return make_ok<pdu>(std::move(result.value()));
219 return result.error();
223 if (result.is_ok()) {
224 return make_ok<pdu>(std::move(result.value()));
226 return result.error();
230 if (result.is_ok()) {
231 return make_ok<pdu>(std::move(result.value()));
233 return result.error();
237 "Unknown PDU type: " + std::to_string(type));
246 std::span<const uint8_t> data) {
249 if (header_result.is_err()) {
250 return header_result.error();
254 if (data.size() < FIXED_PDU_SIZE) {
263 return make_ok(std::move(rj));
271 std::span<const uint8_t> data) {
274 if (header_result.is_err()) {
275 return header_result.error();
286 std::span<const uint8_t> data) {
289 if (header_result.is_err()) {
290 return header_result.error();
302 if (header_result.is_err()) {
303 return header_result.error();
306 if (data.size() < FIXED_PDU_SIZE) {
314 return make_ok(std::move(
abort));
322 std::span<const uint8_t> data) {
325 if (header_result.is_err()) {
326 return header_result.error();
329 const uint32_t
pdu_length = header_result.value();
330 const size_t pdu_end = PDU_HEADER_SIZE +
pdu_length;
333 size_t pos = PDU_HEADER_SIZE;
335 while (pos < pdu_end) {
342 if (pos + 4 > pdu_end) {
344 "Incomplete PDV item length");
350 if (pdv_item_length < 2) {
352 "PDV item length too small");
357 if (pdv_item_length > pdu_end - pos) {
359 "PDV item exceeds PDU bounds");
366 const uint8_t control = data[pos];
370 pdv.
is_last = (control & 0x02) != 0;
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));
378 result.
pdvs.push_back(std::move(pdv));
381 return make_ok(std::move(result));
390 std::vector<presentation_context_rq>,
391 std::vector<presentation_context_ac>,
395 std::vector<presentation_context_rq> pcs_rq;
396 std::vector<presentation_context_ac> pcs_ac;
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");
410 const uint8_t item_type_byte = data[pos];
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");
423 switch (item_type_byte) {
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");
445 size_t sub_pos = pos + 4;
446 const size_t pc_end = pos + item_length;
448 while (sub_pos < pc_end) {
449 if (sub_pos + 4 > pc_end)
break;
451 const uint8_t sub_type = data[sub_pos];
455 if (sub_pos + sub_length > pc_end)
break;
457 if (sub_type == 0x30) {
459 }
else if (sub_type == 0x40) {
461 read_uid(data, sub_pos, sub_length));
463 sub_pos += sub_length;
465 pcs_rq.push_back(std::move(pc));
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");
489 size_t sub_pos = pos + 4;
490 const size_t pc_end = pos + item_length;
492 while (sub_pos < pc_end) {
493 if (sub_pos + 4 > pc_end)
break;
495 const uint8_t sub_type = data[sub_pos];
499 if (sub_pos + sub_length > pc_end)
break;
501 if (sub_type == 0x40) {
504 sub_pos += sub_length;
506 pcs_ac.push_back(std::move(pc));
512 data.subspan(pos, item_length));
513 if (ui_result.is_ok()) {
514 user_info = std::move(ui_result.value());
527 return make_ok(std::make_tuple(
531 std::move(user_info)));
539 std::span<const uint8_t> data) {
544 while (pos < data.size()) {
545 if (pos + 4 > data.size())
break;
547 const uint8_t sub_type = data[pos];
552 if (pos + sub_length > data.size())
break;
556 if (sub_length >= 4) {
570 if (sub_length >= 4) {
575 const size_t padded_uid_length =
576 static_cast<size_t>(uid_length) +
577 (
static_cast<size_t>(uid_length) % 2);
580 if (padded_uid_length + 4 <= sub_length) {
583 role.scu_role = (data[pos + 2 + padded_uid_length] != 0);
584 role.scp_role = (data[pos + 2 + padded_uid_length + 1] != 0);
599 return make_ok(std::move(ui));
607 std::span<const uint8_t> data) {
610 if (header_result.is_err()) {
611 return header_result.error();
614 const uint32_t
pdu_length = header_result.value();
619 "PDU too short for ASSOCIATE-RQ");
626 "Expected version 1, got " + std::to_string(protocol_version));
640 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
641 const size_t variable_length =
pdu_length - ASSOCIATE_HEADER_SIZE;
643 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
645 data.subspan(variable_start, variable_length),
true);
647 if (var_result.is_err()) {
648 return var_result.error();
651 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
657 return make_ok(std::move(rq));
665 std::span<const uint8_t> data) {
668 if (header_result.is_err()) {
669 return header_result.error();
672 const uint32_t
pdu_length = header_result.value();
677 "PDU too short for ASSOCIATE-AC");
684 "Expected version 1, got " + std::to_string(protocol_version));
696 const size_t variable_start = PDU_HEADER_SIZE + ASSOCIATE_HEADER_SIZE;
697 const size_t variable_length =
pdu_length - ASSOCIATE_HEADER_SIZE;
699 if (variable_length > 0 && variable_start + variable_length <= data.size()) {
701 data.subspan(variable_start, variable_length),
false);
703 if (var_result.is_err()) {
704 return var_result.error();
707 auto& [app_ctx, pcs_rq, pcs_ac, user_info] = var_result.value();
713 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.