Skip to content

Commit 9134dc0

Browse files
authored
fix: added validation and tests to range sensor message types for nav2_collision_monitor (#6231)
* fix: added validation and tests to range sensor message types for nav2_collision_monitor Signed-off-by: Mann Bhanushali <bhanushalimann@gmail.com> * fix: Addressed Review changes for nav2_collision_monitor range message validation tests. Signed-off-by: Mann Bhanushali <bhanushalimann@gmail.com> * fix: Added unit tests for nav2_collision_monitor range validation updates Signed-off-by: Mann Bhanushali <bhanushalimann@gmail.com> --------- Signed-off-by: Mann Bhanushali <bhanushalimann@gmail.com>
1 parent bc461f4 commit 9134dc0

7 files changed

Lines changed: 202 additions & 0 deletions

File tree

nav2_collision_monitor/src/range.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
#include "tf2/transform_datatypes.hpp"
2222

2323
#include "nav2_ros_common/node_utils.hpp"
24+
#include "nav2_ros_common/validate_messages.hpp"
2425
#include "nav2_util/robot_utils.hpp"
2526

2627
namespace nav2_collision_monitor
2728
{
2829

30+
constexpr size_t MAX_RANGE_DATA_POINTS = 1e4;
31+
2932
Range::Range(
3033
const nav2::LifecycleNode::WeakPtr & node,
3134
const std::string & source_name,
@@ -89,6 +92,16 @@ bool Range::getData(
8992
return false;
9093
}
9194

95+
const size_t point_count = static_cast<size_t>(
96+
std::ceil(static_cast<double>(data_->field_of_view) / obstacles_angle_)) + 1;
97+
if (point_count > MAX_RANGE_DATA_POINTS) {
98+
RCLCPP_ERROR(
99+
logger_,
100+
"[%s]: Range data would generate %zu points, exceeding the limit of %zu. Ignoring...",
101+
source_name_.c_str(), point_count, MAX_RANGE_DATA_POINTS);
102+
return false;
103+
}
104+
92105
tf2::Transform tf_transform;
93106
if (!getTransform(curr_time, data_->header, tf_transform)) {
94107
return false;
@@ -139,10 +152,23 @@ void Range::getParameters(std::string & source_topic)
139152

140153
obstacles_angle_ = node->declare_or_get_parameter(
141154
source_name_ + ".obstacles_angle", M_PI / 180);
155+
156+
if (!std::isfinite(obstacles_angle_) || obstacles_angle_ <= 0.0) {
157+
throw std::runtime_error{
158+
"Range source " + source_name_ + " has invalid obstacles_angle parameter"};
159+
}
142160
}
143161

144162
void Range::dataCallback(sensor_msgs::msg::Range::ConstSharedPtr msg)
145163
{
164+
if (!nav2::validateMsg(*msg)) {
165+
RCLCPP_ERROR(
166+
logger_,
167+
"[%s]: Malformed range message. Rejecting...",
168+
source_name_.c_str());
169+
return;
170+
}
171+
146172
data_ = msg;
147173
}
148174

nav2_collision_monitor/test/sources_test.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,21 @@ class RangeWrapper : public nav2_collision_monitor::Range
368368
{
369369
return data_ != nullptr;
370370
}
371+
372+
void setData(sensor_msgs::msg::Range::ConstSharedPtr msg)
373+
{
374+
data_ = msg;
375+
}
376+
377+
void setObstaclesAngle(const double obstacles_angle)
378+
{
379+
obstacles_angle_ = obstacles_angle;
380+
}
381+
382+
void processData(sensor_msgs::msg::Range::ConstSharedPtr msg)
383+
{
384+
dataCallback(msg);
385+
}
371386
}; // RangeWrapper
372387

373388
class PolygonWrapper : public nav2_collision_monitor::PolygonSource
@@ -886,6 +901,94 @@ TEST_F(Tester, testIgnoreTimeShift)
886901
checkPolygon(data);
887902
}
888903

904+
TEST_F(Tester, testRangeGeneratedPointLimit)
905+
{
906+
rclcpp::Time curr_time = test_node_->now();
907+
908+
createSources(false);
909+
910+
auto msg = std::make_shared<sensor_msgs::msg::Range>();
911+
msg->header.frame_id = BASE_FRAME_ID;
912+
msg->header.stamp = curr_time;
913+
msg->radiation_type = sensor_msgs::msg::Range::ULTRASOUND;
914+
msg->field_of_view = M_PI;
915+
msg->min_range = 0.1;
916+
msg->max_range = 1.1;
917+
msg->range = 1.0;
918+
919+
range_->setData(msg);
920+
range_->setObstaclesAngle(M_PI / 1e5);
921+
922+
std::vector<nav2_collision_monitor::Point> data;
923+
EXPECT_FALSE(range_->getData(curr_time, data));
924+
EXPECT_TRUE(data.empty());
925+
926+
range_->setData(msg);
927+
range_->setObstaclesAngle(M_PI / 999.0);
928+
929+
data.clear();
930+
EXPECT_TRUE(range_->getData(curr_time, data));
931+
EXPECT_EQ(data.size(), 1000u);
932+
}
933+
934+
TEST_F(Tester, testRangeObstaclesAngleValidation)
935+
{
936+
const std::string valid_range_name = "ValidRange";
937+
938+
test_node_->declare_parameter(
939+
std::string(RANGE_NAME) + ".topic", rclcpp::ParameterValue(RANGE_TOPIC));
940+
test_node_->declare_parameter(
941+
std::string(RANGE_NAME) + ".obstacles_angle", rclcpp::ParameterValue(0.0));
942+
943+
range_ = std::make_shared<RangeWrapper>(
944+
test_node_, RANGE_NAME, tf_buffer_,
945+
BASE_FRAME_ID, GLOBAL_FRAME_ID,
946+
TRANSFORM_TOLERANCE, DATA_TIMEOUT, true);
947+
948+
EXPECT_THROW(range_->configure(), std::runtime_error);
949+
950+
test_node_->declare_parameter(
951+
valid_range_name + ".topic", rclcpp::ParameterValue(RANGE_TOPIC));
952+
test_node_->declare_parameter(
953+
valid_range_name + ".obstacles_angle", rclcpp::ParameterValue(M_PI / 180.0));
954+
range_ = std::make_shared<RangeWrapper>(
955+
test_node_, valid_range_name, tf_buffer_,
956+
BASE_FRAME_ID, GLOBAL_FRAME_ID,
957+
TRANSFORM_TOLERANCE, DATA_TIMEOUT, true);
958+
959+
EXPECT_NO_THROW(range_->configure());
960+
}
961+
962+
TEST_F(Tester, testRangeMessageValidation)
963+
{
964+
createSources();
965+
966+
auto msg = std::make_shared<sensor_msgs::msg::Range>();
967+
msg->header.frame_id = SOURCE_FRAME_ID;
968+
msg->header.stamp = test_node_->now();
969+
msg->radiation_type = sensor_msgs::msg::Range::ULTRASOUND;
970+
msg->field_of_view = 0.0;
971+
msg->min_range = 0.1;
972+
msg->max_range = 1.1;
973+
msg->range = 1.0;
974+
975+
range_->processData(msg);
976+
977+
EXPECT_FALSE(range_->dataReceived());
978+
979+
msg->header.frame_id = SOURCE_FRAME_ID;
980+
msg->header.stamp = test_node_->now();
981+
msg->radiation_type = sensor_msgs::msg::Range::ULTRASOUND;
982+
msg->field_of_view = M_PI / 10;
983+
msg->min_range = 0.1;
984+
msg->max_range = 1.1;
985+
msg->range = 1.0;
986+
987+
range_->processData(msg);
988+
989+
EXPECT_TRUE(range_->dataReceived());
990+
}
991+
889992
TEST_F(Tester, testPointCloudMinRange)
890993
{
891994
rclcpp::Time curr_time = test_node_->now();

nav2_ros_common/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ find_package(rcl_interfaces REQUIRED)
1515
find_package(rclcpp REQUIRED)
1616
find_package(rclcpp_action REQUIRED)
1717
find_package(rclcpp_lifecycle REQUIRED)
18+
find_package(sensor_msgs REQUIRED)
1819
find_package(std_msgs REQUIRED)
1920
find_package(tf2 REQUIRED)
2021
find_package(tf2_geometry_msgs REQUIRED)
@@ -39,6 +40,7 @@ target_link_libraries(${PROJECT_NAME} INTERFACE
3940
pluginlib::pluginlib
4041
rclcpp::rclcpp
4142
rclcpp_lifecycle::rclcpp_lifecycle
43+
${sensor_msgs_TARGETS}
4244
tf2_ros::tf2_ros
4345
rclcpp_action::rclcpp_action
4446
tf2_geometry_msgs::tf2_geometry_msgs
@@ -80,6 +82,7 @@ ament_export_dependencies(
8082
rclcpp
8183
rclcpp_action
8284
rclcpp_lifecycle
85+
sensor_msgs
8386
tf2
8487
tf2_geometry_msgs
8588
tf2_ros

nav2_ros_common/include/nav2_ros_common/validate_messages.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "nav_msgs/msg/odometry.hpp"
2525
#include "geometry_msgs/msg/pose_with_covariance_stamped.hpp"
2626
#include "map_msgs/msg/occupancy_grid_update.hpp"
27+
#include "sensor_msgs/msg/range.hpp"
2728

2829

2930
// @brief Validation Check
@@ -61,6 +62,7 @@ bool validateMsg(const double & num)
6162
const double MAX_COVARIANCE = 1e9;
6263
const double MIN_COVARIANCE = 0;
6364
const double MIN_MAP_RESOLUTION = 1e-6;
65+
const double MAX_RANGE_FIELD_OF_VIEW = M_PI;
6466

6567
template<size_t N>
6668
bool validateMsg(const std::array<double, N> & msg)
@@ -229,6 +231,25 @@ bool validateMsg(const map_msgs::msg::OccupancyGridUpdate & msg)
229231
return true;
230232
}
231233

234+
bool validateMsg(const sensor_msgs::msg::Range & msg)
235+
{
236+
if (!validateMsg(msg.header)) {return false;}
237+
if (!validateMsg(static_cast<double>(msg.field_of_view))) {return false;}
238+
if (!validateMsg(static_cast<double>(msg.min_range))) {return false;}
239+
if (!validateMsg(static_cast<double>(msg.max_range))) {return false;}
240+
if (!validateMsg(static_cast<double>(msg.range))) {return false;}
241+
242+
if (msg.field_of_view <= 0.0 || msg.field_of_view > MAX_RANGE_FIELD_OF_VIEW) {
243+
return false;
244+
}
245+
246+
if (msg.min_range < 0.0 || msg.max_range <= msg.min_range) {
247+
return false;
248+
}
249+
250+
return true;
251+
}
252+
232253
} // namespace nav2
233254

234255
#endif // NAV2_ROS_COMMON__VALIDATE_MESSAGES_HPP_

nav2_ros_common/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<depend>nav2_msgs</depend>
2121
<depend>nav_msgs</depend>
2222
<depend>map_msgs</depend>
23+
<depend>sensor_msgs</depend>
2324
<depend>rcl_interfaces</depend>
2425
<depend>rclcpp</depend>
2526
<depend>rclcpp_action</depend>

nav2_ros_common/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,5 +124,6 @@ target_link_libraries(test_validation_messages
124124
${tf2_geometry_msgs_TARGETS}
125125
pluginlib::pluginlib
126126
${builtin_interfaces_TARGETS}
127+
${sensor_msgs_TARGETS}
127128
${std_msgs_TARGETS}
128129
)

nav2_ros_common/test/test_validation_messages.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <limits>
1919

2020
#include "nav2_ros_common/validate_messages.hpp"
21+
#include "sensor_msgs/msg/range.hpp"
2122

2223
TEST(ValidateMessagesTest, DoubleValueCheck) {
2324
// Test valid double value
@@ -481,5 +482,51 @@ TEST(ValidateMessagesTest, PoseWithCovarianceStampedCheck) {
481482
EXPECT_FALSE(nav2::validateMsg(invalidate_msg2));
482483
}
483484

485+
TEST(ValidateMessagesTest, RangeCheck)
486+
{
487+
sensor_msgs::msg::Range valid_range;
488+
valid_range.header.frame_id = "range_sensor";
489+
valid_range.header.stamp.sec = 123;
490+
valid_range.header.stamp.nanosec = 456789;
491+
valid_range.radiation_type = sensor_msgs::msg::Range::ULTRASOUND;
492+
valid_range.field_of_view = M_PI / 2;
493+
valid_range.min_range = 0.1;
494+
valid_range.max_range = 2.0;
495+
valid_range.range = 1.0;
496+
EXPECT_TRUE(nav2::validateMsg(valid_range));
497+
498+
auto invalid_range = valid_range;
499+
invalid_range.field_of_view = std::numeric_limits<float>::quiet_NaN();
500+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
501+
502+
invalid_range = valid_range;
503+
invalid_range.field_of_view = std::numeric_limits<float>::infinity();
504+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
505+
506+
invalid_range = valid_range;
507+
invalid_range.field_of_view = 0.0;
508+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
509+
510+
invalid_range = valid_range;
511+
invalid_range.field_of_view = -0.1;
512+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
513+
514+
invalid_range = valid_range;
515+
invalid_range.field_of_view = static_cast<float>(M_PI + 0.1);
516+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
517+
518+
invalid_range = valid_range;
519+
invalid_range.range = std::numeric_limits<float>::quiet_NaN();
520+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
521+
522+
invalid_range = valid_range;
523+
invalid_range.min_range = -0.1;
524+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
525+
526+
invalid_range = valid_range;
527+
invalid_range.max_range = invalid_range.min_range;
528+
EXPECT_FALSE(nav2::validateMsg(invalid_range));
529+
}
530+
484531

485532
// Add more test cases for other validateMsg functions if needed

0 commit comments

Comments
 (0)