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