Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6ac72aa
Update Gazebo and ROS configurations for Kilted and Ionic releases
jmservera Jul 20, 2026
33bfeda
Add Product Requirements Document for UbeROS Simulation and Visualiza…
jmservera Jul 21, 2026
c22ca52
docs(prd): approve Simulation and Visualization PRD v1.0.0
jmservera Jul 21, 2026
e6420c5
docs(sim-theme-a): seed pluggable framework plan (FR-A1..A4)
jmservera Jul 21, 2026
6bb43ea
feat(sim-theme-a): pluggable simulator registry + GET /simulators + d…
jmservera Jul 21, 2026
95c331e
docs(sim-theme-c): seed Turtlesim visualizer plan (FR-C1..C4)
jmservera Jul 21, 2026
3974135
feat(sim-theme-c): turtlesim service (Xvfb+x11vnc+websockify) + /sim/…
jmservera Jul 21, 2026
4dfb12f
docs(sim-theme-f): seed Gazebo gzweb web viz plan (FR-F1..F5)
jmservera Jul 21, 2026
389336f
feat(sim-theme-f): headless gz sim -s + WebsocketServer + gzweb clien…
jmservera Jul 21, 2026
56720e5
docs(sim-theme-e): seed ROS 2 integration plan (FR-E1..E6)
jmservera Jul 21, 2026
b4ae2ec
feat(sim-theme-e): co-located ros_gz_bridge with /clock default + dis…
jmservera Jul 21, 2026
9519915
Merge branch 'main' into jmservera/sim-theme-f-gzweb
jmservera Jul 23, 2026
92b5194
merge turtlesim
jmservera Jul 23, 2026
12bc293
Merge branch 'jmservera/sim-theme-f-gzweb' into jmservera/sim-theme-e…
jmservera Jul 23, 2026
16f9fa4
Improve shell script linting by dynamically finding scripts and handl…
jmservera Jul 23, 2026
e57130e
Merge branch 'jmservera/sim-theme-f-gzweb' into jmservera/sim-theme-e…
jmservera Jul 23, 2026
16b7814
Refactor acceptance tests and CI configuration for gzweb integration
jmservera Jul 23, 2026
5608494
Merge branch 'jmservera/sim-theme-f-gzweb' into jmservera/sim-theme-e…
jmservera Jul 23, 2026
c8b6b96
Enhance CI integration and smoke tests for Turtlesim support
jmservera Jul 23, 2026
b67cef7
Update CI integration workflow: enhance Playwright acceptance tests a…
jmservera Jul 23, 2026
9cc4ecc
Refactor CI configuration and update documentation for Gazebo and Tur…
jmservera Jul 23, 2026
904b6d6
Merge branch 'jmservera/sim-theme-f-gzweb' into jmservera/sim-theme-e…
jmservera Jul 23, 2026
7f3ad93
Update documentation across multiple files: enhance README, BRDs, ADR…
jmservera Jul 23, 2026
d5b576b
Update UBEROS_SERVICES to include turtlesim and enhance documentation…
jmservera Jul 23, 2026
eac28a6
Merge branch 'jmservera/sim-theme-f-gzweb' into jmservera/sim-theme-e…
jmservera Jul 23, 2026
335aaff
Merge branch 'main' into jmservera/sim-theme-e-ros-integration
jmservera Jul 23, 2026
4e3c6b9
Enhance ros_gz bridge startup behavior to reconnect when /clock is av…
jmservera Jul 23, 2026
a8ae89c
Add verified test procedure for ros_gz_bridge connectivity with Gazebo
jmservera Jul 23, 2026
9afd947
Fix documentation typos in sim-theme-e-ros-integration and update Doc…
jmservera Jul 23, 2026
d8835ba
Update Dockerfile to use bash with pipefail for RUN commands
jmservera Jul 24, 2026
4f2c9a8
Add FASTDDS_DEFAULT_PROFILES_FILE environment variable for DDS config…
jmservera Jul 24, 2026
6ed26a2
Update environment variable for Fast DDS configuration to reflect dep…
jmservera Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ services:
ROS_DOMAIN_ID: ${ROS_DOMAIN_ID:-42}
RMW_IMPLEMENTATION: ${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp}
ROS_DISCOVERY_SERVER: discovery-server:11811
FASTRTPS_DEFAULT_PROFILES_FILE: /etc/ros/dds_discovery.xml
FASTDDS_DEFAULT_PROFILES_FILE: /etc/ros/dds_discovery.xml
volumes:
- ./workspace/src:/ros_ws/src
- ros-workspace:/ros_ws
Expand Down Expand Up @@ -103,6 +103,10 @@ services:
ROS_DOMAIN_ID: ${ROS_DOMAIN_ID:-42}
RMW_IMPLEMENTATION: ${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp}
ROS_DISCOVERY_SERVER: discovery-server:11811
# The co-located ros_gz bridge (Theme E) joins the ROS graph over the
# discovery server via unicast; this XML profile (bundled in the image)
# points its DDS participant at discovery-server:11811 (FR-E3/FR-E4).
FASTDDS_DEFAULT_PROFILES_FILE: /etc/ros/dds_discovery.xml
volumes:
- ./simulation:/simulation:ro
- gazebo-home:/root/.gz
Expand Down Expand Up @@ -139,7 +143,7 @@ services:
ROS_DOMAIN_ID: ${ROS_DOMAIN_ID:-42}
RMW_IMPLEMENTATION: ${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp}
ROS_DISCOVERY_SERVER: discovery-server:11811
FASTRTPS_DEFAULT_PROFILES_FILE: /etc/ros/dds_discovery.xml
FASTDDS_DEFAULT_PROFILES_FILE: /etc/ros/dds_discovery.xml
networks:
- ros_net
- web_net
Expand Down
2 changes: 1 addition & 1 deletion docs/decisions/ADR-007-discovery-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ DDS ports and break the single-ingress invariant (INV-04).
Run a dedicated **Fast DDS Discovery Server** as its own service
(`fastdds discovery -i 0 -l 0.0.0.0 -p 11811`) on the internal `ros_net`. The
`ros` and `simulator` services point at it via `ROS_DISCOVERY_SERVER` and a
`FASTRTPS_DEFAULT_PROFILES_FILE` (`dds_discovery.xml`), replacing multicast with
`FASTDDS_DEFAULT_PROFILES_FILE` (`dds_discovery.xml`), replacing multicast with
a unicast client/server discovery model.

