PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dicom_session.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
12#include <kcenon/network/interfaces/i_session.h>
13#include <kcenon/network/session/session.h>
14
15#include <algorithm>
16#include <cstring>
17#include <stdexcept>
18#include <type_traits>
19
21
22// =============================================================================
23// Construction / Destruction
24// =============================================================================
25
27 std::shared_ptr<network_system::session::messaging_session> session)
28 : session_(std::move(session)) {
29 if (auto* s = std::get_if<
30 std::shared_ptr<network_system::session::messaging_session>>(&session_)) {
31 // Set up callbacks for the underlying session
32 (*s)->set_receive_callback(
33 [this](const std::vector<uint8_t>& data) {
34 on_data_received(data);
35 });
36
37 (*s)->set_error_callback(
38 [this](std::error_code ec) {
39 on_error(ec);
40 });
41 }
42}
43
45 std::shared_ptr<network_system::session::secure_session> session)
46 : session_(std::move(session)) {
47 if (auto* s = std::get_if<
48 std::shared_ptr<network_system::session::secure_session>>(&session_)) {
49 // Set up callbacks for the underlying secure session
50 (*s)->set_receive_callback(
51 [this](const std::vector<uint8_t>& data) {
52 on_data_received(data);
53 });
54
55 (*s)->set_error_callback(
56 [this](std::error_code ec) {
57 on_error(ec);
58 });
59 }
60}
61
63 std::shared_ptr<kcenon::network::interfaces::i_session> session)
64 : session_(std::move(session)) {
65 // i_session does not support session-level callbacks.
66 // Data must be fed externally via feed_data() / feed_error().
67}
68
72
74 : session_(std::move(other.session_))
75 , receive_buffer_(std::move(other.receive_buffer_))
76 , received_pdus_(std::move(other.received_pdus_))
77 , pdu_callback_(std::move(other.pdu_callback_))
78 , error_callback_(std::move(other.error_callback_))
79 , last_error_(other.last_error_)
80 , closed_(other.closed_) {
81 other.closed_ = true;
82}
83
85 if (this != &other) {
86 close();
87
88 std::lock_guard<std::mutex> lock(mutex_);
89 session_ = std::move(other.session_);
90 receive_buffer_ = std::move(other.receive_buffer_);
91 received_pdus_ = std::move(other.received_pdus_);
92 pdu_callback_ = std::move(other.pdu_callback_);
93 error_callback_ = std::move(other.error_callback_);
94 last_error_ = other.last_error_;
95 closed_ = other.closed_;
96
97 other.closed_ = true;
98 }
99 return *this;
100}
101
102// =============================================================================
103// PDU Operations
104// =============================================================================
105
108 const std::vector<uint8_t>& payload) {
109 std::lock_guard<std::mutex> lock(mutex_);
110
111 if (closed_) {
112 return Result<std::monostate>(error_info("Session is closed"));
113 }
114
115 if (payload.size() > max_pdu_payload_size) {
116 return Result<std::monostate>(error_info("PDU payload exceeds maximum size"));
117 }
118
119 // Encode PDU header
120 auto header = encode_pdu_header(type, static_cast<uint32_t>(payload.size()));
121
122 // Combine header and payload
123 std::vector<uint8_t> pdu_data;
124 pdu_data.reserve(header.size() + payload.size());
125 pdu_data.insert(pdu_data.end(), header.begin(), header.end());
126 pdu_data.insert(pdu_data.end(), payload.begin(), payload.end());
127
128 // Send data
129 send_data(std::move(pdu_data));
130
131 return Result<std::monostate>(std::monostate{});
132}
133
135dicom_session::send_raw(const std::vector<uint8_t>& data) {
136 std::lock_guard<std::mutex> lock(mutex_);
137
138 if (closed_) {
139 return Result<std::monostate>(error_info("Session is closed"));
140 }
141
142 if (data.size() < pdu_header_size) {
143 return Result<std::monostate>(error_info("Raw PDU data too small"));
144 }
145
146 // Send data as-is
147 std::vector<uint8_t> copy = data;
148 send_data(std::move(copy));
149
150 return Result<std::monostate>(std::monostate{});
151}
152
155 std::unique_lock<std::mutex> lock(mutex_);
156
157 if (closed_) {
158 return Result<pdu_data>(error_info("Session is closed"));
159 }
160
161 // Wait for a complete PDU with timeout
162 auto deadline = clock::now() + timeout;
163
164 while (received_pdus_.empty()) {
165 if (closed_) {
166 return Result<pdu_data>(error_info("Session closed while waiting"));
167 }
168
169 auto status = receive_cv_.wait_until(lock, deadline);
170 if (status == std::cv_status::timeout) {
171 return Result<pdu_data>(error_info("Receive timeout"));
172 }
173
174 if (last_error_) {
175 return Result<pdu_data>(error_info("Receive error: " + last_error_.message()));
176 }
177 }
178
179 // Get the first PDU from the queue
180 pdu_data pdu = std::move(received_pdus_.front());
181 received_pdus_.erase(received_pdus_.begin());
182
183 return Result<pdu_data>(std::move(pdu));
184}
185
187 std::function<void(const pdu_data&)> callback) {
188 std::lock_guard<std::mutex> lock(mutex_);
189 pdu_callback_ = std::move(callback);
190}
191
193 std::lock_guard<std::mutex> lock(mutex_);
194 pdu_callback_ = nullptr;
195}
196
197// =============================================================================
198// Connection State
199// =============================================================================
200
202 std::lock_guard<std::mutex> lock(mutex_);
203
204 if (closed_) {
205 return;
206 }
207
208 closed_ = true;
209
210 // Stop the underlying session
211 std::visit([](auto&& session) {
212 if (session) {
213 if constexpr (std::is_same_v<std::decay_t<decltype(session)>,
214 std::shared_ptr<kcenon::network::interfaces::i_session>>) {
215 session->close();
216 } else {
217 session->stop_session();
218 }
219 }
220 }, session_);
221
222 // Notify waiting receivers
223 receive_cv_.notify_all();
224}
225
226bool dicom_session::is_open() const noexcept {
227 std::lock_guard<std::mutex> lock(mutex_);
228
229 if (closed_) {
230 return false;
231 }
232
233 return std::visit([](auto&& session) -> bool {
234 if (session) {
235 if constexpr (std::is_same_v<std::decay_t<decltype(session)>,
236 std::shared_ptr<kcenon::network::interfaces::i_session>>) {
237 return session->is_connected();
238 } else {
239 return !session->is_stopped();
240 }
241 }
242 return false;
243 }, session_);
244}
245
246std::string dicom_session::remote_address() const {
247 std::lock_guard<std::mutex> lock(mutex_);
248
249 // For now, return a placeholder
250 // Real implementation would query the underlying socket
251 return "unknown";
252}
253
254std::string dicom_session::session_id() const {
255 std::lock_guard<std::mutex> lock(mutex_);
256
257 return std::visit([](auto&& session) -> std::string {
258 if (session) {
259 if constexpr (std::is_same_v<std::decay_t<decltype(session)>,
260 std::shared_ptr<kcenon::network::interfaces::i_session>>) {
261 return std::string(session->id());
262 } else {
263 return session->server_id();
264 }
265 }
266 return "invalid";
267 }, session_);
268}
269
270bool dicom_session::is_secure() const noexcept {
271 return std::holds_alternative<
272 std::shared_ptr<network_system::session::secure_session>>(session_);
273}
274
275// =============================================================================
276// External Event Injection
277// =============================================================================
278
279void dicom_session::feed_data(const std::vector<uint8_t>& data) {
280 on_data_received(data);
281}
282
283void dicom_session::feed_error(std::error_code ec) {
284 on_error(ec);
285}
286
287// =============================================================================
288// Error Handling
289// =============================================================================
290
292 std::function<void(std::error_code)> callback) {
293 std::lock_guard<std::mutex> lock(mutex_);
294 error_callback_ = std::move(callback);
295}
296
297std::error_code dicom_session::last_error() const noexcept {
298 std::lock_guard<std::mutex> lock(mutex_);
299 return last_error_;
300}
301
302// =============================================================================
303// Private Methods
304// =============================================================================
305
306void dicom_session::on_data_received(const std::vector<uint8_t>& data) {
307 std::lock_guard<std::mutex> lock(mutex_);
308
309 if (closed_) {
310 return;
311 }
312
313 // Append to receive buffer
314 receive_buffer_.insert(receive_buffer_.end(), data.begin(), data.end());
315
316 // Process complete PDUs
318}
319
320void dicom_session::on_error(std::error_code ec) {
321 std::function<void(std::error_code)> callback;
322
323 {
324 std::lock_guard<std::mutex> lock(mutex_);
325 last_error_ = ec;
326 callback = error_callback_;
327 }
328
329 if (callback) {
330 callback(ec);
331 }
332
333 // Notify waiting receivers
334 receive_cv_.notify_all();
335}
336
338 // Process all complete PDUs in the buffer
339 while (receive_buffer_.size() >= pdu_header_size) {
340 // Parse header
342 uint32_t length;
343
344 if (!parse_pdu_header(receive_buffer_, type, length)) {
345 // Invalid header - this is a protocol error
346 last_error_ = std::make_error_code(std::errc::protocol_error);
347 receive_cv_.notify_all();
348 return;
349 }
350
351 // Sanity check on length
352 if (length > max_pdu_payload_size) {
353 last_error_ = std::make_error_code(std::errc::message_size);
354 receive_cv_.notify_all();
355 return;
356 }
357
358 // Check if we have the complete PDU
359 size_t total_size = pdu_header_size + length;
360 if (receive_buffer_.size() < total_size) {
361 // Need more data
362 return;
363 }
364
365 // Extract payload (skip 6-byte header)
366 std::vector<uint8_t> payload(
368 receive_buffer_.begin() + total_size);
369
370 // Remove processed data from buffer
371 receive_buffer_.erase(
372 receive_buffer_.begin(),
373 receive_buffer_.begin() + total_size);
374
375 // Create PDU data
376 pdu_data pdu(type, std::move(payload));
377
378 // Deliver through callback or queue
379 if (pdu_callback_) {
380 pdu_callback_(pdu);
381 } else {
382 received_pdus_.push_back(std::move(pdu));
383 receive_cv_.notify_one();
384 }
385 }
386}
387
388std::vector<uint8_t>
390 std::vector<uint8_t> header(pdu_header_size);
391
392 // Byte 0: PDU Type
393 header[0] = static_cast<uint8_t>(type);
394
395 // Byte 1: Reserved
396 header[1] = 0x00;
397
398 // Bytes 2-5: Length (Big Endian)
399 header[2] = static_cast<uint8_t>((length >> 24) & 0xFF);
400 header[3] = static_cast<uint8_t>((length >> 16) & 0xFF);
401 header[4] = static_cast<uint8_t>((length >> 8) & 0xFF);
402 header[5] = static_cast<uint8_t>(length & 0xFF);
403
404 return header;
405}
406
407bool dicom_session::parse_pdu_header(const std::vector<uint8_t>& buffer,
408 network::pdu_type& type,
409 uint32_t& length) {
410 if (buffer.size() < pdu_header_size) {
411 return false;
412 }
413
414 // Byte 0: PDU Type
415 uint8_t type_byte = buffer[0];
416
417 // Validate PDU type
418 if (type_byte < 0x01 || type_byte > 0x07) {
419 return false;
420 }
421
422 type = static_cast<network::pdu_type>(type_byte);
423
424 // Byte 1: Reserved (should be 0, but we don't validate)
425
426 // Bytes 2-5: Length (Big Endian)
427 length = (static_cast<uint32_t>(buffer[2]) << 24) |
428 (static_cast<uint32_t>(buffer[3]) << 16) |
429 (static_cast<uint32_t>(buffer[4]) << 8) |
430 static_cast<uint32_t>(buffer[5]);
431
432 return true;
433}
434
435void dicom_session::send_data(std::vector<uint8_t>&& data) {
436 std::visit([&data](auto&& session) {
437 if (session) {
438 if constexpr (std::is_same_v<std::decay_t<decltype(session)>,
439 std::shared_ptr<kcenon::network::interfaces::i_session>>) {
440 (void)session->send(std::move(data));
441 } else {
442 session->send_packet(std::move(data));
443 }
444 }
445 }, session_);
446}
447
448} // namespace kcenon::pacs::integration
Simple result type for error handling when common_system is unavailable.
DICOM session wrapper for network_system sessions.
Result< std::monostate > send_raw(const std::vector< uint8_t > &data)
Send raw PDU data (with header already included)
void process_buffer()
Process buffered data for complete PDUs.
void set_pdu_callback(std::function< void(const pdu_data &)> callback)
Set callback for asynchronous PDU reception.
std::condition_variable receive_cv_
Condition variable for synchronous receive.
static constexpr size_t pdu_header_size
PDU header size per DICOM PS3.8.
bool is_open() const noexcept
Check if the session is open.
void feed_error(std::error_code ec)
Feed an error from external source.
Result< std::monostate > send_pdu(network::pdu_type type, const std::vector< uint8_t > &payload)
Send a complete DICOM PDU.
std::error_code last_error() const noexcept
Get the last error code.
bool is_secure() const noexcept
Check if this is a secure (TLS) session.
std::function< void(const pdu_data &)> pdu_callback_
PDU callback for async reception.
void clear_pdu_callback()
Clear the PDU callback.
std::vector< uint8_t > receive_buffer_
Receive buffer for PDU framing.
void feed_data(const std::vector< uint8_t > &data)
Feed received data from external source.
static bool parse_pdu_header(const std::vector< uint8_t > &buffer, network::pdu_type &type, uint32_t &length)
Parse PDU header from buffer.
void on_data_received(const std::vector< uint8_t > &data)
Handle incoming data from network.
session_variant session_
Underlying network session.
std::error_code last_error_
Last error code.
dicom_session(std::shared_ptr< network_system::session::messaging_session > session)
Construct a DICOM session wrapping a messaging_session.
dicom_session & operator=(const dicom_session &)=delete
std::function< void(std::error_code)> error_callback_
Error callback.
void set_error_callback(std::function< void(std::error_code)> callback)
Set callback for error events.
Result< pdu_data > receive_pdu(duration timeout=default_timeout)
Receive a complete DICOM PDU.
std::mutex mutex_
Mutex for thread safety.
static std::vector< uint8_t > encode_pdu_header(network::pdu_type type, uint32_t length)
Encode PDU header.
static constexpr size_t max_pdu_payload_size
Maximum PDU payload size (sanity check)
std::string remote_address() const
Get the remote peer address.
std::vector< pdu_data > received_pdus_
Queue of received complete PDUs.
std::string session_id() const
Get session identifier.
void on_error(std::error_code ec)
Handle connection errors.
void send_data(std::vector< uint8_t > &&data)
Send data through the underlying session.
~dicom_session()
Destructor (closes session if open)
DICOM session wrapper for network_system sessions.
pdu_type
PDU (Protocol Data Unit) types as defined in DICOM PS3.8.
Definition pdu_types.h:25
kcenon::common::Result< T > Result
Result type alias for PACS operations.
Definition result.h:30
kcenon::common::error_info error_info
Error information type.
Definition result.h:40
Container for received PDU data.