Skip to content

Commit 9c486f8

Browse files
committed
add: orchestrator security threat indicator
1 parent d934011 commit 9c486f8

20 files changed

Lines changed: 390 additions & 5 deletions

modules/01-operating-room/CMakeLists.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,30 @@ connextdds_rtiddsgen_run(
5353
OUTPUT_DIRECTORY
5454
"${CMAKE_CURRENT_BINARY_DIR}/src/"
5555
LANG C++11
56-
DISABLE_PREPROCESSOR
56+
UNBOUNDED
57+
)
58+
59+
# Generate C++ types for DDS Security builtin logging topic when NDDSHOME is available.
60+
set(BUILTIN_LOGGING_IDL "${CONNEXTDDS_DIR}/resource/idl/builtin_logging_type.idl")
61+
connextdds_rtiddsgen_run(
62+
IDL_FILE
63+
"${BUILTIN_LOGGING_IDL}"
64+
OUTPUT_DIRECTORY
65+
"${CMAKE_CURRENT_BINARY_DIR}/src/"
66+
LANG C++11
67+
UNBOUNDED
5768
)
5869

5970
# Use command line to manually generate Python types
6071
execute_process(
61-
COMMAND bash -c "rtiddsgen ../../system_arch/Types.xml -ppDisable -d src -language python -create typefiles"
72+
COMMAND bash -c "${RTICODEGEN_DIR}/rtiddsgen ../../system_arch/Types.xml -ppDisable -d src -language python -create typefiles"
6273
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
6374
)
6475