## Alternatives considered
Expand Down
48 changes: 48 additions & 0 deletions docs/plans/sim-theme-e-ros-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Theme E — ROS 2 integration for Gazebo (ros_gz)

> Plan stub. Source of truth: [Simulation & Visualization PRD](../prds/uberos-simulation-visualization.md) §7.5.

## Scope — FR-E1 … FR-E6
- FR-E1 — `ros_gz_bridge` runs co-located with `gz sim` in the Gazebo container.
- FR-E2 — Default bridge for `/clock` (`rosgraph_msgs/msg/Clock`) via a bridge config file; further per-world bridges additive.
- FR-E3 — Bridge reaches ROS via the Fast DDS discovery server (unicast, no multicast); gz-transport stays intra-container.
- FR-E4 — Gazebo entrypoint sources `/opt/ros/${ROS_DISTRO}` and provides the DDS discovery config.
- FR-E5 — `ROS_DISTRO` ↔ `GZ_RELEASE` pinned to a compatible pair (kilted ↔ ionic).
- FR-E6 — Remove the now-redundant `ros-gz` from the `ros` image (bridge lives only in the Gazebo container).

## Dependency / lane
- **Lane 2 (Gazebo backend).** Coordinate with **Theme F** (both reshape the Gazebo container/service); sequence E after F's container reshape or share the lane.
- Architecture rationale: two discovery systems (gz-transport vs DDS) — see PRD §6.

## Likely files
- `services/simulator|gazebo/` entrypoint + `ros_gz` bridge config (`/clock`)
- `services/ros/Dockerfile` (drop `ros-gz`)

## Tasks
- [ ] Research: ros_gz_bridge config format for the pinned pair; discovery-server env vs XML in the Gazebo container
- [ ] Plan: bridge launch + `/clock` config + entrypoint ROS sourcing
- [ ] Implement: co-located bridge, `/clock`, ros-image cleanup
- [ ] Tests: `ros2 topic echo /clock rosgraph_msgs/msg/Clock --once` returns a clock sample; no multicast
- [ ] Acceptance (PRD §7.5)

## Verified test procedure (2026-07-24)

Use this from the ROS shell (web terminal in the `ros` container) to verify that Gazebo is reachable from ROS through `ros_gz_bridge`:

```bash
source /opt/ros/${ROS_DISTRO}/setup.bash
export ROS_DOMAIN_ID=42
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export ROS_DISCOVERY_SERVER=discovery-server:11811
export FASTDDS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml
ros2 topic echo /clock rosgraph_msgs/msg/Clock --once
```

Expected result: one `clock` message is printed.

