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