aboutsummaryrefslogtreecommitdiff
path: root/src/regex/regex_block_lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/regex/regex_block_lib.c')
-rw-r--r--src/regex/regex_block_lib.c380
1 files changed, 187 insertions, 193 deletions
diff --git a/src/regex/regex_block_lib.c b/src/regex/regex_block_lib.c
index 77efa6180..f0feb3628 100644
--- a/src/regex/regex_block_lib.c
+++ b/src/regex/regex_block_lib.c
@@ -11,12 +11,12 @@
11 WITHOUT ANY WARRANTY; without even the implied warranty of 11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details. 13 Affero General Public License for more details.
14 14
15 You should have received a copy of the GNU Affero General Public License 15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 17
18 SPDX-License-Identifier: AGPL3.0-or-later 18 SPDX-License-Identifier: AGPL3.0-or-later
19*/ 19 */
20/** 20/**
21 * @author Bartlomiej Polot 21 * @author Bartlomiej Polot
22 * @file regex/regex_block_lib.c 22 * @file regex/regex_block_lib.c
@@ -27,15 +27,14 @@
27#include "regex_block_lib.h" 27#include "regex_block_lib.h"
28#include "gnunet_constants.h" 28#include "gnunet_constants.h"
29 29
30#define LOG(kind,...) GNUNET_log_from (kind,"regex-bck",__VA_ARGS__) 30#define LOG(kind, ...) GNUNET_log_from(kind, "regex-bck", __VA_ARGS__)
31 31
32GNUNET_NETWORK_STRUCT_BEGIN 32GNUNET_NETWORK_STRUCT_BEGIN
33 33
34/** 34/**
35 * Information for each edge. 35 * Information for each edge.
36 */ 36 */
37struct EdgeInfo 37struct EdgeInfo {
38{
39 /** 38 /**
40 * Index of the destination of this edge in the 39 * Index of the destination of this edge in the
41 * unique destinations array. 40 * unique destinations array.
@@ -53,9 +52,7 @@ struct EdgeInfo
53/** 52/**
54 * @brief Block to announce a regex state. 53 * @brief Block to announce a regex state.
55 */ 54 */
56struct RegexBlock 55struct RegexBlock {
57{
58
59 /** 56 /**
60 * Length of the proof regex string. 57 * Length of the proof regex string.
61 */ 58 */
@@ -85,7 +82,6 @@ struct RegexBlock
85 /* followed by 'char tokens[num_edges][edge_info[k].token_length]'; 82 /* followed by 'char tokens[num_edges][edge_info[k].token_length]';
86 essentially all of the tokens one after the other in the 83 essentially all of the tokens one after the other in the
87 order of the edges; tokens are NOT 0-terminated */ 84 order of the edges; tokens are NOT 0-terminated */
88
89}; 85};
90 86
91 87
@@ -100,15 +96,15 @@ GNUNET_NETWORK_STRUCT_END
100 * @return #GNUNET_YES if the block is accepting, #GNUNET_NO if not 96 * @return #GNUNET_YES if the block is accepting, #GNUNET_NO if not
101 */ 97 */
102int 98int
103GNUNET_BLOCK_is_accepting (const struct RegexBlock *block, 99GNUNET_BLOCK_is_accepting(const struct RegexBlock *block,
104 size_t size) 100 size_t size)
105{ 101{
106 if (size < sizeof (struct RegexBlock)) 102 if (size < sizeof(struct RegexBlock))
107 { 103 {
108 GNUNET_break_op (0); 104 GNUNET_break_op(0);
109 return GNUNET_SYSERR; 105 return GNUNET_SYSERR;
110 } 106 }
111 return ntohs (block->is_accepting); 107 return ntohs(block->is_accepting);
112} 108}
113 109
114 110
@@ -121,28 +117,27 @@ GNUNET_BLOCK_is_accepting (const struct RegexBlock *block,
121 * @return #GNUNET_OK if the proof is valid for the given key. 117 * @return #GNUNET_OK if the proof is valid for the given key.
122 */ 118 */
123int 119int
124REGEX_BLOCK_check_proof (const char *proof, 120REGEX_BLOCK_check_proof(const char *proof,
125 size_t proof_len, 121 size_t proof_len,
126 const struct GNUNET_HashCode *key) 122 const struct GNUNET_HashCode *key)
127{ 123{
128 struct GNUNET_HashCode key_check; 124 struct GNUNET_HashCode key_check;
129 125
130 if ( (NULL == proof) || (NULL == key)) 126 if ((NULL == proof) || (NULL == key))
131 { 127 {
132 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Proof check failed, was NULL.\n"); 128 GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Proof check failed, was NULL.\n");
133 return GNUNET_NO; 129 return GNUNET_NO;
134 } 130 }
135 GNUNET_CRYPTO_hash (proof, proof_len, &key_check); 131 GNUNET_CRYPTO_hash(proof, proof_len, &key_check);
136 return (0 == 132 return (0 ==
137 GNUNET_CRYPTO_hash_cmp (key, &key_check)) ? GNUNET_OK : GNUNET_NO; 133 GNUNET_CRYPTO_hash_cmp(key, &key_check)) ? GNUNET_OK : GNUNET_NO;
138} 134}
139 135
140 136
141/** 137/**
142 * Struct to keep track of the xquery while iterating all the edges in a block. 138 * Struct to keep track of the xquery while iterating all the edges in a block.
143 */ 139 */
144struct CheckEdgeContext 140struct CheckEdgeContext {
145{
146 /** 141 /**
147 * Xquery: string we are looking for. 142 * Xquery: string we are looking for.
148 */ 143 */
@@ -152,7 +147,6 @@ struct CheckEdgeContext
152 * Has any edge matched the xquery so far? (GNUNET_OK / GNUNET_NO) 147 * Has any edge matched the xquery so far? (GNUNET_OK / GNUNET_NO)
153 */ 148 */
154 int found; 149 int found;
155
156}; 150};
157 151
158 152
@@ -167,24 +161,24 @@ struct CheckEdgeContext
167 * @return #GNUNET_YES, to keep iterating 161 * @return #GNUNET_YES, to keep iterating
168 */ 162 */
169static int 163static int
170check_edge (void *cls, 164check_edge(void *cls,
171 const char *token, 165 const char *token,
172 size_t len, 166 size_t len,
173 const struct GNUNET_HashCode *key) 167 const struct GNUNET_HashCode *key)
174{ 168{
175 struct CheckEdgeContext *ctx = cls; 169 struct CheckEdgeContext *ctx = cls;
176 170
177 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 171 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
178 "edge %.*s [%u]: %s\n", 172 "edge %.*s [%u]: %s\n",
179 (int) len, 173 (int)len,
180 token, 174 token,
181 (unsigned int) len, 175 (unsigned int)len,
182 GNUNET_h2s (key)); 176 GNUNET_h2s(key));
183 if (NULL == ctx->xquery) 177 if (NULL == ctx->xquery)
184 return GNUNET_YES; 178 return GNUNET_YES;
185 if (strlen (ctx->xquery) < len) 179 if (strlen(ctx->xquery) < len)
186 return GNUNET_YES; /* too long */ 180 return GNUNET_YES; /* too long */
187 if (0 == strncmp (ctx->xquery, token, len)) 181 if (0 == strncmp(ctx->xquery, token, len))
188 ctx->found = GNUNET_OK; 182 ctx->found = GNUNET_OK;
189 return GNUNET_YES; /* keep checking for malformed data! */ 183 return GNUNET_YES; /* keep checking for malformed data! */
190} 184}
@@ -203,48 +197,48 @@ check_edge (void *cls,
203 * #GNUNET_SYSERR if the block is invalid. 197 * #GNUNET_SYSERR if the block is invalid.
204 */ 198 */
205int 199int
206REGEX_BLOCK_check (const struct RegexBlock *block, 200REGEX_BLOCK_check(const struct RegexBlock *block,
207 size_t size, 201 size_t size,
208 const struct GNUNET_HashCode *query, 202 const struct GNUNET_HashCode *query,
209 const char *xquery) 203 const char *xquery)
210{ 204{
211 struct GNUNET_HashCode key; 205 struct GNUNET_HashCode key;
212 struct CheckEdgeContext ctx; 206 struct CheckEdgeContext ctx;
213 int res; 207 int res;
214 208
215 LOG (GNUNET_ERROR_TYPE_DEBUG, 209 LOG(GNUNET_ERROR_TYPE_DEBUG,
216 "Block check\n"); 210 "Block check\n");
217 if (GNUNET_OK != 211 if (GNUNET_OK !=
218 REGEX_BLOCK_get_key (block, size, 212 REGEX_BLOCK_get_key(block, size,
219 &key)) 213 &key))
220 { 214 {
221 GNUNET_break_op (0); 215 GNUNET_break_op(0);
222 return GNUNET_SYSERR; 216 return GNUNET_SYSERR;
223 } 217 }
224 if (NULL != query && 218 if (NULL != query &&
225 0 != GNUNET_memcmp (&key, 219 0 != GNUNET_memcmp(&key,
226 query)) 220 query))
227 { 221 {
228 GNUNET_break_op (0); 222 GNUNET_break_op(0);
229 return GNUNET_SYSERR; 223 return GNUNET_SYSERR;
230 } 224 }
231 if ( (GNUNET_YES == ntohs (block->is_accepting)) && 225 if ((GNUNET_YES == ntohs(block->is_accepting)) &&
232 ( (NULL == xquery) || ('\0' == xquery[0]) ) ) 226 ((NULL == xquery) || ('\0' == xquery[0])))
233 { 227 {
234 LOG (GNUNET_ERROR_TYPE_DEBUG, 228 LOG(GNUNET_ERROR_TYPE_DEBUG,
235 " out! Is accepting: %u, xquery %p\n", 229 " out! Is accepting: %u, xquery %p\n",
236 ntohs(block->is_accepting), 230 ntohs(block->is_accepting),
237 xquery); 231 xquery);
238 return GNUNET_OK; 232 return GNUNET_OK;
239 } 233 }
240 ctx.xquery = xquery; 234 ctx.xquery = xquery;
241 ctx.found = GNUNET_NO; 235 ctx.found = GNUNET_NO;
242 res = REGEX_BLOCK_iterate (block, size, &check_edge, &ctx); 236 res = REGEX_BLOCK_iterate(block, size, &check_edge, &ctx);
243 if (GNUNET_SYSERR == res) 237 if (GNUNET_SYSERR == res)
244 return GNUNET_SYSERR; 238 return GNUNET_SYSERR;
245 if (NULL == xquery) 239 if (NULL == xquery)
246 return GNUNET_YES; 240 return GNUNET_YES;
247 LOG (GNUNET_ERROR_TYPE_DEBUG, "Result %d\n", ctx.found); 241 LOG(GNUNET_ERROR_TYPE_DEBUG, "Result %d\n", ctx.found);
248 return ctx.found; 242 return ctx.found;
249} 243}
250 244
@@ -258,9 +252,9 @@ REGEX_BLOCK_check (const struct RegexBlock *block,
258 * @return #GNUNET_OK on success, #GNUNET_SYSERR if the block is malformed 252 * @return #GNUNET_OK on success, #GNUNET_SYSERR if the block is malformed
259 */ 253 */
260int 254int
261REGEX_BLOCK_get_key (const struct RegexBlock *block, 255REGEX_BLOCK_get_key(const struct RegexBlock *block,
262 size_t block_len, 256 size_t block_len,
263 struct GNUNET_HashCode *key) 257 struct GNUNET_HashCode *key)
264{ 258{
265 uint16_t len; 259 uint16_t len;
266 const struct GNUNET_HashCode *destinations; 260 const struct GNUNET_HashCode *destinations;
@@ -269,23 +263,23 @@ REGEX_BLOCK_get_key (const struct RegexBlock *block,
269 uint16_t num_edges; 263 uint16_t num_edges;
270 size_t total; 264 size_t total;
271 265
272 if (block_len < sizeof (struct RegexBlock)) 266 if (block_len < sizeof(struct RegexBlock))
273 { 267 {
274 GNUNET_break_op (0); 268 GNUNET_break_op(0);
275 return GNUNET_SYSERR; 269 return GNUNET_SYSERR;
276 } 270 }
277 num_destinations = ntohs (block->num_destinations); 271 num_destinations = ntohs(block->num_destinations);
278 num_edges = ntohs (block->num_edges); 272 num_edges = ntohs(block->num_edges);
279 len = ntohs (block->proof_len); 273 len = ntohs(block->proof_len);
280 destinations = (const struct GNUNET_HashCode *) &block[1]; 274 destinations = (const struct GNUNET_HashCode *)&block[1];
281 edges = (const struct EdgeInfo *) &destinations[num_destinations]; 275 edges = (const struct EdgeInfo *)&destinations[num_destinations];
282 total = sizeof (struct RegexBlock) + num_destinations * sizeof (struct GNUNET_HashCode) + num_edges * sizeof (struct EdgeInfo) + len; 276 total = sizeof(struct RegexBlock) + num_destinations * sizeof(struct GNUNET_HashCode) + num_edges * sizeof(struct EdgeInfo) + len;
283 if (block_len < total) 277 if (block_len < total)
284 { 278 {
285 GNUNET_break_op (0); 279 GNUNET_break_op(0);
286 return GNUNET_SYSERR; 280 return GNUNET_SYSERR;
287 } 281 }
288 GNUNET_CRYPTO_hash (&edges[num_edges], len, key); 282 GNUNET_CRYPTO_hash(&edges[num_edges], len, key);
289 return GNUNET_OK; 283 return GNUNET_OK;
290} 284}
291 285
@@ -306,10 +300,10 @@ REGEX_BLOCK_get_key (const struct RegexBlock *block,
306 * be errors in further edges. 300 * be errors in further edges.
307 */ 301 */
308int 302int
309REGEX_BLOCK_iterate (const struct RegexBlock *block, 303REGEX_BLOCK_iterate(const struct RegexBlock *block,
310 size_t size, 304 size_t size,
311 REGEX_INTERNAL_EgdeIterator iterator, 305 REGEX_INTERNAL_EgdeIterator iterator,
312 void *iter_cls) 306 void *iter_cls)
313{ 307{
314 uint16_t len; 308 uint16_t len;
315 const struct GNUNET_HashCode *destinations; 309 const struct GNUNET_HashCode *destinations;
@@ -321,54 +315,54 @@ REGEX_BLOCK_iterate (const struct RegexBlock *block,
321 unsigned int n; 315 unsigned int n;
322 size_t off; 316 size_t off;
323 317
324 LOG (GNUNET_ERROR_TYPE_DEBUG, "Block iterate\n"); 318 LOG(GNUNET_ERROR_TYPE_DEBUG, "Block iterate\n");
325 if (size < sizeof (struct RegexBlock)) 319 if (size < sizeof(struct RegexBlock))
326 { 320 {
327 GNUNET_break_op (0); 321 GNUNET_break_op(0);
328 return GNUNET_SYSERR; 322 return GNUNET_SYSERR;
329 } 323 }
330 num_destinations = ntohs (block->num_destinations); 324 num_destinations = ntohs(block->num_destinations);
331 num_edges = ntohs (block->num_edges); 325 num_edges = ntohs(block->num_edges);
332 len = ntohs (block->proof_len); 326 len = ntohs(block->proof_len);
333 destinations = (const struct GNUNET_HashCode *) &block[1]; 327 destinations = (const struct GNUNET_HashCode *)&block[1];
334 edges = (const struct EdgeInfo *) &destinations[num_destinations]; 328 edges = (const struct EdgeInfo *)&destinations[num_destinations];
335 aux = (const char *) &edges[num_edges]; 329 aux = (const char *)&edges[num_edges];
336 total = sizeof (struct RegexBlock) + num_destinations * sizeof (struct GNUNET_HashCode) + num_edges * sizeof (struct EdgeInfo) + len; 330 total = sizeof(struct RegexBlock) + num_destinations * sizeof(struct GNUNET_HashCode) + num_edges * sizeof(struct EdgeInfo) + len;
337 if (size < total) 331 if (size < total)
338 { 332 {
339 GNUNET_break_op (0); 333 GNUNET_break_op(0);
340 return GNUNET_SYSERR; 334 return GNUNET_SYSERR;
341 } 335 }
342 for (n=0;n<num_edges;n++) 336 for (n = 0; n < num_edges; n++)
343 total += ntohs (edges[n].token_length); 337 total += ntohs(edges[n].token_length);
344 if (size != total) 338 if (size != total)
345 { 339 {
346 fprintf (stderr, "Expected %u, got %u\n", 340 fprintf(stderr, "Expected %u, got %u\n",
347 (unsigned int) size, 341 (unsigned int)size,
348 (unsigned int) total); 342 (unsigned int)total);
349 GNUNET_break_op (0); 343 GNUNET_break_op(0);
350 return GNUNET_SYSERR; 344 return GNUNET_SYSERR;
351 } 345 }
352 off = len; 346 off = len;
353 LOG (GNUNET_ERROR_TYPE_DEBUG, 347 LOG(GNUNET_ERROR_TYPE_DEBUG,
354 "Start iterating block of size %u, proof %u, off %u edges %u\n", 348 "Start iterating block of size %u, proof %u, off %u edges %u\n",
355 size, len, off, n); 349 size, len, off, n);
356 /* &aux[off] always points to our token */ 350 /* &aux[off] always points to our token */
357 for (n=0;n<num_edges;n++) 351 for (n = 0; n < num_edges; n++)
358 { 352 {
359 LOG (GNUNET_ERROR_TYPE_DEBUG, 353 LOG(GNUNET_ERROR_TYPE_DEBUG,
360 "Edge %u/%u, off %u tokenlen %u (%.*s)\n", 354 "Edge %u/%u, off %u tokenlen %u (%.*s)\n",
361 n+1, num_edges, off, 355 n + 1, num_edges, off,
362 ntohs (edges[n].token_length), ntohs (edges[n].token_length), 356 ntohs(edges[n].token_length), ntohs(edges[n].token_length),
363 &aux[off]); 357 &aux[off]);
364 if (NULL != iterator) 358 if (NULL != iterator)
365 if (GNUNET_NO == iterator (iter_cls, 359 if (GNUNET_NO == iterator(iter_cls,
366 &aux[off], 360 &aux[off],
367 ntohs (edges[n].token_length), 361 ntohs(edges[n].token_length),
368 &destinations[ntohs (edges[n].destination_index)])) 362 &destinations[ntohs(edges[n].destination_index)]))
369 return GNUNET_OK; 363 return GNUNET_OK;
370 off += ntohs (edges[n].token_length); 364 off += ntohs(edges[n].token_length);
371 } 365 }
372 return GNUNET_OK; 366 return GNUNET_OK;
373} 367}
374 368
@@ -384,11 +378,11 @@ REGEX_BLOCK_iterate (const struct RegexBlock *block,
384 * @return the regex block, NULL on error 378 * @return the regex block, NULL on error
385 */ 379 */
386struct RegexBlock * 380struct RegexBlock *
387REGEX_BLOCK_create (const char *proof, 381REGEX_BLOCK_create(const char *proof,
388 unsigned int num_edges, 382 unsigned int num_edges,
389 const struct REGEX_BLOCK_Edge *edges, 383 const struct REGEX_BLOCK_Edge *edges,
390 int accepting, 384 int accepting,
391 size_t *rsize) 385 size_t *rsize)
392{ 386{
393 struct RegexBlock *block; 387 struct RegexBlock *block;
394 struct GNUNET_HashCode destinations[1024]; /* 1024 = 64k/64 bytes/key == absolute MAX */ 388 struct GNUNET_HashCode destinations[1024]; /* 1024 = 64k/64 bytes/key == absolute MAX */
@@ -404,64 +398,64 @@ REGEX_BLOCK_create (const char *proof,
404 unsigned int i; 398 unsigned int i;
405 char *aux; 399 char *aux;
406 400
407 len = strlen (proof); 401 len = strlen(proof);
408 if (len > UINT16_MAX) 402 if (len > UINT16_MAX)
409 {
410 GNUNET_break (0);
411 return NULL;
412 }
413 unique_destinations = 0;
414 total = sizeof (struct RegexBlock) + len;
415 for (i=0;i<num_edges;i++)
416 {
417 slen = strlen (edges[i].label);
418 if (slen > UINT16_MAX)
419 { 403 {
420 GNUNET_break (0); 404 GNUNET_break(0);
421 return NULL; 405 return NULL;
422 } 406 }
423 total += slen; 407 unique_destinations = 0;
424 for (j=0;j<unique_destinations;j++) 408 total = sizeof(struct RegexBlock) + len;
425 if (0 == memcmp (&destinations[j], 409 for (i = 0; i < num_edges; i++)
426 &edges[i].destination,
427 sizeof (struct GNUNET_HashCode)))
428 break;
429 if (j >= 1024)
430 { 410 {
431 GNUNET_break (0); 411 slen = strlen(edges[i].label);
432 return NULL; 412 if (slen > UINT16_MAX)
413 {
414 GNUNET_break(0);
415 return NULL;
416 }
417 total += slen;
418 for (j = 0; j < unique_destinations; j++)
419 if (0 == memcmp(&destinations[j],
420 &edges[i].destination,
421 sizeof(struct GNUNET_HashCode)))
422 break;
423 if (j >= 1024)
424 {
425 GNUNET_break(0);
426 return NULL;
427 }
428 destination_indices[i] = j;
429 if (j == unique_destinations)
430 destinations[unique_destinations++] = edges[i].destination;
433 } 431 }
434 destination_indices[i] = j; 432 total += num_edges * sizeof(struct EdgeInfo) + unique_destinations * sizeof(struct GNUNET_HashCode);
435 if (j == unique_destinations)
436 destinations[unique_destinations++] = edges[i].destination;
437 }
438 total += num_edges * sizeof (struct EdgeInfo) + unique_destinations * sizeof (struct GNUNET_HashCode);
439 if (total >= GNUNET_CONSTANTS_MAX_BLOCK_SIZE) 433 if (total >= GNUNET_CONSTANTS_MAX_BLOCK_SIZE)
440 { 434 {
441 GNUNET_break (0); 435 GNUNET_break(0);
442 return NULL; 436 return NULL;
443 } 437 }
444 block = GNUNET_malloc (total); 438 block = GNUNET_malloc(total);
445 block->proof_len = htons (len); 439 block->proof_len = htons(len);
446 block->is_accepting = htons (accepting); 440 block->is_accepting = htons(accepting);
447 block->num_edges = htons (num_edges); 441 block->num_edges = htons(num_edges);
448 block->num_destinations = htons (unique_destinations); 442 block->num_destinations = htons(unique_destinations);
449 dests = (struct GNUNET_HashCode *) &block[1]; 443 dests = (struct GNUNET_HashCode *)&block[1];
450 GNUNET_memcpy (dests, destinations, sizeof (struct GNUNET_HashCode) * unique_destinations); 444 GNUNET_memcpy(dests, destinations, sizeof(struct GNUNET_HashCode) * unique_destinations);
451 edgeinfos = (struct EdgeInfo *) &dests[unique_destinations]; 445 edgeinfos = (struct EdgeInfo *)&dests[unique_destinations];
452 aux = (char *) &edgeinfos[num_edges]; 446 aux = (char *)&edgeinfos[num_edges];
453 off = len; 447 off = len;
454 GNUNET_memcpy (aux, proof, len); 448 GNUNET_memcpy(aux, proof, len);
455 for (i=0;i<num_edges;i++) 449 for (i = 0; i < num_edges; i++)
456 { 450 {
457 slen = strlen (edges[i].label); 451 slen = strlen(edges[i].label);
458 edgeinfos[i].token_length = htons ((uint16_t) slen); 452 edgeinfos[i].token_length = htons((uint16_t)slen);
459 edgeinfos[i].destination_index = htons (destination_indices[i]); 453 edgeinfos[i].destination_index = htons(destination_indices[i]);
460 GNUNET_memcpy (&aux[off], 454 GNUNET_memcpy(&aux[off],
461 edges[i].label, 455 edges[i].label,
462 slen); 456 slen);
463 off += slen; 457 off += slen;
464 } 458 }
465 *rsize = total; 459 *rsize = total;
466 return block; 460 return block;
467} 461}