ROS 2 rclcpp + rcl - rolling  rolling-e7363f10
ROS 2 C++ Client Library with ROS Client Library
expand_topic_name.c
1 // Copyright 2017 Open Source Robotics Foundation, Inc.
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 #ifdef __cplusplus
16 extern "C"
17 {
18 #endif
19 
20 #include "rcl/expand_topic_name.h"
21 
22 #include <stdbool.h>
23 #include <string.h>
24 
25 #include "./common.h"
26 #include "rcl/allocator.h"
27 #include "rcl/error_handling.h"
28 #include "rcl/types.h"
30 #include "rcutils/error_handling.h"
31 #include "rcutils/format_string.h"
32 #include "rcutils/repl_str.h"
33 #include "rcutils/strdup.h"
34 #include "rcutils/types/string_map.h"
35 #include "rmw/error_handling.h"
36 #include "rmw/ret_types.h"
37 #include "rmw/validate_namespace.h"
38 #include "rmw/validate_node_name.h"
39 
40 // built-in substitution strings
41 #define SUBSTITUION_NODE_NAME "{node}"
42 #define SUBSTITUION_NAMESPACE "{ns}"
43 #define SUBSTITUION_NAMESPACE2 "{namespace}"
44 
47  const char * input_topic_name,
48  const char * node_name,
49  const char * node_namespace,
50  const rcutils_string_map_t * substitutions,
51  rcl_allocator_t allocator,
52  char ** output_topic_name)
53 {
54  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_INVALID_ARGUMENT);
55  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_TOPIC_NAME_INVALID);
56  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_NODE_INVALID_NAME);
57  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_NODE_INVALID_NAMESPACE);
58  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_UNKNOWN_SUBSTITUTION);
59  RCUTILS_CAN_SET_MSG_AND_RETURN_WITH_ERROR_OF(RCL_RET_BAD_ALLOC);
60 
61  // check arguments that could be null
62  RCL_CHECK_ARGUMENT_FOR_NULL(input_topic_name, RCL_RET_INVALID_ARGUMENT);
63  RCL_CHECK_ARGUMENT_FOR_NULL(node_name, RCL_RET_INVALID_ARGUMENT);
64  RCL_CHECK_ARGUMENT_FOR_NULL(node_namespace, RCL_RET_INVALID_ARGUMENT);
65  RCL_CHECK_ARGUMENT_FOR_NULL(substitutions, RCL_RET_INVALID_ARGUMENT);
66  RCL_CHECK_ARGUMENT_FOR_NULL(output_topic_name, RCL_RET_INVALID_ARGUMENT);
67  // validate the input topic
68  int validation_result;
69  rcl_ret_t ret = rcl_validate_topic_name(input_topic_name, &validation_result, NULL);
70  if (ret != RCL_RET_OK) {
71  // error message already set
72  return ret;
73  }
74  if (validation_result != RCL_TOPIC_NAME_VALID) {
75  RCL_SET_ERROR_MSG(rcl_topic_name_validation_result_string(validation_result));
77  }
78  // validate the node name
79  rmw_ret_t rmw_ret;
80  rmw_ret = rmw_validate_node_name(node_name, &validation_result, NULL);
81  if (rmw_ret != RMW_RET_OK) {
82  RCL_SET_ERROR_MSG(rmw_get_error_string().str);
83  return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
84  }
85  if (validation_result != RMW_NODE_NAME_VALID) {
86  RCL_SET_ERROR_MSG(rmw_node_name_validation_result_string(validation_result));
88  }
89  // validate the namespace
90  rmw_ret = rmw_validate_namespace(node_namespace, &validation_result, NULL);
91  if (rmw_ret != RMW_RET_OK) {
92  RCL_SET_ERROR_MSG(rmw_get_error_string().str);
93  return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
94  }
95  if (validation_result != RMW_NODE_NAME_VALID) {
96  RCL_SET_ERROR_MSG(rmw_namespace_validation_result_string(validation_result));
98  }
99  // check if the topic has substitutions to be made
100  bool has_a_substitution = strchr(input_topic_name, '{') != NULL;
101  bool has_a_namespace_tilde = input_topic_name[0] == '~';
102  bool is_absolute = input_topic_name[0] == '/';
103  // if absolute and doesn't have any substitution
104  if (is_absolute && !has_a_substitution) {
105  // nothing to do, duplicate and return
106  *output_topic_name = rcutils_strdup(input_topic_name, allocator);
107  if (!*output_topic_name) {
108  *output_topic_name = NULL;
109  rcl_reset_error();
110  RCL_SET_ERROR_MSG("failed to allocate memory for output topic");
111  return RCL_RET_BAD_ALLOC;
112  }
113  return RCL_RET_OK;
114  }
115  char * local_output = NULL;
116  // if has_a_namespace_tilde, replace that first
117  if (has_a_namespace_tilde) {
118  // special case where node_namespace is just '/'
119  // then no additional separating '/' is needed
120  const char * fmt = (strlen(node_namespace) == 1) ? "%s%s%s" : "%s/%s%s";
121  local_output =
122  rcutils_format_string(allocator, fmt, node_namespace, node_name, input_topic_name + 1);
123  if (!local_output) {
124  *output_topic_name = NULL;
125  RCL_SET_ERROR_MSG("failed to allocate memory for output topic");
126  return RCL_RET_BAD_ALLOC;
127  }
128  }
129  // if it has any substitutions, replace those
130  if (has_a_substitution) {
131  // Assumptions entering this scope about the topic string:
132  //
133  // - All {} are matched and balanced
134  // - There is no nesting, i.e. {{}}
135  // - There are no empty substitution substr, i.e. '{}' versus '{something}'
136  //
137  // These assumptions are taken because this is checked in the validation function.
138  const char * current_output = (local_output) ? local_output : input_topic_name;
139  const char * next_opening_brace = NULL;
140  // current_output may be replaced on each loop if a substitution is made
141  while ((next_opening_brace = strchr(current_output, '{')) != NULL) {
142  const char * next_closing_brace = strchr(current_output, '}');
143  // conclusion based on above assumptions: next_closing_brace - next_opening_brace > 1
144  size_t substitution_substr_len = next_closing_brace - next_opening_brace + 1;
145  // figure out what the replacement is for this substitution
146  const char * replacement = NULL;
147  if (strncmp(SUBSTITUION_NODE_NAME, next_opening_brace, substitution_substr_len) == 0) {
148  replacement = node_name;
149  } else if ( // NOLINT
150  strncmp(SUBSTITUION_NAMESPACE, next_opening_brace, substitution_substr_len) == 0 ||
151  strncmp(SUBSTITUION_NAMESPACE2, next_opening_brace, substitution_substr_len) == 0)
152  {
153  replacement = node_namespace;
154  } else {
155  replacement = rcutils_string_map_getn(
156  substitutions,
157  // compare {substitution}
158  // ^ until ^
159  next_opening_brace + 1, substitution_substr_len - 2);
160  if (!replacement) {
161  // in this case, it is neither node name nor ns nor in the substitutions map, so error
162  *output_topic_name = NULL;
163  char * unmatched_substitution =
164  rcutils_strndup(next_opening_brace, substitution_substr_len, allocator);
165  if (unmatched_substitution) {
166  RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
167  "unknown substitution: %s", unmatched_substitution);
168  } else {
169  RCUTILS_SAFE_FWRITE_TO_STDERR("failed to allocate memory for unmatched substitution\n");
170  }
171  allocator.deallocate(unmatched_substitution, allocator.state);
172  allocator.deallocate(local_output, allocator.state);
174  }
175  }
176  // at this point replacement will be set or an error would have returned out
177  // do the replacement
178  char * next_substitution =
179  rcutils_strndup(next_opening_brace, substitution_substr_len, allocator);
180  if (!next_substitution) {
181  *output_topic_name = NULL;
182  rcl_reset_error();
183  RCL_SET_ERROR_MSG("failed to allocate memory for substitution");
184  allocator.deallocate(local_output, allocator.state);
185  return RCL_RET_BAD_ALLOC;
186  }
187  char * original_local_output = local_output;
188  local_output = rcutils_repl_str(current_output, next_substitution, replacement, &allocator);
189  allocator.deallocate(next_substitution, allocator.state); // free no matter what
190  allocator.deallocate(original_local_output, allocator.state); // free no matter what
191  if (!local_output) {
192  *output_topic_name = NULL;
193  rcl_reset_error();
194  RCL_SET_ERROR_MSG("failed to allocate memory for expanded topic");
195  return RCL_RET_BAD_ALLOC;
196  }
197  current_output = local_output;
198  // loop until all substitutions are replaced
199  } // while
200  }
201  // finally make the name absolute if it isn't already
202  if (
203  (local_output && local_output[0] != '/') ||
204  (!local_output && input_topic_name[0] != '/'))
205  {
206  char * original_local_output = local_output;
207  // special case where node_namespace is just '/'
208  // then no additional separating '/' is needed
209  const char * fmt = (strlen(node_namespace) == 1) ? "%s%s" : "%s/%s";
210  local_output = rcutils_format_string(
211  allocator, fmt, node_namespace, (local_output) ? local_output : input_topic_name);
212  if (original_local_output) {
213  allocator.deallocate(original_local_output, allocator.state);
214  }
215  if (!local_output) {
216  *output_topic_name = NULL;
217  RCL_SET_ERROR_MSG("failed to allocate memory for output topic");
218  return RCL_RET_BAD_ALLOC;
219  }
220  }
221  // finally store the result in the out pointer and return
222  *output_topic_name = local_output;
223  return RCL_RET_OK;
224 }
225 
226 rcl_ret_t
227 rcl_get_default_topic_name_substitutions(rcutils_string_map_t * string_map)
228 {
229  RCL_CHECK_ARGUMENT_FOR_NULL(string_map, RCL_RET_INVALID_ARGUMENT);
230 
231  // right now there are no default substitutions
232 
233  return RCL_RET_OK;
234 }
235 
236 #ifdef __cplusplus
237 }
238 #endif
rcutils_allocator_t rcl_allocator_t
Encapsulation of an allocator.
Definition: allocator.h:31
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_expand_topic_name(const char *input_topic_name, const char *node_name, const char *node_namespace, const rcutils_string_map_t *substitutions, rcl_allocator_t allocator, char **output_topic_name)
Expand a given topic name into a fully-qualified topic name.
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_get_default_topic_name_substitutions(rcutils_string_map_t *string_map)
Fill a given string map with the default substitution pairs.
#define RCL_RET_NODE_INVALID_NAMESPACE
Invalid node namespace return code.
Definition: types.h:64
#define RCL_RET_UNKNOWN_SUBSTITUTION
Topic name substitution is unknown.
Definition: types.h:52
#define RCL_RET_OK
Success return code.
Definition: types.h:28
#define RCL_RET_BAD_ALLOC
Failed to allocate memory return code.
Definition: types.h:34
#define RCL_RET_INVALID_ARGUMENT
Invalid argument return code.
Definition: types.h:36
#define RCL_RET_TOPIC_NAME_INVALID
Topic name does not pass validation.
Definition: types.h:48
#define RCL_RET_NODE_INVALID_NAME
Invalid node name return code.
Definition: types.h:62
rmw_ret_t rcl_ret_t
The type that holds an rcl return code.
Definition: types.h:25
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_validate_topic_name(const char *topic_name, int *validation_result, size_t *invalid_index)
Validate a given topic name.
#define RCL_TOPIC_NAME_VALID
The topic name is valid.
RCL_PUBLIC RCL_WARN_UNUSED const char * rcl_topic_name_validation_result_string(int validation_result)
Return a validation result description, or NULL if unknown or RCL_TOPIC_NAME_VALID.