6576
# Build object library with types
66-
add_library(refArchTypes OBJECT
77+
add_library(refArchTypes OBJECT
6778
${Types_CXX11_GENERATED_SOURCES}
79+
${builtin_logging_type_CXX11_GENERATED_SOURCES}
6880
)
6981
target_link_libraries(refArchTypes
7082
PRIVATE

modules/01-operating-room/src/DdsUtils.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ inline const std::string orchestrator_dp_fqn =
2828
DP_LIBRARY_NAME + SEPARATOR + DP_ORCHESTRATOR_NAME;
2929
inline const std::string patient_sensor_dp_fqn =
3030
DP_LIBRARY_NAME + SEPARATOR + DP_PATIENT_SENSOR_NAME;
31+
inline const std::string securelog_reader_dp_fqn =
32+
DP_LIBRARY_NAME + SEPARATOR + DP_SECURELOG_READER_NAME;
3133

3234
// DW / DR prefixes
3335
inline const std::string dw_prefix = PUBLISHER_NAME + SEPARATOR + DW_PREFIX;
@@ -45,6 +47,7 @@ inline const std::string device_command_dw_fqn =
4547
inline const std::string device_command_dr_fqn =
4648
dr_prefix + ENDPOINT_DEVICE_COMMAND_NAME;
4749
inline const std::string vitals_dw_fqn = dw_prefix + ENDPOINT_VITALS_NAME;
50+
inline const std::string securelog_dr_fqn = dr_prefix + ENDPOINT_SECURELOG_NAME;
4851

4952
} // namespace DdsUtils
5053

modules/01-operating-room/src/Orchestrator.cxx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "Types.hpp"
2828
#include "DdsUtils.hpp"
29+
#include "SecureLogUtils.hpp"
2930

3031
// Heartbeat listener to automatically monitor other applications
3132
class HeartbeatListener
@@ -114,6 +115,13 @@ class OrchestratorApp {
114115

115116
waitset_status += status_read_condition;
116117

118+
if (SecureLogUtils::is_secure(participant)) {
119+
securelog_reader = SecureLogUtils::setup_secure_log_reader(
120+
std::bind(&OrchestratorApp::process_secure_log, this, std::placeholders::_1),
121+
default_provider
122+
);
123+
}
124+
117125
app = Gtk::Application::create("orchestrator.orchestrator");
118126
app->signal_activate().connect([this]() { ui_setup(); });
119127
}
@@ -134,6 +142,10 @@ class OrchestratorApp {
134142
std::map<Common::DeviceType, Gtk::RadioButton *> device_map;
135143
Gtk::ScrolledWindow *scroll;
136144
Glib::RefPtr<Gtk::TextBuffer> buffer;
145+
Gtk::Label *security_indicator = nullptr;
146+
sigc::connection security_flash_connection;
147+
bool security_flash_on = false;
148+
int security_flash_ticks_remaining = 0;
137149

138150
// Connext entities
139151
dds::domain::DomainParticipant participant = dds::core::null;
@@ -145,6 +157,9 @@ class OrchestratorApp {
145157
rti::core::cond::AsyncWaitSet waitset_status;
146158
std::shared_ptr<HeartbeatListener> hb_listener;
147159

160+
// Connext secure logging entities
161+
SecureLogUtils::SecureLogReader securelog_reader = {dds::core::null, dds::core::null};
162+
148163
void log_alert(std::string msg)
149164
{
150165
Glib::signal_idle().connect([this, msg]() -> bool {
@@ -193,6 +208,86 @@ class OrchestratorApp {
193208
command_writer.write(command);
194209
}
195210

211+
void set_security_indicator_ok()
212+
{
213+
if (security_indicator == nullptr) {
214+
return;
215+
}
216+
217+
auto ctx = security_indicator->get_style_context();
218+
ctx->remove_class("security-threat-on");
219+
ctx->remove_class("security-threat-off");
220+
ctx->add_class("security-ok");
221+
security_indicator->set_text("SECURITY: OK");
222+
}
223+
224+
void trigger_security_flash()
225+
{
226+
Glib::signal_idle().connect([this]() -> bool {
227+
if (security_indicator == nullptr) {
228+
return false;
229+
}
230+
231+
// Keep flashing for ~8s after the latest threat event.
232+
security_flash_ticks_remaining = 16;
233+
234+
if (security_flash_connection.connected()) {
235+
return false;
236+
}
237+
238+
security_flash_on = false;
239+
security_flash_connection = Glib::signal_timeout().connect(
240+
[this]() -> bool {
241+
if (security_indicator == nullptr) {
242+
return false;
243+
}
244+
245+
auto ctx = security_indicator->get_style_context();
246+
ctx->remove_class("security-ok");
247+
ctx->remove_class("security-threat-on");
248+
ctx->remove_class("security-threat-off");
249+
250+
security_flash_on = !security_flash_on;
251+
if (security_flash_on) {
252+
security_indicator->set_text("SECURITY THREAT");
253+
ctx->add_class("security-threat-on");
254+
} else {
255+
security_indicator->set_text("SECURITY THREAT");
256+
ctx->add_class("security-threat-off");
257+
}
258+
259+
--security_flash_ticks_remaining;
260+
if (security_flash_ticks_remaining <= 0) {
261+
set_security_indicator_ok();
262+
security_flash_connection.disconnect();
263+
return false;
264+
}
265+
266+
return true;
267+
},
268+
500);
269+
270+
return false;
271+
});
272+
}
273+
274+
bool is_security_threat(const DDSSecurity::BuiltinLoggingTypeV2 &sample)
275+
{
276+
return static_cast<int32_t>(sample.severity())
277+
// return sample.value<int32_t>("severity")
278+
<= static_cast<int32_t>(DDSSecurity::LoggingLevel::WARNING_LEVEL);
279+
}
280+
281+
void process_secure_log(const SecureLogUtils::SecureLogType& log)
282+
{
283+
if (is_security_threat(log)) {
284+
std::stringstream ss;
285+
ss << "SECURITY THREAT [" << log.appname() << "] " << log.message();
286+
log_alert(ss.str());
287+
trigger_security_flash();
288+
}
289+
}
290+
196291
void process_status()
197292
{
198293
dds::sub::LoanedSamples<Common::DeviceStatus> samples =
@@ -263,6 +358,17 @@ class OrchestratorApp {
263358
hdr->pack_start(*logo, false, false, 0);
264359
hdr->reorder_child(*logo, 0);
265360
} catch (...) {}
361+
362+
if (SecureLogUtils::is_secure(participant)) {
363+
security_indicator = Gtk::manage(new Gtk::Label("SECURITY: OK"));
364+
auto ctx = security_indicator->get_style_context();
365+
ctx->add_class("security-indicator");
366+
ctx->add_class("security-ok");
367+
security_indicator->set_margin_start(10);
368+
security_indicator->set_margin_end(4);
369+
security_indicator->set_visible(true);
370+
hdr->pack_end(*security_indicator, false, false, 0);
371+
}
266372
}
267373
}
268374

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// (c) 2026 Copyright, Real-Time Innovations, Inc. (RTI) All rights reserved.
3+
//
4+
// RTI grants Licensee a license to use, modify, compile, and create derivative
5+
// works of the software solely for use with RTI Connext DDS. Licensee may
6+
// redistribute copies of the software provided that all such copies are
7+
// subject to this license. The software is provided "as is", with no warranty
8+
// of any type, including any warranty for fitness for any purpose. RTI is
9+
// under no obligation to maintain or support the software. RTI shall not be
10+
// liable for any incidental or consequential damages arising out of the use or
11+
// inability to use the software.
12+
13+
#ifndef SECURE_LOG_UTILS_H
14+
#define SECURE_LOG_UTILS_H
15+
16+
17+
#include <string>
18+
#include <dds/domain/DomainParticipant.hpp>
19+
#include <rti/core/policy/CorePolicy.hpp>
20+
#include <dds/sub/DataReader.hpp>
21+
#include <dds/sub/find.hpp>
22+
#include <rti/sub/SampleProcessor.hpp>
23+
#include "DdsUtils.hpp"
24+
#include "builtin_logging_type.hpp"
25+
26+
27+
// Define the SecureLogUtils namespace
28+
namespace SecureLogUtils {
29+
30+
using SecureLogType = DDSSecurity::BuiltinLoggingTypeV2;
31+
using SecureLogHandler = std::function<void(const SecureLogType&)>;
32+
using SecureLogReader = std::pair<dds::sub::DataReader<SecureLogType>, rti::sub::SampleProcessor>;
33+
34+
bool is_secure(const dds::domain::DomainParticipant& participant)
35+
{
36+
if (participant == dds::core::null) {
37+
throw std::runtime_error("Participant must be created to determine if secure.");
38+
}
39+
return participant.qos().policy<rti::core::policy::Property>().exists("com.rti.serv.load_plugin");
40+
}
41+
42+
SecureLogReader setup_secure_log_reader(
43+
SecureLogHandler log_handler,
44+
dds::core::QosProvider qos_provider = dds::core::QosProvider::Default()
45+
)
46+
{
47+
try {
48+
rti::domain::register_type<SecureLogType>();
49+
} catch (const std::exception& e) {
50+
std::cerr << "Failed to register internal secure logging type: " << e.what() << std::endl;
51+
}
52+
53+
// Initialize Participant
54+
dds::domain::DomainParticipant securelog_participant =
55+
qos_provider.extensions().create_participant_from_config(
56+
DdsUtils::securelog_reader_dp_fqn);
57+
if (securelog_participant == dds::core::null) {
58+
throw std::runtime_error("Failed to lookup secure log reader participant");
59+
}
60+
61+
// Initialize DataReader
62+
dds::sub::DataReader<SecureLogType> securelog_reader =
63+
rti::sub::find_datareader_by_name<
64+
dds::sub::DataReader<SecureLogType>>(
65+
securelog_participant,
66+
DdsUtils::securelog_dr_fqn);
67+
if (securelog_reader == dds::core::null) {
68+
throw std::runtime_error("Failed to lookup secure log reader datareader");
69+
}
70+
71+
rti::sub::SampleProcessor securelog_processor;
72+
securelog_processor.attach_reader(
73+
securelog_reader,
74+
[log_handler](const rti::sub::LoanedSample<SecureLogType>&sample) {
75+
if (sample.info().valid()) {
76+
log_handler(sample.data());
77+
}
78+
}
79+
);
80+
81+
return {securelog_reader, securelog_processor};
82+
}
83+
84+
} // namespace SecureLogUtils
85+
86+
#endif // SECURE_LOG_UTILS_H

modules/01-operating-room/ui/orchestrator.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@
3636
font-size: 14px;
3737
}
3838

39+
.security-indicator {
40+
font-size: 14px;
41+
font-weight: bold;
42+
padding: 4px 10px;
43+
border-radius: 12px;
44+
}
45+
46+
.security-ok {
47+
color: #00E676;
48+
background-color: #00331A;
49+
border: 1px solid #006633;
50+
}
51+
52+
.security-threat-on {
53+
color: #FFFFFF;
54+
background-color: #C62828;
55+
border: 1px solid #FF8A80;
56+
}
57+
58+
.security-threat-off {
59+
color: #FF8A80;
60+
background-color: #2A0A0A;
61+
border: 1px solid #7A1F1F;
62+
}
63+
3964
/* ── Device cards ────────────────────────────────────────────────────── */
4065
button.device-card,
4166
.device-card {

system_arch/Types.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<const name="DP_ORCHESTRATOR_NAME" type="string" value="&quot;dp/Orchestrator&quot;"/>
1010
<const name="DP_PATIENT_MONITOR_NAME" type="string" value="&quot;dp/PatientMonitor&quot;"/>
1111
<const name="DP_PATIENT_SENSOR_NAME" type="string" value="&quot;dp/PatientSensor&quot;"/>
12+
<const name="DP_SECURELOG_READER_NAME" type="string" value="&quot;dp/SecureLogReader&quot;"/>
1213
<const name="SEPARATOR" type="string" value="&quot;::&quot;"/>
1314
<const name="PUBLISHER_NAME" type="string" value="&quot;p/publisher&quot;"/>
1415
<const name="SUBSCRIBER_NAME" type="string" value="&quot;s/subscriber&quot;"/>
@@ -19,6 +20,7 @@
1920
<const name="ENDPOINT_DEVICE_HEARTBEAT_NAME" type="string" value="&quot;DeviceHeartbeat&quot;"/>
2021
<const name="ENDPOINT_DEVICE_COMMAND_NAME" type="string" value="&quot;DeviceCommand&quot;"/>
2122
<const name="ENDPOINT_VITALS_NAME" type="string" value="&quot;Vitals&quot;"/>
23+
<const name="ENDPOINT_SECURELOG_NAME" type="string" value="&quot;SecureLog&quot;"/>
2224
</module>
2325
</module>
2426
<module name="Common">

system_arch/qos/NonSecureAppsQos.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ inability to use the software. -->
1717
<qos_profile name="Orchestrator" base_name="SystemLibrary::DefaultParticipant"/>
1818
<qos_profile name="PatientSensor" base_name="SystemLibrary::DefaultParticipant"/>
1919
<qos_profile name="PatientMonitor" base_name="SystemLibrary::DefaultParticipant"/>
20+
<qos_profile name="SecureLogReader" base_name="SystemLibrary::DefaultParticipant"/>
2021
</qos_library>
2122

2223
<qos_library name="RtiServicesLib">

system_arch/qos/Qos.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,33 @@ inability to use the software. -->
116116
</deadline>
117117
</datareader_qos>
118118
</qos_profile>
119+
120+
<!-- For the Secure Log topic -->
121+
<qos_profile name="SecureLog" base_name="BuiltinQosLib::Generic.KeepLastReliable.TransientLocal">
122+
<datawriter_qos>
123+
<history>
124+
<!-- Default history depth used by Security Plugin secure logging writers -->
125+
<depth>64</depth>
126+
</history>
127+
</datawriter_qos>
128+
<datareader_qos>
129+
<base_name>
130+
<!-- Secure logging type is unbounded, so we will use dynamic allocation -->
131+
<element>BuiltinQosSnippetLib::Optimization.DataCache.LargeData.DynamicMemAlloc</element>
132+
</base_name>
133+
<!-- Default history depth used by Security Plugin secure logging writers -->
134+
<history>
135+
<depth>64</depth>
136+
</history>
137+
<resource_limits>
138+
<max_samples>64</max_samples>
139+
</resource_limits>
140+
<!-- We do not care about historical secure log events prior to joining a system -->
141+
<durability>
142+
<kind>VOLATILE_DURABILITY_QOS</kind>
143+
</durability>
144+
</datareader_qos>
145+
</qos_profile>
119146
</qos_library>
120147

121148
</dds>

0 commit comments

Comments
 (0)