PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
storage_commitment_scp.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
11
16
17namespace kcenon::pacs::services {
18
19// =============================================================================
20// Construction
21// =============================================================================
22
24 std::shared_ptr<storage::storage_interface> storage,
25 std::shared_ptr<di::ILogger> logger)
26 : scp_service(std::move(logger))
27 , storage_(std::move(storage)) {}
28
29// =============================================================================
30// scp_service Interface
31// =============================================================================
32
33std::vector<std::string> storage_commitment_scp::supported_sop_classes() const {
35}
36
39 uint8_t context_id,
40 const network::dimse::dimse_message& request) {
41
42 using namespace network::dimse;
43
44 switch (request.command()) {
45 case command_field::n_action_rq:
46 return handle_n_action(assoc, context_id, request);
47
48 default:
51 "Unexpected command for Storage Commitment SCP: " +
52 std::string(to_string(request.command())));
53 }
54}
55
56std::string_view storage_commitment_scp::service_name() const noexcept {
57 return "Storage Commitment SCP";
58}
59
60// =============================================================================
61// Statistics
62// =============================================================================
63
65 return actions_processed_.load();
66}
67
69 return instances_committed_.load();
70}
71
73 return instances_failed_.load();
74}
75
81
82// =============================================================================
83// N-ACTION Handler
84// =============================================================================
85
88 uint8_t context_id,
89 const network::dimse::dimse_message& request) {
90
91 using namespace network::dimse;
92
93 // Validate Requested SOP Class UID
94 auto sop_class_uid = request.command_set().get_string(
95 tag_requested_sop_class_uid);
96 if (sop_class_uid.empty()) {
97 sop_class_uid = request.affected_sop_class_uid();
98 }
99
100 if (sop_class_uid != storage_commitment_push_model_sop_class_uid) {
102 assoc, context_id, request.message_id(),
103 status_refused_sop_class_not_supported);
104 }
105
106 // Validate Action Type ID = 1 (Request Storage Commitment)
107 auto action_type = request.action_type_id();
108 if (!action_type.has_value() ||
109 action_type.value() != storage_commitment_action_type_request) {
111 assoc, context_id, request.message_id(),
112 status_error_no_such_action_type);
113 }
114
115 // Verify we have a dataset
116 if (!request.has_dataset()) {
118 assoc, context_id, request.message_id(),
119 status_error_missing_attribute);
120 }
121
122 const auto& dataset = request.dataset().value().get();
123
124 // Extract Transaction UID (0008,1195)
125 auto transaction_uid = dataset.get_string(core::tags::transaction_uid);
126 if (transaction_uid.empty()) {
128 assoc, context_id, request.message_id(),
129 status_error_missing_attribute);
130 }
131
132 // Parse Referenced SOP Sequence (0008,1199)
133 auto references = parse_referenced_sop_sequence(dataset);
134 if (references.empty()) {
136 assoc, context_id, request.message_id(),
137 status_error_missing_attribute);
138 }
139
140 // Send N-ACTION-RSP with Success (acknowledge the request)
141 auto rsp_result = send_n_action_response(
142 assoc, context_id, request.message_id(),
143 status_success);
144 if (rsp_result.is_err()) {
145 return rsp_result;
146 }
147
148 // Update statistics
150
151 // Verify instances against storage
152 auto result = verify_instances(transaction_uid, references);
153
154 // Update instance statistics
155 instances_committed_ += result.success_references.size();
156 instances_failed_ += result.failed_references.size();
157
158 // Send N-EVENT-REPORT with verification results
159 return send_event_report(assoc, context_id, result);
160}
161
162// =============================================================================
163// Instance Verification
164// =============================================================================
165
167 const std::string& transaction_uid,
168 const std::vector<sop_reference>& references) {
169
170 commitment_result result;
171 result.transaction_uid = transaction_uid;
172 result.timestamp = std::chrono::system_clock::now();
173
174 for (const auto& ref : references) {
175 if (storage_ && storage_->exists(ref.sop_instance_uid)) {
176 result.success_references.push_back(ref);
177 } else {
178 result.failed_references.emplace_back(
180 }
181 }
182
183 return result;
184}
185
186// =============================================================================
187// N-EVENT-REPORT Sender
188// =============================================================================
189
192 uint8_t context_id,
193 const commitment_result& result) {
194
195 using namespace network::dimse;
196
197 // Determine event type: 1 = all success, 2 = failures exist
198 uint16_t event_type = result.failed_references.empty()
201
202 // Build N-EVENT-REPORT-RQ
203 auto event_rq = make_n_event_report_rq(
204 1, // message_id
207 event_type);
208
209 // Build and attach event dataset
210 auto event_dataset = build_event_report_dataset(result);
211 event_rq.set_dataset(std::move(event_dataset));
212
213 // Send N-EVENT-REPORT-RQ
214 return assoc.send_dimse(context_id, event_rq);
215}
216
217// =============================================================================
218// Response Helpers
219// =============================================================================
220
223 uint8_t context_id,
224 uint16_t message_id,
226
227 using namespace network::dimse;
228
229 auto response = make_n_action_rsp(
230 message_id,
234 status);
235
236 return assoc.send_dimse(context_id, response);
237}
238
239// =============================================================================
240// Dataset Parsing Helpers
241// =============================================================================
242
244 const core::dicom_dataset& dataset) {
245
246 std::vector<sop_reference> references;
247
248 const auto* seq = dataset.get_sequence(core::tags::referenced_sop_sequence);
249 if (seq == nullptr) {
250 return references;
251 }
252
253 for (const auto& item : *seq) {
254 sop_reference ref;
257
258 if (!ref.sop_class_uid.empty() && !ref.sop_instance_uid.empty()) {
259 references.push_back(std::move(ref));
260 }
261 }
262
263 return references;
264}
265
267 const commitment_result& result) {
268
270
271 // Transaction UID (0008,1195)
273 result.transaction_uid);
274
275 // Referenced SOP Sequence (0008,1199) — successful instances
276 if (!result.success_references.empty()) {
277 auto& success_seq = ds.get_or_create_sequence(
279
280 for (const auto& ref : result.success_references) {
283 encoding::vr_type::UI, ref.sop_class_uid);
285 encoding::vr_type::UI, ref.sop_instance_uid);
286 success_seq.push_back(std::move(item));
287 }
288 }
289
290 // Failed SOP Sequence (0008,1198) — failed instances
291 if (!result.failed_references.empty()) {
292 auto& failed_seq = ds.get_or_create_sequence(
294
295 for (const auto& [ref, reason] : result.failed_references) {
298 encoding::vr_type::UI, ref.sop_class_uid);
300 encoding::vr_type::UI, ref.sop_instance_uid);
301 item.set_numeric<uint16_t>(core::tags::failure_reason,
303 static_cast<uint16_t>(reason));
304 failed_seq.push_back(std::move(item));
305 }
306 }
307
308 return ds;
309}
310
311} // namespace kcenon::pacs::services
auto get_sequence(dicom_tag tag) const noexcept -> const std::vector< dicom_dataset > *
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
auto get_or_create_sequence(dicom_tag tag) -> std::vector< dicom_dataset > &
Insert or create a sequence element with the given tag.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
auto message_id() const noexcept -> uint16_t
Get the message ID.
auto affected_sop_class_uid() const -> std::string
Get the Affected SOP Class UID.
auto command_set() noexcept -> core::dicom_dataset &
Get mutable reference to the command set.
auto has_dataset() const noexcept -> bool
Check if the message has an associated data set.
auto dataset() -> kcenon::pacs::Result< std::reference_wrapper< core::dicom_dataset > >
Get mutable reference to the data set.
auto action_type_id() const -> std::optional< uint16_t >
Get the Action Type ID (for N-ACTION)
auto command() const noexcept -> command_field
Get the command field.
network::Result< std::monostate > handle_message(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request) override
Handle an incoming DIMSE message.
storage_commitment_scp(std::shared_ptr< storage::storage_interface > storage, std::shared_ptr< di::ILogger > logger=nullptr)
Construct Storage Commitment SCP with storage backend.
commitment_result verify_instances(const std::string &transaction_uid, const std::vector< sop_reference > &references)
network::Result< std::monostate > handle_n_action(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request)
static std::vector< sop_reference > parse_referenced_sop_sequence(const core::dicom_dataset &dataset)
std::shared_ptr< storage::storage_interface > storage_
network::Result< std::monostate > send_n_action_response(network::association &assoc, uint8_t context_id, uint16_t message_id, network::dimse::status_code status)
static core::dicom_dataset build_event_report_dataset(const commitment_result &result)
std::string_view service_name() const noexcept override
Get the service name for logging/debugging.
std::vector< std::string > supported_sop_classes() const override
Get the list of SOP Class UIDs supported by this service.
network::Result< std::monostate > send_event_report(network::association &assoc, uint8_t context_id, const commitment_result &result)
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
constexpr dicom_tag failed_sop_sequence
Failed SOP Sequence — instances that failed commitment (PS3.4 J.3)
constexpr dicom_tag referenced_sop_sequence
Referenced SOP Sequence — instances in commitment request/success (PS3.4 J.3)
constexpr dicom_tag referenced_sop_class_uid
Referenced SOP Class UID (in Sequence)
constexpr dicom_tag failure_reason
Failure Reason — reason code for commitment failure (PS3.4 Table J.3-2)
constexpr dicom_tag referenced_sop_instance_uid
Referenced SOP Instance UID (in Sequence)
constexpr dicom_tag transaction_uid
Transaction UID — identifies a Storage Commitment transaction (PS3.4 J.3)
@ UI
Unique Identifier (64 chars max)
@ US
Unsigned Short (2 bytes)
constexpr int storage_commitment_unexpected_command
Definition result.h:180
uint16_t status_code
DIMSE status code type alias.
constexpr std::string_view storage_commitment_push_model_sop_instance_uid
Storage Commitment Push Model SOP Instance UID (Well-Known)
constexpr uint16_t storage_commitment_event_type_success
N-EVENT-REPORT: Storage Commitment Request Successful (Event Type ID = 1)
constexpr std::string_view storage_commitment_push_model_sop_class_uid
Storage Commitment Push Model SOP Class UID (PS3.4 Table J.3-1)
@ no_such_object_instance
Referenced SOP Instance not found in storage.
constexpr uint16_t storage_commitment_event_type_failure
N-EVENT-REPORT: Storage Commitment Request Complete - Failures Exist (Event Type ID = 2)
constexpr uint16_t storage_commitment_action_type_request
N-ACTION: Request Storage Commitment (Action Type ID = 1)
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249
Result<T> type aliases and helpers for PACS system.
DIMSE status codes.
DICOM Storage Commitment Push Model SCP service.
Result of a Storage Commitment verification.
std::chrono::system_clock::time_point timestamp
Timestamp when verification was completed.
std::vector< std::pair< sop_reference, commitment_failure_reason > > failed_references
Failed SOP Instance references with failure reasons.
std::vector< sop_reference > success_references
Successfully committed SOP Instance references.
std::string transaction_uid
Transaction UID identifying this commitment request.
Reference to a SOP Instance in a commitment request.
std::string sop_class_uid
Referenced SOP Class UID (0008,1150)
std::string sop_instance_uid
Referenced SOP Instance UID (0008,1155)