ROS 2 rclcpp + rcl - rolling  rolling-e7363f10
ROS 2 C++ Client Library with ROS Client Library
validate_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 
21 
22 #include <ctype.h>
23 #include <stdbool.h>
24 #include <string.h>
25 
26 #include "rcl/error_handling.h"
27 #include "rcl/types.h"
28 #include "rcutils/isalnum_no_locale.h"
29 
32  const char * topic_name,
33  int * validation_result,
34  size_t * invalid_index)
35 {
36  RCL_CHECK_ARGUMENT_FOR_NULL(topic_name, RCL_RET_INVALID_ARGUMENT);
38  topic_name, strlen(topic_name), validation_result, invalid_index);
39 }
40 
43  const char * topic_name,
44  size_t topic_name_length,
45  int * validation_result,
46  size_t * invalid_index)
47 {
48  RCL_CHECK_ARGUMENT_FOR_NULL(topic_name, RCL_RET_INVALID_ARGUMENT);
49  RCL_CHECK_ARGUMENT_FOR_NULL(validation_result, RCL_RET_INVALID_ARGUMENT);
50 
51  if (topic_name_length == 0) {
52  *validation_result = RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING;
53  if (invalid_index) {
54  *invalid_index = 0;
55  }
56  return RCL_RET_OK;
57  }
58  // check that the first character is not a number
59  if (isdigit(topic_name[0]) != 0) {
60  // this is the case where the topic is relative and the first token starts with a number
61  // e.g. 7foo/bar is invalid
63  if (invalid_index) {
64  *invalid_index = 0;
65  }
66  return RCL_RET_OK;
67  }
68  // note topic_name_length is >= 1 at this point
69  if (topic_name[topic_name_length - 1] == '/') {
70  // catches both "/foo/" and "/"
72  if (invalid_index) {
73  *invalid_index = topic_name_length - 1;
74  }
75  return RCL_RET_OK;
76  }
77  // check for unallowed characters, nested and unmatched {} too
78  bool in_open_curly_brace = false;
79  size_t opening_curly_brace_index = 0;
80  for (size_t i = 0; i < topic_name_length; ++i) {
81  if (rcutils_isalnum_no_locale(topic_name[i])) {
82  // if within curly braces and the first character is a number, error
83  // e.g. foo/{4bar} is invalid
84  if (
85  isdigit(topic_name[i]) != 0 &&
86  in_open_curly_brace &&
87  i > 0 &&
88  (i - 1 == opening_curly_brace_index))
89  {
91  if (invalid_index) {
92  *invalid_index = i;
93  }
94  return RCL_RET_OK;
95  }
96  // if it is an alpha numeric character, i.e. [0-9|A-Z|a-z], continue
97  continue;
98  } else if (topic_name[i] == '_') {
99  // if it is an underscore, continue
100  continue;
101  } else if (topic_name[i] == '/') {
102  // if it is a forward slash within {}, error
103  if (in_open_curly_brace) {
105  if (invalid_index) {
106  *invalid_index = i;
107  }
108  return RCL_RET_OK;
109  }
110  // if it is a forward slash outside of {}, continue
111  continue;
112  } else if (topic_name[i] == '~') {
113  // if it is a tilde not in the first position, validation fails
114  if (i != 0) {
115  *validation_result = RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE;
116  if (invalid_index) {
117  *invalid_index = i;
118  }
119  return RCL_RET_OK;
120  }
121  // if it is a tilde in the first position, continue
122  continue;
123  } else if (topic_name[i] == '{') {
124  opening_curly_brace_index = i;
125  // if starting a nested curly brace, error
126  // e.g. foo/{{bar}_baz} is invalid
127  // ^
128  if (in_open_curly_brace) {
130  if (invalid_index) {
131  *invalid_index = i;
132  }
133  return RCL_RET_OK;
134  }
135  in_open_curly_brace = true;
136  // if it is a new, open curly brace, continue
137  continue;
138  } else if (topic_name[i] == '}') {
139  // if not preceded by a {, error
140  if (!in_open_curly_brace) {
142  if (invalid_index) {
143  *invalid_index = i;
144  }
145  return RCL_RET_OK;
146  }
147  in_open_curly_brace = false;
148  // if it is a closing curly brace, continue
149  continue;
150  } else {
151  // if it is none of these, then it is an unallowed character in a topic name
152  if (in_open_curly_brace) {
154  } else {
156  }
157  if (invalid_index) {
158  *invalid_index = i;
159  }
160  return RCL_RET_OK;
161  }
162  }
163  // check to make sure substitutions were properly closed
164  if (in_open_curly_brace) {
165  // case where a substitution is never closed, e.g. 'foo/{bar'
167  if (invalid_index) {
168  *invalid_index = opening_curly_brace_index;
169  }
170  return RCL_RET_OK;
171  }
172  // check for tokens (other than the first) that start with a number
173  for (size_t i = 0; i < topic_name_length; ++i) {
174  if (i == topic_name_length - 1) {
175  // if this is the last character, then nothing to check
176  continue;
177  }
178  // past this point, assuming i+1 is a valid index
179  if (topic_name[i] == '/') {
180  if (isdigit(topic_name[i + 1]) != 0) {
181  // this is the case where a '/' if followed by a number, i.e. [0-9]
183  if (invalid_index) {
184  *invalid_index = i + 1;
185  }
186  return RCL_RET_OK;
187  }
188  } else if (i == 1 && topic_name[0] == '~') {
189  // special case where first character is ~ but second character is not /
190  // e.g. ~foo is invalid
192  if (invalid_index) {
193  *invalid_index = 1;
194  }
195  return RCL_RET_OK;
196  }
197  }
198  // everything was ok, set result to valid topic, avoid setting invalid_index, and return
199  *validation_result = RCL_TOPIC_NAME_VALID;
200  return RCL_RET_OK;
201 }
202 
203 const char *
205 {
206  switch (validation_result) {
208  return NULL;
210  return "topic name must not be empty string";
212  return "topic name must not end with a forward slash";
214  return
215  "topic name must not contain characters other than alphanumerics, '_', '~', '{', or '}'";
217  return "topic name token must not start with a number";
219  return "topic name must not have unmatched (unbalanced) curly braces '{}'";
221  return "topic name must not have tilde '~' unless it is the first character";
223  return "topic name must not have a tilde '~' that is not followed by a forward slash '/'";
225  return "substitution name must not contain characters other than alphanumerics or '_'";
227  return "substitution name must not start with a number";
228  default:
229  return "unknown result code for rcl topic name validation";
230  }
231 }
232 
233 #ifdef __cplusplus
234 }
235 #endif
#define RCL_RET_OK
Success return code.
Definition: types.h:28
#define RCL_RET_INVALID_ARGUMENT
Invalid argument return code.
Definition: types.h:36
rmw_ret_t rcl_ret_t
The type that holds an rcl return code.
Definition: types.h:25
#define RCL_TOPIC_NAME_INVALID_ENDS_WITH_FORWARD_SLASH
The topic name is invalid because it ends with a forward slash.
#define RCL_TOPIC_NAME_INVALID_MISPLACED_TILDE
The topic name is invalid because it has a misplaced tilde in it.
#define RCL_TOPIC_NAME_INVALID_NAME_TOKEN_STARTS_WITH_NUMBER
The topic name is invalid because one of the tokens starts with a number.
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_INVALID_CONTAINS_UNALLOWED_CHARACTERS
The topic name is invalid because it has characters that are not allowed.
#define RCL_TOPIC_NAME_INVALID_IS_EMPTY_STRING
The topic name is invalid because it is an empty string.
#define RCL_TOPIC_NAME_INVALID_TILDE_NOT_FOLLOWED_BY_FORWARD_SLASH
The topic name is invalid because a tilde is not directly followed by a forward slash.
#define RCL_TOPIC_NAME_INVALID_SUBSTITUTION_CONTAINS_UNALLOWED_CHARACTERS
The topic name is invalid because one of the substitutions has characters that are not allowed.
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_validate_topic_name_with_size(const char *topic_name, size_t topic_name_length, int *validation_result, size_t *invalid_index)
Validate a given topic name.
#define RCL_TOPIC_NAME_VALID
The topic name is valid.
#define RCL_TOPIC_NAME_INVALID_UNMATCHED_CURLY_BRACE
The topic name is invalid because it has an unmatched curly brace.
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.
#define RCL_TOPIC_NAME_INVALID_SUBSTITUTION_STARTS_WITH_NUMBER
The topic name is invalid because one of the substitutions starts with a number.