Notes:
- In this stack, `ros2 topic list` may not show `/clock` even when the bridge is working.
- The explicit type in `ros2 topic echo` is required for a reliable check.
- `ROS_DISCOVERY_SERVER` must be `discovery-server:11811` with no spaces around `:`.
- Use `--once` (no space), not `-- once`.
- Fast DDS deprecates `FASTRTPS_DEFAULT_PROFILES_FILE`; use `FASTDDS_DEFAULT_PROFILES_FILE`.
7 changes: 5 additions & 2 deletions docs/specs/01-Init-research.md
Original file line number Diff line number Diff line change
Expand Up @@ -661,11 +661,14 @@ exec ros2 launch rosbridge_server rosbridge_websocket_launch.xml port:=9090
ROS_DOMAIN_ID=42
ROS_DISCOVERY_SERVER=discovery-server:11811
RMW_IMPLEMENTATION=rmw_fastrtps_cpp
FASTRTPS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml
FASTDDS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml
```

Domain ID `42` is inside the cross-platform-safe range of 0–101.[^dds-domain]

`FASTRTPS_DEFAULT_PROFILES_FILE` is deprecated in current Fast DDS releases.
Use `FASTDDS_DEFAULT_PROFILES_FILE` for active deployments.

> **Recommendation:** Use a Fast DDS Discovery Server because Docker bridge networks do not reliably carry the multicast discovery used by default DDS configuration. Without explicit discovery, ROS nodes in separate containers can silently fail to find each other.[^dds-multicast]

### Gazebo, World Assets, and GUI Streaming
Expand Down Expand Up @@ -1014,7 +1017,7 @@ RUN chmod +x /entrypoint.sh

ENV ROS_DOMAIN_ID=42
ENV RMW_IMPLEMENTATION=rmw_fastrtps_cpp
ENV FASTRTPS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml
ENV FASTDDS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml

EXPOSE 9090 7681
ENTRYPOINT ["/entrypoint.sh"]
Expand Down
46 changes: 46 additions & 0 deletions services/gazebo/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ ARG GZ_RELEASE=ionic
ARG ROS_DISTRO=kilted
FROM ghcr.io/openrobotics/gazebo:${GZ_RELEASE}-full

# Hadolint DL4006: RUN commands in this file use pipes, so execute RUN with
# bash + pipefail to propagate errors from all pipeline segments.
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# ROS_DISTRO is not automatically re-exposed after FROM; redeclare it so the
# Theme E ros_gz bridge (added later to this same container) can source ROS.
ARG ROS_DISTRO
Expand All @@ -25,8 +29,50 @@ ENV ROS_DISTRO=${ROS_DISTRO}
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
procps \
curl \
gnupg2 \
ca-certificates \
Comment thread
jmservera marked this conversation as resolved.
&& rm -rf /var/lib/apt/lists/*

# --- Theme E: ros_gz bridge (FR-E1/FR-E4/FR-E5) -------------------------------
# The Gazebo `-full` base is NOT a ROS image, so add the packages.ros.org apt
# repo (signed-by keyring, no apt-key) and install the ros_gz BRIDGE only.
#
# We install `ros-${ROS_DISTRO}-ros-gz-bridge` rather than the full `ros-gz`
# metapackage on purpose (FR-E5): the bridge is the ONLY ros_gz component this
# container runs, and it depends solely on the gz-transport/gz-msgs stack for
# the PINNED Gazebo release — which the kilted<->ionic pairing makes identical
# to the Gazebo already in the base image, so apt resolves ONE Gazebo version
# and never pulls a second. The full metapackage would drag in ros_gz_sim /
# ros_gz_image (a redundant sim launcher + camera-render deps) that this
# headless, client-rendered path does not need. Keep ROS_DISTRO<->GZ_RELEASE
# pinned to kilted<->ionic (FR-E5); changing one without the other can pull a
# mismatched Gazebo.
RUN . /etc/os-release \
&& UBUNTU_CODENAME="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}" \
&& test -n "${UBUNTU_CODENAME}" \
&& curl -fsSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
| gpg --dearmor -o /usr/share/keyrings/ros-archive-keyring.gpg \
# NOTE: HTTP is intentional here. In this environment the HTTPS endpoint
# fails certificate principal validation (`SEC_E_WRONG_PRINCIPAL`), while
# HTTP works reliably for apt index retrieval.
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu ${UBUNTU_CODENAME} main" \
> /etc/apt/sources.list.d/ros2.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-ros-gz-bridge \
&& rm -rf /var/lib/apt/lists/*

# DDS discovery-server client profile so the ros_gz bridge finds the ROS graph
# over unicast (FR-E3/FR-E4, RISK-4). Same profile the ros/turtlesim services
# carry; ROS_DISCOVERY_SERVER + ROS_DOMAIN_ID are supplied by compose.
COPY config/dds_discovery.xml /etc/ros/dds_discovery.xml
ENV FASTDDS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml

# Default bridge topic map (/clock). Additive: more topics append here (FR-E2).
COPY config/ros_gz_bridge.yaml /etc/uberos/ros_gz_bridge.yaml
# ------------------------------------------------------------------------------

# Disable automatic Fuel downloads; the world uses built-in primitives only.
COPY config/fuel.yaml /root/.gz/fuel/config.yaml

Expand Down
38 changes: 38 additions & 0 deletions services/gazebo/config/dds_discovery.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Fast DDS Discovery Server client profile (PRD Theme E, FR-E4).

This is the Gazebo container's copy of the shared discovery-client profile
(kept aligned with services/ros/config/dds_discovery.xml for behavior). The ros_gz bridge that
runs in this container joins the ROS graph through the central
discovery-server over UNICAST — Docker bridge networks do not reliably carry
the multicast that default DDS discovery relies on (research RISK-4).

Only the bridge's DDS side uses this profile; gz sim's own gz-transport
discovery stays intra-container and is unaffected.
-->
<dds xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<profiles>
<participant profile_name="uberos_discovery_client" is_default_profile="true">
<rtps>
<builtin>
<discovery_config>
<discoveryProtocol>CLIENT</discoveryProtocol>
<discoveryServersList>
<!-- Fast DDS 3.x (ROS Kilted): discoveryServersList children are
<locator> elements directly. The pre-3.x
<RemoteServer prefix="GUID"> form was removed and makes the
parser treat the list as empty ("without content"). -->
<locator>
<udpv4>
<address>discovery-server</address>
<port>11811</port>
</udpv4>
</locator>
</discoveryServersList>
</discovery_config>
</builtin>
</rtps>
</participant>
</profiles>
</dds>
27 changes: 27 additions & 0 deletions services/gazebo/config/ros_gz_bridge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# UbeROS ros_gz bridge config (PRD Theme E, FR-E2).
#
# Consumed by ros_gz_bridge's `parameter_bridge` (`--ros-args -p config_file:=`),
# which runs co-located with `gz sim` in THIS container (FR-E1). The bridge is
# the only component that speaks both transports: it reads gz-transport topics
# over localhost (intra-container, reliable) and republishes them on the ROS
# graph, which is discovered over the Fast DDS discovery server via unicast
# (FR-E3). gz sim itself never joins DDS.
#
# Default topic set is intentionally minimal: only the simulation clock, so ROS
# nodes can run on simulated time. Additional per-world/per-model topics
# (e.g. /cmd_vel, /odom, joint states, TF) are ADDITIVE — append more list
# entries here (or supply a world-specific config via UBEROS_BRIDGE_CONFIG)
# without changing the entrypoint.
#
# Entry schema (ros_gz_bridge): each list item maps one ROS topic to one gz
# topic with an explicit message-type pair and a direction. Directions are
# GZ_TO_ROS, ROS_TO_GZ, or BIDIRECTIONAL.

# /clock — simulation time published by gz sim's physics; bridged into ROS as
# rosgraph_msgs/msg/Clock so `use_sim_time` consumers follow the simulator.
# One-way (GZ_TO_ROS): nothing in ROS should drive the simulator's clock.
- ros_topic_name: "/clock"
gz_topic_name: "/clock"
ros_type_name: "rosgraph_msgs/msg/Clock"
gz_type_name: "gz.msgs.Clock"
direction: GZ_TO_ROS
46 changes: 34 additions & 12 deletions services/gazebo/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,40 @@ GZ_PID=$!
gz launch -v 1 "${LAUNCH_FILE}" &
WS_PID=$!

# --- Theme E integration point (ros_gz bridge) --------------------------------
# The ros_gz bridge is OUT OF SCOPE for Theme F. Theme E adds ROS + the bridge
# to THIS container: install ros-${ROS_DISTRO}-ros-gz in the Dockerfile, then
# source ROS and start the bridge here, e.g.
# set +u; source "/opt/ros/${ROS_DISTRO}/setup.bash"; set -u
# ros2 run ros_gz_bridge parameter_bridge ... &
# Nothing above depends on ROS, so the headless web-viz path works standalone.
# --- Theme E integration point (ros_gz bridge, FR-E1..FR-E4) ------------------
# The ros_gz bridge runs co-located with `gz sim` in THIS container so the
# gz-transport hop stays intra-container (localhost) and only the DDS side
# crosses the network, via the Fast DDS discovery server (unicast, no
# multicast). Source ROS first (its setup scripts reference unbound vars, so
# relax nounset while sourcing), then start parameter_bridge with the /clock
# config. gz sim/gz launch were started ABOVE, before ROS is on PATH, so they
# keep their native gz environment and are unaffected by the ROS overlay.
if [[ -z "${ROS_DISTRO:-}" ]]; then
echo "ERROR: ROS_DISTRO is required for Theme E bridge startup" >&2
exit 1
fi

set +u
# shellcheck source=/dev/null
source "/opt/ros/${ROS_DISTRO}/setup.bash"
set -u

BRIDGE_CONFIG="${UBEROS_BRIDGE_CONFIG:-/etc/uberos/ros_gz_bridge.yaml}"

# Invoke the parameter_bridge executable by its full install path instead of
# `ros2 run`: this headless image installs only ros-gz-bridge (no ros2cli), so
# the `ros2` CLI plugin is not present. The bridge reads the YAML topic map and
# registers on the ROS graph through the discovery server (env from compose).
# Bridge may start before `gz sim` advertises /clock; this is intentional (it reconnects when /clock appears).
"/opt/ros/${ROS_DISTRO}/lib/ros_gz_bridge/parameter_bridge" \
Comment thread
jmservera marked this conversation as resolved.
--ros-args -p "config_file:=${BRIDGE_CONFIG}" &
BRIDGE_PID=$!
# ------------------------------------------------------------------------------

# Forward termination to both children for a clean shutdown.
trap 'kill -TERM "${GZ_PID}" "${WS_PID}" 2>/dev/null || true' TERM INT
# Forward termination to all children for a clean shutdown.
trap 'kill -TERM "${GZ_PID}" "${WS_PID}" "${BRIDGE_PID}" 2>/dev/null || true' TERM INT

# Exit as soon as either process dies so Docker's restart policy can recover the
# pair (a lone websocket server or a lone sim is not useful).
wait -n "${GZ_PID}" "${WS_PID}"
# Exit as soon as any core process dies so Docker's restart policy can recover
# the group (a lone websocket server, a lone sim, or a lone bridge is not
# useful).
wait -n "${GZ_PID}" "${WS_PID}" "${BRIDGE_PID}"
11 changes: 8 additions & 3 deletions services/ros/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,21 @@ FROM ros:${ROS_DISTRO}-ros-base
ARG ROS_DISTRO
ENV ROS_DISTRO=${ROS_DISTRO}

# Install colcon toolchain, rosbridge, rosapi, ros-gz integration, and utilities.
# Install colcon toolchain, rosbridge, rosapi, and utilities.
# procps provides pgrep; curl/wget used by tooling and health probing.
#
# The ros_gz bridge is NOT installed here (Theme E, FR-E6): it runs co-located
# with `gz sim` in the gazebo container so the gz-transport hop stays
# intra-container and only its DDS side crosses the network. This image would
# only need `ros-gz` if a ROS-side node consumed ros_gz_interfaces, which none
# currently do.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3-colcon-common-extensions \
python3-rosdep \
python3-vcstool \
ros-${ROS_DISTRO}-rosbridge-suite \
ros-${ROS_DISTRO}-rosapi \
ros-${ROS_DISTRO}-ros-gz \
procps \
tmux \
curl \
Expand Down Expand Up @@ -55,7 +60,7 @@ RUN chmod +x /entrypoint.sh /usr/local/bin/uberos-term

ENV ROS_DOMAIN_ID=42
ENV RMW_IMPLEMENTATION=rmw_fastrtps_cpp
ENV FASTRTPS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml
ENV FASTDDS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml

# rosbridge WebSocket and ttyd. Exposed for inter-container use only; the proxy
# is the sole ingress and these ports must never be host-published.
Expand Down
2 changes: 1 addition & 1 deletion services/turtlesim/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ RUN chmod +x /entrypoint.sh
ENV DISPLAY=:99
ENV ROS_DOMAIN_ID=42
ENV RMW_IMPLEMENTATION=rmw_fastrtps_cpp
ENV FASTRTPS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml
ENV FASTDDS_DEFAULT_PROFILES_FILE=/etc/ros/dds_discovery.xml

# noVNC (6080) and VNC (5900) are reachable only through the proxy; never
# host-published (INV-04, WP-13 security baseline).
Expand Down
Loading