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