Nav2 Navigation Stack - rolling  main
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 "nav2_msgs/msg/costmap.hpp"
26 #include "nav2_msgs/msg/costmap_meta_data.hpp"
27 #include "geometry_msgs/msg/pose_with_covariance_stamped.hpp"
28 #include "map_msgs/msg/occupancy_grid_update.hpp"
29 #include "sensor_msgs/msg/range.hpp"
30 
31 
32 // @brief Validation Check
33 // Check received message is safe or not for the nav2-system
34 // For each msg-type known in nav2, we could check it as following:
35 // if(!validateMsg()) RCLCPP_ERROR(,"malformed msg. Rejecting.")
36 //
37 // Workflow of validateMsg():
38 // if here's a sub-msg-type in the received msg,
39 // the content of sub-msg would be checked as sub-msg-type
40 // then, check the whole received msg.
41 //
42 // Following conditions are involved in check:
43 // 1> Value Check: to avoid damaged value like like `nan`, `INF`, empty string and so on
44 // 2> Logic Check: to avoid value with bad logic,
45 // like the size of `map` should be equal to `height*width`
46 // 3> Any other needed condition could be joint here in future
47 
48 namespace nav2
49 {
50 
51 
52 bool validateMsg(const double & num)
53 {
54  /* @brief double/float value check
55  * if here'a need to check message validation
56  * it should be avoid to use double value like `nan`, `inf`
57  * otherwise, we regard it as an invalid message
58  */
59  if (std::isinf(num)) {return false;}
60  if (std::isnan(num)) {return false;}
61  return true;
62 }
63 
64 const double MAX_COVARIANCE = 1e9;
65 const double MIN_COVARIANCE = 0;
66 const double MIN_MAP_RESOLUTION = 1e-6;
67 const double MAX_RANGE_FIELD_OF_VIEW = M_PI;
68 
69 template<size_t N>
70 bool validateMsg(const std::array<double, N> & msg)
71 {
72  /* @brief value check for double-array
73  * like the field `covariance` used in the msg-type:
74  * geometry_msgs::msg::PoseWithCovarianceStamped
75  */
76  for (const auto & element : msg) {
77  if (!validateMsg(element)) {return false;}
78 
79  if (std::abs(element) > MAX_COVARIANCE || element < MIN_COVARIANCE) {
80  return false;
81  }
82  }
83 
84  return true;
85 }
86 
87 const int NSEC_PER_SEC = 1e9; // 1 second = 1e9 nanosecond
88 bool validateMsg(const builtin_interfaces::msg::Time & msg)
89 {
90  if (msg.nanosec >= NSEC_PER_SEC) {
91  return false; // invalid nanosec-stamp
92  }
93  return true;
94 }
95 
96 bool validateMsg(const std_msgs::msg::Header & msg)
97 {
98  // check sub-type
99  if (!validateMsg(msg.stamp)) {return false;}
100 
101  /* @brief frame_id check
102  * if here'a need to check message validation
103  * it should at least have a non-empty frame_id
104  * otherwise, we regard it as an invalid message
105  */
106  if (msg.frame_id.empty()) {return false;}
107  return true;
108 }
109 
110 bool validateMsg(const geometry_msgs::msg::Point & msg)
111 {
112  // check sub-type
113  if (!validateMsg(msg.x)) {return false;}
114  if (!validateMsg(msg.y)) {return false;}
115  if (!validateMsg(msg.z)) {return false;}
116  return true;
117 }
118 
119 const double epsilon = 1e-4;
120 bool validateMsg(const geometry_msgs::msg::Quaternion & msg)
121 {
122  // check sub-type
123  if (!validateMsg(msg.x)) {return false;}
124  if (!validateMsg(msg.y)) {return false;}
125  if (!validateMsg(msg.z)) {return false;}
126  if (!validateMsg(msg.w)) {return false;}
127 
128  if (abs(msg.x * msg.x + msg.y * msg.y + msg.z * msg.z + msg.w * msg.w - 1.0) >= epsilon) {
129  return false;
130  }
131 
132  return true;
133 }
134 
135 bool validateMsg(const geometry_msgs::msg::Pose & msg)
136 {
137  // check sub-type
138  if (!validateMsg(msg.position)) {return false;}
139  if (!validateMsg(msg.orientation)) {return false;}
140  return true;
141 }
142 
143 bool validateMsg(const geometry_msgs::msg::PoseWithCovariance & msg)
144 {
145  // check sub-type
146  if (!validateMsg(msg.pose)) {return false;}
147  if (!validateMsg(msg.covariance)) {return false;}
148 
149  return true;
150 }
151 
152 bool validateMsg(const geometry_msgs::msg::PoseWithCovarianceStamped & msg)
153 {
154  // check sub-type
155  if (!validateMsg(msg.header)) {return false;}
156  if (!validateMsg(msg.pose)) {return false;}
157  if (!validateMsg(msg.pose.covariance)) {return false;}
158  return true;
159 }
160 
161 
162 // Function to verify map meta information
163 bool validateMsg(const nav_msgs::msg::MapMetaData & msg)
164 {
165  // check sub-type
166  if (!validateMsg(msg.origin)) {return false;}
167  if (!validateMsg(msg.resolution)) {return false;}
168 
169  if (msg.resolution < MIN_MAP_RESOLUTION) {return false;}
170 
171  // logic check
172  // 1> we don't need an empty map
173  if (msg.height == 0 || msg.width == 0) {return false;}
174  return true;
175 }
176 
177 // for msg-type like map, costmap and others as `OccupancyGrid`
178 bool validateMsg(const nav_msgs::msg::OccupancyGrid & msg)
179 {
180  // check sub-type
181  if (!validateMsg(msg.header)) {return false;}
182  // msg.data : @todo any check for it ?
183  if (!validateMsg(msg.info)) {return false;}
184 
185  if (msg.info.width > INT16_MAX || msg.info.height > INT16_MAX) {
186  // avoid overflow in nav2_amcl::convertMap()
187  // because map_t size_x and size_y are int
188  return false;
189  }
190 
191  uint32_t num_cells;
192  if (__builtin_mul_overflow(msg.info.width, msg.info.height, &num_cells)) {
193  // avoid overflow msg.info.width * msg.info.height in nav2_amcl::convertMap()
194  return false;
195  }
196 
197  // check logic
198  if (msg.data.size() != static_cast<size_t>(num_cells)) {
199  return false; // check map-size
200  }
201 
202  return true;
203 }
204 
205 // for partial map updates as `OccupancyGridUpdate`
206 bool validateMsg(const map_msgs::msg::OccupancyGridUpdate & msg)
207 {
208  // check sub-type
209  if (!validateMsg(msg.header)) {return false;}
210 
211  // check logic
212  if (msg.data.size() != static_cast<size_t>(msg.width) * msg.height) {
213  return false; // check update-size
214  }
215 
216  // value check: x/y are int32, negative origin is invalid
217  if (msg.x < 0 || msg.y < 0) {return false;}
218 
219  if (msg.x > INT16_MAX || msg.y > INT16_MAX ||
220  msg.width > INT16_MAX || msg.height > INT16_MAX)
221  {
222  // avoid integer overflow in StaticLayer::incomingUpdate()
223  return false;
224  }
225 
226  uint32_t num_cells;
227  if (__builtin_mul_overflow(msg.width, msg.height, &num_cells)) {
228  // avoid overflow msg.width * msg.height in StaticLayer::incomingUpdate()
229  return false;
230  }
231 
232  return true;
233 }
234 
235 bool validateMsg(const sensor_msgs::msg::Range & msg)
236 {
237  if (!validateMsg(msg.header)) {return false;}
238  if (!validateMsg(static_cast<double>(msg.field_of_view))) {return false;}
239  if (!validateMsg(static_cast<double>(msg.min_range))) {return false;}
240  if (!validateMsg(static_cast<double>(msg.max_range))) {return false;}
241  if (!validateMsg(static_cast<double>(msg.range))) {return false;}
242 
243  if (msg.field_of_view <= 0.0 || msg.field_of_view > MAX_RANGE_FIELD_OF_VIEW) {
244  return false;
245  }
246 
247  if (msg.min_range < 0.0 || msg.max_range <= msg.min_range) {
248  return false;
249  }
250 
251  return true;
252 }
253 
254 bool validateMsg(const nav2_msgs::msg::CostmapMetaData & msg)
255 {
256  if (!validateMsg(msg.origin)) {return false;}
257  if (!validateMsg(msg.resolution)) {return false;}
258 
259  if (msg.resolution < MIN_MAP_RESOLUTION) {return false;}
260  if (msg.size_x == 0 || msg.size_y == 0) {return false;}
261 
262  return true;
263 }
264 
265 bool validateMsg(const nav2_msgs::msg::Costmap & msg)
266 {
267  if (!validateMsg(msg.header)) {return false;}
268  if (!validateMsg(msg.metadata)) {return false;}
269 
270  uint32_t num_cells;
271  if (__builtin_mul_overflow(msg.metadata.size_x, msg.metadata.size_y, &num_cells)) {
272  return false;
273  }
274 
275  if (msg.data.size() != static_cast<size_t>(num_cells)) {
276  return false;
277  }
278 
279  return true;
280 }
281 
282 } // namespace nav2
283 
284 #endif // NAV2_ROS_COMMON__VALIDATE_MESSAGES_HPP_