ROS 2 rclcpp + rcl - jazzy  jazzy
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_SET_ERROR_MSG("failed to allocate memory for output topic");
108  return RCL_RET_BAD_ALLOC;
109  }
110  return RCL_RET_OK;
111  }
112  char * local_output = NULL;
113  // if has_a_namespace_tilde, replace that first
114  if (has_a_namespace_tilde) {
115  // special case where node_namespace is just '/'
116  // then no additional separating '/' is needed
117  const char * fmt = (strlen(node_namespace) == 1) ? "%s%s%s" : "%s/%s%s";
118  local_output =
119  rcutils_format_string(allocator, fmt, node_namespace, node_name, input_topic_name + 1);
120  if (!local_output) {
121  *output_topic_name = NULL;
122  RCL_SET_ERROR_MSG("failed to allocate memory for output topic");
123  return RCL_RET_BAD_ALLOC;
124  }
125  }
126  // if it has any substitutions, replace those
127  if (has_a_substitution) {
128  // Assumptions entering this scope about the topic string:
129  //
130  // - All {} are matched and balanced
131  // - There is no nesting, i.e. {{}}
132  // - There are no empty substitution substr, i.e. '{}' versus '{something}'
133  //
134  // These assumptions are taken because this is checked in the validation function.
135  const char * current_output = (local_output) ? local_output : input_topic_name;
136  char * next_opening_brace = NULL;
137  // current_output may be replaced on each loop if a substitution is made
138  while ((next_opening_brace = strchr(current_output, '{')) != NULL) {
139  char * next_closing_brace = strchr(current_output, '}');
140  // conclusion based on above assumptions: next_closing_brace - next_opening_brace > 1
141  size_t substitution_substr_len = next_closing_brace - next_opening_brace + 1;
142  // figure out what the replacement is for this substitution
143  const char * replacement = NULL;
144  if (strncmp(SUBSTITUION_NODE_NAME, next_opening_brace, substitution_substr_len) == 0) {
145  replacement = node_name;
146  } else if ( // NOLINT
147  strncmp(SUBSTITUION_NAMESPACE, next_opening_brace, substitution_substr_len) == 0 ||
148  strncmp(SUBSTITUION_NAMESPACE2, next_opening_brace, substitution_substr_len) == 0)
149  {
150  replacement = node_namespace;
151  } else {
152  replacement = rcutils_string_map_getn(
153  substitutions,
154  // compare {substitution}
155  // ^ until ^
156  next_opening_brace + 1, substitution_substr_len - 2);
157  if (!replacement) {
158  // in this case, it is neither node name nor ns nor in the substitutions map, so error
159  *output_topic_name = NULL;
160  char * unmatched_substitution =
161  rcutils_strndup(next_opening_brace, substitution_substr_len, allocator);
162  if (unmatched_substitution) {
163  RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
164  "unknown substitution: %s", unmatched_substitution);
165  } else {
166  RCUTILS_SAFE_FWRITE_TO_STDERR("failed to allocate memory for unmatched substitution\n");
167  }
168  allocator.deallocate(unmatched_substitution, allocator.state);
169  allocator.deallocate(local_output, allocator.state);
171  }
172  }
173  // at this point replacement will be set or an error would have returned out
174  // do the replacement
175  char * next_substitution =
176  rcutils_strndup(next_opening_brace, substitution_substr_len, allocator);
177  if (!next_substitution) {
178  *output_topic_name = NULL;
179  RCL_SET_ERROR_MSG("failed to allocate memory for substitution");
180  allocator.deallocate(local_output, allocator.state);
181  return RCL_RET_BAD_ALLOC;
182  }
183  char * original_local_output = local_output;
184  local_output = rcutils_repl_str(current_output, next_substitution, replacement, &allocator);
185  allocator.deallocate(next_substitution, allocator.state); // free no matter what
186  allocator.deallocate(original_local_output, allocator.state); // free no matter what
187  if (!local_output) {
188  *output_topic_name = NULL;
189  RCL_SET_ERROR_MSG("failed to allocate memory for expanded topic");
190  return RCL_RET_BAD_ALLOC;
191  }
192  current_output = local_output;
193  // loop until all substitutions are replaced
194  } // while
195  }
196  // finally make the name absolute if it isn't already
197  if (
198  (local_output && local_output[0] != '/') ||
199  (!local_output && input_topic_name[0] != '/'))
200  {
201  char * original_local_output = local_output;
202  // special case where node_namespace is just '/'
203  // then no additional separating '/' is needed
204  const char * fmt = (strlen(node_namespace) == 1) ? "%s%s" : "%s/%s";
205  local_output = rcutils_format_string(
206  allocator, fmt, node_namespace, (local_output) ? local_output : input_topic_name);
207  if (original_local_output) {
208  allocator.deallocate(original_local_output, allocator.state);
209  }
210  if (!local_output) {
211  *output_topic_name = NULL;
212  RCL_SET_ERROR_MSG("failed to allocate memory for output topic");
213  return RCL_RET_BAD_ALLOC;
214  }
215  }
216  // finally store the result in the out pointer and return
217  *output_topic_name = local_output;
218  return RCL_RET_OK;
219 }
220 
221 rcl_ret_t
222 rcl_get_default_topic_name_substitutions(rcutils_string_map_t * string_map)
223 {
224  RCL_CHECK_ARGUMENT_FOR_NULL(string_map, RCL_RET_INVALID_ARGUMENT);
225 
226  // right now there are no default substitutions
227 
228  return RCL_RET_OK;
229 }
230 
231 #ifdef __cplusplus
232 }
233 #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.