PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
uid_mapping.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
13
14#include <chrono>
15#include <random>
16#include <sstream>
17
18namespace kcenon::pacs::security {
19
20uid_mapping::uid_mapping(std::string uid_root)
21 : uid_root_{std::move(uid_root)} {}
22
24 std::shared_lock lock(other.mutex_);
25 uid_root_ = other.uid_root_;
26 original_to_anon_ = other.original_to_anon_;
27 anon_to_original_ = other.anon_to_original_;
28 uid_counter_.store(other.uid_counter_.load());
29}
30
32 std::unique_lock lock(other.mutex_);
33 uid_root_ = std::move(other.uid_root_);
34 original_to_anon_ = std::move(other.original_to_anon_);
35 anon_to_original_ = std::move(other.anon_to_original_);
36 uid_counter_.store(other.uid_counter_.load());
37}
38
40 if (this != &other) {
41 std::scoped_lock lock(mutex_, other.mutex_);
42 uid_root_ = other.uid_root_;
43 original_to_anon_ = other.original_to_anon_;
44 anon_to_original_ = other.anon_to_original_;
45 uid_counter_.store(other.uid_counter_.load());
46 }
47 return *this;
48}
49
51 if (this != &other) {
52 std::scoped_lock lock(mutex_, other.mutex_);
53 uid_root_ = std::move(other.uid_root_);
54 original_to_anon_ = std::move(other.original_to_anon_);
55 anon_to_original_ = std::move(other.anon_to_original_);
56 uid_counter_.store(other.uid_counter_.load());
57 }
58 return *this;
59}
60
61auto uid_mapping::get_or_create(std::string_view original_uid)
63 // First try read-only lookup
64 {
65 std::shared_lock lock(mutex_);
66 auto it = original_to_anon_.find(original_uid);
67 if (it != original_to_anon_.end()) {
68 return it->second;
69 }
70 }
71
72 // Need to create new mapping - upgrade to write lock
73 std::unique_lock lock(mutex_);
74
75 // Double-check after acquiring write lock
76 auto it = original_to_anon_.find(original_uid);
77 if (it != original_to_anon_.end()) {
78 return it->second;
79 }
80
81 // Generate new UID
82 auto new_uid = generate_uid();
83 std::string original_str{original_uid};
84
85 original_to_anon_[original_str] = new_uid;
86 anon_to_original_[new_uid] = original_str;
87
88 return new_uid;
89}
90
91auto uid_mapping::get_anonymized(std::string_view original_uid) const
92 -> std::optional<std::string> {
93 std::shared_lock lock(mutex_);
94 auto it = original_to_anon_.find(original_uid);
95 if (it != original_to_anon_.end()) {
96 return it->second;
97 }
98 return std::nullopt;
99}
100
101auto uid_mapping::get_original(std::string_view anonymized_uid) const
102 -> std::optional<std::string> {
103 std::shared_lock lock(mutex_);
104 auto it = anon_to_original_.find(anonymized_uid);
105 if (it != anon_to_original_.end()) {
106 return it->second;
107 }
108 return std::nullopt;
109}
110
112 std::string_view original_uid,
113 std::string_view anonymized_uid
114) -> kcenon::common::VoidResult {
115 std::unique_lock lock(mutex_);
116
117 auto existing = original_to_anon_.find(original_uid);
118 if (existing != original_to_anon_.end()) {
119 if (existing->second != anonymized_uid) {
120 return kcenon::common::make_error<std::monostate>(
121 1,
122 "Original UID already mapped to different value",
123 "uid_mapping"
124 );
125 }
126 return kcenon::common::ok();
127 }
128
129 std::string original_str{original_uid};
130 std::string anon_str{anonymized_uid};
131
132 original_to_anon_[original_str] = anon_str;
133 anon_to_original_[anon_str] = original_str;
134
135 return kcenon::common::ok();
136}
137
138auto uid_mapping::has_mapping(std::string_view original_uid) const -> bool {
139 std::shared_lock lock(mutex_);
140 return original_to_anon_.contains(original_uid);
141}
142
143auto uid_mapping::size() const -> std::size_t {
144 std::shared_lock lock(mutex_);
145 return original_to_anon_.size();
146}
147
148auto uid_mapping::empty() const -> bool {
149 std::shared_lock lock(mutex_);
150 return original_to_anon_.empty();
151}
152
154 std::unique_lock lock(mutex_);
155 original_to_anon_.clear();
156 anon_to_original_.clear();
157}
158
159auto uid_mapping::remove(std::string_view original_uid) -> bool {
160 std::unique_lock lock(mutex_);
161
162 auto it = original_to_anon_.find(original_uid);
163 if (it == original_to_anon_.end()) {
164 return false;
165 }
166
167 auto anon_uid = it->second;
168 original_to_anon_.erase(it);
169 anon_to_original_.erase(anon_uid);
170
171 return true;
172}
173
174auto uid_mapping::to_json() const -> std::string {
175 std::shared_lock lock(mutex_);
176
177 std::ostringstream oss;
178 oss << "{\n";
179 oss << " \"uid_root\": \"" << uid_root_ << "\",\n";
180 oss << " \"mappings\": [\n";
181
182 bool first = true;
183 for (const auto& [original, anon] : original_to_anon_) {
184 if (!first) {
185 oss << ",\n";
186 }
187 oss << " {\"original\": \"" << original
188 << "\", \"anonymized\": \"" << anon << "\"}";
189 first = false;
190 }
191
192 oss << "\n ]\n";
193 oss << "}";
194
195 return oss.str();
196}
197
198auto uid_mapping::from_json(std::string_view /*json*/)
199 -> kcenon::common::VoidResult {
200 // Simple JSON parsing - in production, use a proper JSON library
201 // This is a placeholder implementation
202 return kcenon::common::make_error<std::monostate>(
203 1, "JSON parsing not yet implemented", "uid_mapping"
204 );
205}
206
207auto uid_mapping::merge(const uid_mapping& other) -> std::size_t {
208 std::shared_lock other_lock(other.mutex_);
209 std::unique_lock this_lock(mutex_);
210
211 std::size_t added = 0;
212 for (const auto& [original, anon] : other.original_to_anon_) {
213 if (!original_to_anon_.contains(original)) {
214 original_to_anon_[original] = anon;
215 anon_to_original_[anon] = original;
216 ++added;
217 }
218 }
219
220 return added;
221}
222
223void uid_mapping::set_uid_root(std::string root) {
224 std::unique_lock lock(mutex_);
225 uid_root_ = std::move(root);
226}
227
228auto uid_mapping::get_uid_root() const -> std::string {
229 std::shared_lock lock(mutex_);
230 return uid_root_;
231}
232
233auto uid_mapping::generate_uid() const -> std::string {
234 // Generate UID based on timestamp and counter
235 auto now = std::chrono::system_clock::now();
236 auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
237 now.time_since_epoch()
238 ).count();
239
240 auto counter = uid_counter_.fetch_add(1);
241
242 // Random component for additional uniqueness
243 std::random_device rd;
244 std::mt19937 gen(rd());
245 std::uniform_int_distribution<std::uint32_t> dist(0, 999999);
246 auto random_part = dist(gen);
247
248 std::ostringstream oss;
249 oss << uid_root_ << "." << timestamp << "." << counter << "." << random_part;
250
251 return oss.str();
252}
253
254} // namespace kcenon::pacs::security
std::map< std::string, std::string, std::less<> > anon_to_original_
Reverse mapping: anonymized -> original.
auto has_mapping(std::string_view original_uid) const -> bool
Check if an original UID has been mapped.
auto generate_uid() const -> std::string
Generate a new unique UID.
void clear()
Clear all mappings.
auto to_json() const -> std::string
Export mappings to JSON format.
std::map< std::string, std::string, std::less<> > original_to_anon_
Forward mapping: original -> anonymized.
auto get_original(std::string_view anonymized_uid) const -> std::optional< std::string >
Get original UID from anonymized UID (reverse lookup)
std::string uid_root_
UID root for generated UIDs (default: pacs_system root)
auto get_anonymized(std::string_view original_uid) const -> std::optional< std::string >
Get existing mapping without creating new one.
void set_uid_root(std::string root)
Set the UID root for generated UIDs.
auto operator=(const uid_mapping &other) -> uid_mapping &
Copy assignment.
auto empty() const -> bool
Check if the mapping is empty.
auto get_uid_root() const -> std::string
Get the current UID root.
auto get_or_create(std::string_view original_uid) -> kcenon::common::Result< std::string >
Get existing mapping or create new one.
std::shared_mutex mutex_
Mutex for thread-safe access.
uid_mapping()=default
Default constructor - creates empty mapping.
auto size() const -> std::size_t
Get the number of mappings.
auto merge(const uid_mapping &other) -> std::size_t
Merge mappings from another uid_mapping.
auto add_mapping(std::string_view original_uid, std::string_view anonymized_uid) -> kcenon::common::VoidResult
Add a specific mapping.
std::atomic< std::uint64_t > uid_counter_
Counter for UID generation.
auto from_json(std::string_view json) -> kcenon::common::VoidResult
Import mappings from JSON format.
auto remove(std::string_view original_uid) -> bool
Remove a specific mapping.
UID mapping for consistent de-identification across studies.