32std::string make_cid(std::size_t index) {
33 static thread_local std::mt19937_64 rng(
34 static_cast<std::uint64_t
>(
35 std::chrono::steady_clock::now().time_since_epoch().count()));
36 std::uniform_int_distribution<std::uint64_t> dist;
37 std::ostringstream oss;
38 oss <<
"doc-" << index <<
'-' << std::hex << dist(rng)
51 std::shared_ptr<kcenon::pacs::security::xds_audit_sink> sink) {
59 std::vector<std::string> document_uids;
60 document_uids.reserve(s.
documents.size());
62 document_uids.push_back(d.unique_id);
66 audit(s, document_uids, r);
70 std::vector<std::string> cids;
72 for (std::size_t i = 0; i < s.
documents.size(); ++i) {
73 cids.push_back(make_cid(i));
77 if (env_result.is_err()) {
78 return do_audit(kcenon::common::make_error<submit_response>(
81 auto env = env_result.value();
86 if (sign_result.is_err()) {
87 return do_audit(kcenon::common::make_error<submit_response>(
88 sign_result.error()));
91 std::vector<detail::mtom_part> parts;
93 for (std::size_t i = 0; i < s.
documents.size(); ++i) {
98 parts.push_back(std::move(p));
102 if (mtom_result.is_err()) {
103 return do_audit(kcenon::common::make_error<submit_response>(
104 mtom_result.error()));
106 auto mtom = mtom_result.value();
110 if (http_result.is_err()) {
111 return do_audit(kcenon::common::make_error<submit_response>(
112 http_result.error()));
114 auto response = http_result.value();
129 const std::string& body,
const std::string& submission_set_uid) {
131 return kcenon::common::make_error<submit_response>(
133 "RegistryResponse exceeds 8 MiB cap (" +
134 std::to_string(body.size()) +
" bytes) - rejected to "
135 "bound pugixml memory footprint",
138 pugi::xml_document doc;
139 auto parse = doc.load_buffer(body.data(), body.size(),
140 pugi::parse_default, pugi::encoding_utf8);
142 return kcenon::common::make_error<submit_response>(
144 std::string(
"failed to parse RegistryResponse: ") +
149 auto registry_response =
151 "//*[local-name()='RegistryResponse' or "
152 "local-name()='RegistryResponseType']")
154 if (!registry_response) {
155 return kcenon::common::make_error<submit_response>(
157 "response contains no RegistryResponse element",
161 const std::string status =
162 registry_response.attribute(
"status").as_string();
163 if (status.empty()) {
164 return kcenon::common::make_error<submit_response>(
166 "RegistryResponse is missing status attribute",
170 static constexpr std::string_view success_status =
171 "urn:oasis:names:tc:ebxml-regrep:ResponseStatusType:Success";
172 static constexpr std::string_view partial_success_status =
173 "urn:oasis:names:tc:ebxml-regrep:ResponseStatusType:PartialSuccess";
176 out.registry_response_status = status;
177 out.submission_set_unique_id = submission_set_uid;
179 if (status == success_status) {
182 if (status == partial_success_status) {
183 return kcenon::common::make_error<submit_response>(
185 "registry reported PartialSuccess: " + status,
188 return kcenon::common::make_error<submit_response>(
190 "registry reported non-success status: " + status,
195 const std::vector<std::string>& document_uids,
198 kcenon::pacs::security::xds_iti41_event e;
203 e.document_unique_ids = document_uids;
209 e.failure_description = r.error().message;
216 std::shared_ptr<kcenon::pacs::security::xds_audit_sink>
audit_sink_;
220 : impl_(std::make_unique<
impl>(std::move(opts), std::move(signing))) {}
233 std::shared_ptr<kcenon::pacs::security::xds_audit_sink> sink) {
void set_audit_sink(std::shared_ptr< kcenon::pacs::security::xds_audit_sink > sink)
impl(http_options opts, signing_material signing)
std::shared_ptr< kcenon::pacs::security::xds_audit_sink > audit_sink_
kcenon::common::Result< submit_response > parse_registry_response(const std::string &body, const std::string &submission_set_uid)
static constexpr std::size_t kMaxRegistryResponseBytes
void audit(const submission_set &s, const std::vector< std::string > &document_uids, const kcenon::common::Result< submit_response > &r)
signing_material signing_
kcenon::common::Result< submit_response > submit(const submission_set &s)
ITI-41 Document Source actor.
void set_audit_sink(std::shared_ptr< kcenon::pacs::security::xds_audit_sink > sink)
Install an ATNA audit sink.
kcenon::common::Result< submit_response > submit(const submission_set &s)
Submit an XDS.b submission set.
std::unique_ptr< impl > impl_
document_source(http_options opts, signing_material signing)
Construct an ITI-41 Document Source bound to an endpoint and a signing credential.
IHE XDS.b Document Source Actor (ITI-41 Provide and Register Document Set-b).
libcurl-backed HTTPS POST client for ITI-41 transport.
MTOM / XOP multipart/related packager for ITI-41.
kcenon::common::Result< packaged_mtom > package_mtom(const std::string &envelope_xml, const std::vector< mtom_part > &parts)
Assemble a multipart/related MTOM body.
kcenon::common::Result< http_response > http_post(const http_options &opts, const std::string &content_type, const std::string &body)
POST body to opts.endpoint with the given Content-Type.
kcenon::common::Result< bool > sign_envelope(built_envelope &env, std::string_view cert_pem, std::string_view key_pem, std::string_view key_password)
Sign the body + timestamp of env in place.
kcenon::common::Result< built_envelope > build_iti41_envelope(const submission_set &s, const std::vector< std::string > &cid_refs)
Build a SOAP envelope for ITI-41 from submission metadata and a list of content-id references for MTO...
constexpr std::string_view error_source
Short identifier used as the "source" field of make_error.
@ transport_invalid_response
@ registry_partial_success
@ registry_failure_response
@ transport_response_too_large
SOAP 1.2 envelope builder for IHE XDS.b ITI-41 requests.
One attachment part, mapped 1:1 to an xop:Include in the envelope.
std::vector< std::uint8_t > content
HTTPS transport options for the Document Source actor.
Signing material for WS-Security XML-DSig.
std::string private_key_password
std::string certificate_pem
std::string private_key_pem
ebRIM/ebRS-shaped submission set metadata.
std::string submission_set_unique_id
std::vector< xds_document > documents
WS-Security XML-DSig signer backed by OpenSSL EVP_DigestSign.
ATNA audit event helpers for the IHE XDS.b actors (ITI-18/41/43).