Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
validate_messages.hpp
1 // Copyright (c) 2024 GoesM
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 
16 #ifndef NAV2_ROS_COMMON__VALIDATE_MESSAGES_HPP_
17 #define NAV2_ROS_COMMON__VALIDATE_MESSAGES_HPP_
18 
19 #include <array>
20 #include <cmath>
21 #include <iostream>
22 
23 #include "nav_msgs/msg/occupancy_grid.hpp"
24 #include "nav_msgs/msg/odometry.hpp"
25 #include "geometry_msgs/msg/pose_with_covariance_stamped.hpp"
26 #include "map_msgs/msg/occupancy_grid_update.hpp"
27 #include "sensor_msgs/msg/range.hpp"
28 
29 
30 // @brief Validation Check
31 // Check received message is safe or not for the nav2-system
32 // For each msg-type known in nav2, we could check it as following:
33 // if(!validateMsg()) RCLCPP_ERROR(,"malformed msg. Rejecting.")
34 //
35 // Workflow of validateMsg():
36 // if here's a sub-msg-type in the received msg,
37 // the content of sub-msg would be checked as sub-msg-type
38 // then, check the whole received msg.
39 //
40 // Following conditions are involved in check:
41 // 1> Value Check: to avoid damaged value like like `nan`, `INF`, empty string and so on
42 // 2> Logic Check: to avoid value with bad logic,
43 // like the size of `map` should be equal to `height*width`
44 // 3> Any other needed condition could be joint here in future
45 
46 namespace nav2
47 {
48 
49 
50 bool validateMsg(const double & num)
51 {
52  /* @brief double/float value check
53  * if here'a need to check message validation
54  * it should be avoid to use double value like `nan`, `inf`
55  * otherwise, we regard it as an invalid message
56  */
57  if (std::isinf(num)) {return false;}
58  if (std::isnan(num)) {return false;}
59  return true;
60 }
61 
62 const double MAX_COVARIANCE = 1e9;
63 const double MIN_COVARIANCE = 0;
64 const double MIN_MAP_RESOLUTION = 1e-6;
65 const double MAX_RANGE_FIELD_OF_VIEW = M_PI;
66 
67 template<size_t N>
68 bool validateMsg(const std::array<double, N> & msg)
69 {
70  /* @brief value check for double-array
71  * like the field `covariance` used in the msg-type:
72  * geometry_msgs::msg::PoseWithCovarianceStamped
73  */
74  for (const auto & element : msg) {
75  if (!validateMsg(element)) {return false;}
76 
77  if (std::abs(element) > MAX_COVARIANCE || element < MIN_COVARIANCE) {
78  return false;
79  }
80  }
81 
82  return true;
83 }
84 
85 const int NSEC_PER_SEC = 1e9; // 1 second = 1e9 nanosecond
86 bool validateMsg(const builtin_interfaces::msg::Time & msg)
87 {
88  if (msg.nanosec >= NSEC_PER_SEC) {
89  return false; // invalid nanosec-stamp
90  }
91  return true;
92 }
93 
94 bool validateMsg(const std_msgs::msg::Header & msg)
95 {
96  // check sub-type
97  if (!validateMsg(msg.stamp)) {return false;}
98 
99  /* @brief frame_id check
100  * if here'a need to check message validation
101  * it should at least have a non-empty frame_id
102  * otherwise, we regard it as an invalid message
103  */
104  if (msg.frame_id.empty()) {return false;}
105  return true;
106 }
107 
108 bool validateMsg(const geometry_msgs::msg::Point & msg)
109 {
110  // check sub-type
111  if (!validateMsg(msg.x)) {return false;}
112  if (!validateMsg(msg.y)) {return false;}
113  if (!validateMsg(msg.z)) {return false;}
114  return true;
115 }
116 
117 const double epsilon = 1e-4;
118 bool validateMsg(const geometry_msgs::msg::Quaternion & msg)
119 {
120  // check sub-type
121  if (!validateMsg(msg.x)) {return false;}
122  if (!validateMsg(msg.y)) {return false;}
123  if (!validateMsg(msg.z)) {return false;}
124  if (!validateMsg(msg.w)) {return false;}
125 
126  if (abs(msg.x * msg.x + msg.y * msg.y + msg.z * msg.z + msg.w * msg.w - 1.0) >= epsilon) {
127  return false;
128  }
129 
130  return true;
131 }
132 
133 bool validateMsg(const geometry_msgs::msg::Pose & msg)
134 {
135  // check sub-type
136  if (!validateMsg(msg.position)) {return false;}
137  if (!validateMsg(msg.orientation)) {return false;}
138  return true;
139 }
140 
141 bool validateMsg(const geometry_msgs::msg::PoseWithCovariance & msg)
142 {
143  // check sub-type
144  if (!validateMsg(msg.pose)) {return false;}
145  if (!validateMsg(msg.covariance)) {return false;}
146 
147  return true;
148 }
149 
150 bool validateMsg(const geometry_msgs::msg::PoseWithCovarianceStamped & msg)
151 {
152  // check sub-type
153  if (!validateMsg(msg.header)) {return false;}
154  if (!validateMsg(msg.pose)) {return false;}
155  if (!validateMsg(msg.pose.covariance)) {return false;}
156  return true;
157 }
158 
159 
160 // Function to verify map meta information
161 bool validateMsg(const nav_msgs::msg::MapMetaData & msg)
162 {
163  // check sub-type
164  if (!validateMsg(msg.origin)) {return false;}
165  if (!validateMsg(msg.resolution)) {return false;}
166 
167  if (msg.resolution < MIN_MAP_RESOLUTION) {return false;}
168 
169  // logic check
170  // 1> we don't need an empty map
171  if (msg.height == 0 || msg.width == 0) {return false;}
172  return true;
173 }
174 
175 // for msg-type like map, costmap and others as `OccupancyGrid`
176 bool validateMsg(const nav_msgs::msg::OccupancyGrid & msg)
177 {
178  // check sub-type
179  if (!validateMsg(msg.header)) {return false;}
180  // msg.data : @todo any check for it ?
181  if (!validateMsg(msg.info)) {return false;}
182 
183  if (msg.info.width > INT16_MAX || msg.info.height > INT16_MAX) {
184  // avoid overflow in nav2_amcl::convertMap()
185  // because map_t size_x and size_y are int
186  return false;
187  }
188 
189  uint32_t num_cells;
190  if (__builtin_mul_overflow(msg.info.width, msg.info.height, &num_cells)) {
191  // avoid overflow msg.info.width * msg.info.height in nav2_amcl::convertMap()
192  return false;
193  }
194 
195  // check logic
196  if (msg.data.size() != static_cast<size_t>(num_cells)) {
197  return false; // check map-size
198  }
199 
200  return true;
201 }
202 
203 // for partial map updates as `OccupancyGridUpdate`
204 bool validateMsg(const map_msgs::msg::OccupancyGridUpdate & msg)
205 {
206  // check sub-type
207  if (!validateMsg(msg.header)) {return false;}
208 
209  // check logic
210  if (msg.data.size() != static_cast<size_t>(msg.width) * msg.height) {
211  return false; // check update-size
212  }
213 
214  // value check: x/y are int32, negative origin is invalid
215  if (msg.x < 0 || msg.y < 0) {return false;}
216 
217  if (msg.x > INT16_MAX || msg.y > INT16_MAX ||
218  msg.width > INT16_MAX || msg.height > INT16_MAX)
219  {
220  // avoid integer overflow in StaticLayer::incomingUpdate()
221  return false;
222  }
223 
224  uint32_t num_cells;
225  if (__builtin_mul_overflow(msg.width, msg.height, &num_cells)) {
226  // avoid overflow msg.width * msg.height in StaticLayer::incomingUpdate()
227  return false;
228  }
229 
230  return true;
231 }
232 
233 bool validateMsg(const sensor_msgs::msg::Range & msg)
234 {
235  if (!validateMsg(msg.header)) {return false;}
236  if (!validateMsg(static_cast<double>(msg.field_of_view))) {return false;}
237  if (!validateMsg(static_cast<double>(msg.min_range))) {return false;}
238  if (!validateMsg(static_cast<double>(msg.max_range))) {return false;}
239  if (!validateMsg(static_cast<double>(msg.range))) {return false;}
240 
241  if (msg.field_of_view <= 0.0 || msg.field_of_view > MAX_RANGE_FIELD_OF_VIEW) {
242  return false;
243  }
244 
245  if (msg.min_range < 0.0 || msg.max_range <= msg.min_range) {
246  return false;
247  }
248 
249  return true;
250 }
251 
252 } // namespace nav2
253 
254 #endif // NAV2_ROS_COMMON__VALIDATE_MESSAGES_HPP_