1 | #ifndef VWS_COMMON_DECLARE |
2 | #define VWS_COMMON_DECLARE |
3 | |
4 | #include <stddef.h> |
5 | #include <stdint.h> |
6 | |
7 | #include <openssl/ssl.h> |
8 | |
9 | #include "common.h" |
10 | #include "util/sc_map.h" |
11 | |
12 | // Typedefs for brevity |
13 | typedef const char* cstr; |
14 | typedef const unsigned char* ucstr; |
15 | |
16 | #ifdef __cplusplus |
17 | extern "C" { |
18 | #endif |
19 | |
20 | //------------------------------------------------------------------------------ |
21 | // Error handling |
22 | //------------------------------------------------------------------------------ |
23 | |
24 | /** |
25 | * @brief Enumerates error codes used in the library. |
26 | */ |
27 | typedef enum |
28 | { |
29 | VE_SUCCESS = 0, /**< No error */ |
30 | VE_TIMEOUT = (1 << 1), /**< Socket timeout */ |
31 | VE_WARN = (1 << 2), /**< Warning */ |
32 | VE_SOCKET = (1 << 3), /**< Socket disconnect */ |
33 | VE_SEND = (1 << 4), /**< Socket send error */ |
34 | VE_RECV = (1 << 5), /**< Socket receive error */ |
35 | VE_SYS = (1 << 6), /**< System call error */ |
36 | VE_RT = (1 << 7), /**< Runtime error */ |
37 | VE_MEM = (1 << 8), /**< Memory failure */ |
38 | VE_FATAL = (1 << 9), /**< Fatal error */ |
39 | } vws_error_code_t; |
40 | |
41 | // Trace levels |
42 | typedef enum vws_tl_t |
43 | { |
44 | VT_OFF = 0, |
45 | VT_APPLICATION = 1, |
46 | VT_MODULE = 2, |
47 | VT_SERVICE = 3, |
48 | VT_PROTOCOL = 4, |
49 | VT_THREAD = 5, |
50 | VT_TCPIP = 6, |
51 | VT_LOCK = 7, |
52 | VT_MEMORY = 8, |
53 | VT_ALL = 9 |
54 | } vws_tl_t; |
55 | |
56 | /** |
57 | * @brief Defines a structure for vrtql errors. |
58 | */ |
59 | typedef struct |
60 | { |
61 | uint64_t code; /**< Error code */ |
62 | char* text; /**< Error text */ |
63 | } vws_error_value; |
64 | |
65 | /**< The SSL context for the connection. */ |
66 | extern SSL_CTX* vws_ssl_ctx; |
67 | |
68 | //------------------------------------------------------------------------------ |
69 | // Tracing |
70 | //------------------------------------------------------------------------------ |
71 | |
72 | /** |
73 | * @brief Enumerates the levels of logging. |
74 | */ |
75 | typedef enum |
76 | { |
77 | VL_DEBUG, /**< Debug level log */ |
78 | VL_INFO, /**< Information level log */ |
79 | VL_WARN, /**< Warning level log */ |
80 | VL_ERROR, /**< Error level log */ |
81 | VL_LEVEL_COUNT /**< Count of log levels */ |
82 | } vws_log_level_t; |
83 | |
84 | /** |
85 | * @brief Logs a trace message. |
86 | * |
87 | * @param level The level of the log |
88 | * @param format The format string for the message |
89 | * @param ... The arguments for the format string |
90 | */ |
91 | void vws_trace(vws_log_level_t level, const char* format, ...); |
92 | |
93 | /** |
94 | * @brief Lock the log mutex to synchronize access to the logging functionality. |
95 | * |
96 | * This function is responsible for locking the log mutex, which ensures that |
97 | * only one thread can access the logging functionality at a time. It uses |
98 | * platform-specific synchronization mechanisms depending on the platform: |
99 | * |
100 | * - On Windows, it uses the Windows API function WaitForSingleObject() to |
101 | * lock the mutex. |
102 | * - On other platforms, it uses the pthread_mutex_lock() function to lock |
103 | * the mutex. |
104 | * |
105 | * If an error occurs while trying to lock the mutex, this function will |
106 | * submit an error message using vws_error_default_submit() with a |
107 | * corresponding error code. |
108 | * |
109 | * @see vws_trace_unlock() |
110 | */ |
111 | void vws_trace_lock(); |
112 | |
113 | /** |
114 | * @brief Unlock the log mutex to release synchronization after accessing |
115 | * the logging functionality. |
116 | * |
117 | * This function is responsible for unlocking the log mutex, allowing other |
118 | * threads to access the logging functionality. It complements the |
119 | * vws_trace_lock() function and should be called after finishing the |
120 | * logging-related tasks to release the mutex. |
121 | * |
122 | * This function uses platform-specific synchronization mechanisms depending |
123 | * on the platform: |
124 | * |
125 | * - On Windows, it uses the Windows API function ReleaseMutex() to unlock |
126 | * the mutex. |
127 | * - On other platforms, it uses the pthread_mutex_unlock() function to |
128 | * unlock the mutex. |
129 | * |
130 | * If an error occurs while trying to unlock the mutex, this function will |
131 | * submit an error message using vws_error_default_submit() with a |
132 | * corresponding error code. |
133 | * |
134 | * @see vws_trace_lock() |
135 | */ |
136 | void vws_trace_unlock(); |
137 | |
138 | /** |
139 | * @brief Callback for tracing |
140 | */ |
141 | typedef void (*vws_trace_cb)(vws_log_level_t level, const char* fmt, ...); |
142 | |
143 | /** |
144 | * @brief Callback for memory allocation with malloc(). |
145 | */ |
146 | typedef void* (*vws_malloc_cb)(size_t size); |
147 | |
148 | /** |
149 | * @brief Callback for memory deallocatiion with free(). |
150 | */ |
151 | typedef void (*vws_free_cb)(void* memory); |
152 | |
153 | /** |
154 | * @brief Callback for malloc failure. This is called when vrtql.malloc() fails |
155 | * to allocate memory. This function is meant to handle, report and/or recover |
156 | * from the error. Whatever this function returns will be returned from the |
157 | * vrtql.malloc(). |
158 | * |
159 | * @param size The size argument passed to vrtql.malloc() |
160 | */ |
161 | typedef void* (*vws_malloc_error_cb)(size_t size); |
162 | |
163 | /** |
164 | * @brief Callback for memory allocation with calloc. |
165 | */ |
166 | typedef void* (*vws_calloc_cb)(size_t nmemb, size_t size); |
167 | |
168 | /** |
169 | * @brief Callback for calloc failure. This is called when vrtql.calloc() fails |
170 | * to allocate memory. This function is meant to handle, report and/or recover |
171 | * from the error. Whatever this function returns will be returned from the |
172 | * vrtql.calloc(). |
173 | * |
174 | * @param nmemb The nmemb argument passed to vrtql.calloc() |
175 | * @param size The size argument passed to vrtql.calloc() |
176 | */ |
177 | typedef void* (*vws_calloc_error_cb)(size_t nmemb, size_t size); |
178 | |
179 | /** |
180 | * @brief Callback for memory allocation with realloc. |
181 | * |
182 | * @param ptr The ptr argument passed to vrtql.realloc() |
183 | */ |
184 | typedef void* (*vws_realloc_cb)(void* ptr, size_t size); |
185 | |
186 | /** |
187 | * @brief Callback for realloc failure. This is called when vrtql.realloc() |
188 | * fails to allocate memory. This function is meant to handle, report and/or |
189 | * recover from the error. Whatever this function returns will be returned from |
190 | * the vrtql.realloc(). |
191 | * |
192 | * @param ptr The ptr argument passed to vrtql.realloc() |
193 | * @param size The size argument passed to vrtql.realloc() |
194 | */ |
195 | typedef void* (*vws_realloc_error_cb)(void* ptr, size_t size); |
196 | |
197 | /** |
198 | * @brief Callback for memory allocation with strdup. |
199 | * |
200 | * @param ptr The ptr argument passed to vrtql.strdup() |
201 | */ |
202 | typedef void* (*vws_strdup_cb)(cstr ptr); |
203 | |
204 | /** |
205 | * @brief Callback for strdup failure. This is called when vrtql.strdup() |
206 | * fails to allocate memory. This function is meant to handle, report and/or |
207 | * recover from the error. Whatever this function returns will be returned from |
208 | * the vrtql.strdup(). |
209 | * |
210 | * @param ptr The ptr argument passed to vrtql.strdup() |
211 | * @param size The size argument passed to vrtql.strdup() |
212 | */ |
213 | typedef void* (*vws_strdup_error_cb)(cstr ptr); |
214 | |
215 | /** |
216 | * @brief Callback for error submission. Error submission function. Error |
217 | * submission takes care of recording the error in the vrtql.e member. The next |
218 | * step is the process the error. |
219 | */ |
220 | typedef int (*vws_error_submit_cb)(int code, cstr message, ...); |
221 | |
222 | /** |
223 | * @brief Callback for error processing. Error processing function. Error |
224 | * processing makes policy decisions, if any, on how to handle specific classes |
225 | * of errors, for example how to exit the process on fatal errors (VE_FATAL), or |
226 | * how to handle memory allocation errors (VE_MEM). |
227 | */ |
228 | typedef int (*vws_error_process_cb)(int code, cstr message); |
229 | |
230 | /** |
231 | * @brief Callback for error clearing. The default is to set VE_SUCESS. |
232 | */ |
233 | typedef void (*vws_error_clear_cb)(); |
234 | |
235 | /** |
236 | * @brief Callback for success conditions. The default is to set VE_SUCESS. |
237 | */ |
238 | typedef void (*vws_error_success_cb)(); |
239 | |
240 | /** |
241 | * @brief Defines the global vrtql environment. |
242 | */ |
243 | typedef struct |
244 | { |
245 | vws_malloc_cb malloc; /**< malloc function */ |
246 | vws_malloc_error_cb malloc_error; /**< malloc error hanlding */ |
247 | vws_calloc_cb calloc; /**< calloc function */ |
248 | vws_calloc_error_cb calloc_error; /**< calloc error hanlding */ |
249 | vws_realloc_cb realloc; /**< realloc function */ |
250 | vws_realloc_error_cb realloc_error; /**< calloc error hanlding */ |
251 | vws_strdup_cb strdup; /**< strdup function */ |
252 | vws_strdup_error_cb strdup_error; /**< calloc error hanlding */ |
253 | vws_free_cb free; /**< free function */ |
254 | vws_error_submit_cb error; /**< Error submission function */ |
255 | vws_error_process_cb process_error; /**< Error processing function */ |
256 | vws_error_clear_cb clear_error; /**< Error clear function */ |
257 | vws_error_clear_cb success; /**< Error clear function */ |
258 | vws_error_value e; /**< Last error value */ |
259 | vws_trace_cb trace; /**< Error clear function */ |
260 | uint8_t tracelevel; /**< Tracing leve (0 is off) */ |
261 | uint64_t state; /**< Contains global state flags */ |
262 | unsigned char sslbuf[4096]; /**< Thread-local SSL buffer */ |
263 | } vws_env; |
264 | |
265 | /** |
266 | * @brief The global vrtql environment variable |
267 | */ |
268 | extern __thread vws_env vws; |
269 | |
270 | /** |
271 | * @brief Defines a buffer for vrtql. |
272 | */ |
273 | typedef struct vws_buffer |
274 | { |
275 | unsigned char* data; /**< The data in the buffer */ |
276 | size_t allocated; /**< The amount of space allocated for the buffer */ |
277 | size_t size; /**< The current size of the data in the buffer */ |
278 | } vws_buffer; |
279 | |
280 | //------------------------------------------------------------------------------ |
281 | // Buffer |
282 | //------------------------------------------------------------------------------ |
283 | |
284 | /** |
285 | * @brief Creates a new vrtql buffer. |
286 | * |
287 | * @return Returns a new vws_buffer instance |
288 | */ |
289 | vws_buffer* vws_buffer_new(); |
290 | |
291 | /** |
292 | * @brief Frees a vrtql buffer. |
293 | * |
294 | * @param buffer The buffer to be freed |
295 | */ |
296 | void vws_buffer_free(vws_buffer* buffer); |
297 | |
298 | /** |
299 | * @brief Clears a vrtql buffer. |
300 | * |
301 | * @param buffer The buffer to be cleared |
302 | */ |
303 | void vws_buffer_clear(vws_buffer* buffer); |
304 | |
305 | /** |
306 | * @brief Appends formatted data to a vws_buffer using printf() format. |
307 | * |
308 | * This function appends formatted data to the specified vws_buffer using a |
309 | * printf()-style format string and variable arguments. The formatted data is |
310 | * appended to the existing content of the buffer. |
311 | * |
312 | * @param buffer The vws_buffer to append the formatted data to. |
313 | * @param format The printf() format string specifying the format of the data to |
314 | * be appended. |
315 | * @param ... Variable arguments corresponding to the format specifier in the |
316 | * format string. |
317 | * |
318 | * @note The behavior of this function is similar to the standard printf() |
319 | * function, where the format string specifies the expected format of the |
320 | * data and the variable arguments provide the values to be formatted and |
321 | * appended to the buffer. |
322 | * |
323 | * @note The vws_buffer must be initialized and have sufficient capacity to |
324 | * hold the appended data. If the buffer capacity is exceeded, the |
325 | * behavior is undefined. |
326 | * |
327 | * @warning Take care to ensure the format string and variable arguments are |
328 | * consistent, as mismatches can lead to undefined behavior or security |
329 | * vulnerabilities (e.g., format string vulnerabilities). |
330 | * |
331 | * @see vws_buffer_init |
332 | * @see vws_buffer_append |
333 | */ |
334 | void vws_buffer_printf(vws_buffer* buffer, cstr format, ...); |
335 | |
336 | /** |
337 | * @brief Appends data to a vrtql buffer. |
338 | * |
339 | * @param buffer The buffer to append to |
340 | * @param data The data to append |
341 | * @param size The size of the data |
342 | */ |
343 | void vws_buffer_append(vws_buffer* buffer, ucstr data, size_t size); |
344 | |
345 | /** |
346 | * @brief Drains a vrtql buffer by a given size. |
347 | * |
348 | * @param buffer The buffer to drain |
349 | * @param size The size to drain from the buffer |
350 | */ |
351 | void vws_buffer_drain(vws_buffer* buffer, size_t size); |
352 | |
353 | //------------------------------------------------------------------------------ |
354 | // Map |
355 | //------------------------------------------------------------------------------ |
356 | |
357 | /** |
358 | * @brief Retrieves a value from the map using a string key. |
359 | * |
360 | * Returns a constant string pointer to the value associated with the key. |
361 | * |
362 | * @param map The map from which to retrieve the value. |
363 | * @param key The string key to use for retrieval. |
364 | * @return A constant string pointer to the value associated with the key. |
365 | */ |
366 | cstr vws_map_get(struct sc_map_str* map, cstr key); |
367 | |
368 | /** |
369 | * @brief Sets a value in the map using a string key and value. |
370 | * |
371 | * It will create a new key-value pair or update the value if the key already exists. |
372 | * |
373 | * @param map The map in which to set the value. |
374 | * @param key The string key to use for setting. |
375 | * @param value The string value to set. |
376 | */ |
377 | void vws_map_set(struct sc_map_str* map, cstr key, cstr value); |
378 | |
379 | /** |
380 | * @brief Removes a key-value pair from the map using a string key. |
381 | * |
382 | * If the key exists in the map, it will be removed along with its associated value. |
383 | * |
384 | * @param map The map from which to remove the key-value pair. |
385 | * @param key The string key to use for removal. |
386 | */ |
387 | void vws_map_remove(struct sc_map_str* map, cstr key); |
388 | |
389 | /** |
390 | * @brief Removes all key-value pair from the map, calling free() on them |
391 | * |
392 | * @param map The map to clear |
393 | */ |
394 | void vws_map_clear(struct sc_map_str* map); |
395 | |
396 | //------------------------------------------------------------------------------ |
397 | // KVS Map |
398 | //------------------------------------------------------------------------------ |
399 | |
400 | /** |
401 | * @brief Structure to hold a value with a pointer to data and its size. |
402 | */ |
403 | typedef struct |
404 | { |
405 | void* data; ///< Pointer to the data. |
406 | size_t size; ///< Size of the data. |
407 | } vws_value; |
408 | |
409 | /** |
410 | * @brief Structure to hold a key-value pair, where the key is a string and the |
411 | * value is a vws_value struct. |
412 | */ |
413 | typedef struct |
414 | { |
415 | const char* key; ///< String key. |
416 | vws_value value; ///< Value associated with the key. |
417 | } vws_kvp; |
418 | |
419 | /** |
420 | * @brief Structure to represent a dynamic array of key-value pairs. |
421 | */ |
422 | typedef struct |
423 | { |
424 | vws_kvp* array; ///< Pointer to the array of key-value pairs. |
425 | size_t used; ///< Number of key-value pairs currently in use. |
426 | size_t size; ///< Capacity of the allocated array. |
427 | } vws_kvs; |
428 | |
429 | /** |
430 | * @brief Initializes a new dynamic array for key-value pairs. |
431 | * |
432 | * @param size Initial capacity of the dynamic array. |
433 | * @return Pointer to the newly created vws_kvs structure. |
434 | */ |
435 | vws_kvs* vws_kvs_new(size_t size); |
436 | |
437 | /** |
438 | * @brief Frees the allocated memory for the dynamic array and its contents. |
439 | * |
440 | * @param m Pointer to the vws_kvs structure to be freed. |
441 | */ |
442 | void vws_kvs_free(vws_kvs* m); |
443 | |
444 | /** |
445 | * @brief Clear all key/value pairs from map |
446 | * |
447 | * @param m Pointer to the vws_kvs structure to be cleared |
448 | */ |
449 | void vws_kvs_clear(vws_kvs* m); |
450 | |
451 | /** |
452 | * @brief Returns the number of key/value pairs |
453 | * |
454 | * @return Number of key/value pairs |
455 | */ |
456 | size_t vws_kvs_size(vws_kvs* m); |
457 | |
458 | /** |
459 | * @brief Inserts a key-value pair into the dynamic array in sorted order. |
460 | * |
461 | * @param m Pointer to the vws_kvs structure. |
462 | * @param key String key to insert. |
463 | * @param data Pointer to the data to be associated with the key. |
464 | * @param size Size of the data. |
465 | */ |
466 | void vws_kvs_set(vws_kvs* m, const char* key, void* data, size_t size); |
467 | |
468 | /** |
469 | * @brief Retrieves the value associated with the given key using binary search. |
470 | * |
471 | * @param m Pointer to the vws_kvs structure. |
472 | * @param key String key to search for. |
473 | * @return Pointer to the vws_value structure if found, otherwise NULL. |
474 | */ |
475 | vws_value* vws_kvs_get(vws_kvs* m, const char* key); |
476 | |
477 | /** |
478 | * @brief Inserts a key-value pair with a C-string as the value into the dynamic |
479 | * array in sorted order. |
480 | * |
481 | * @param m Pointer to the vws_kvs structure. |
482 | * @param key String key to insert. |
483 | * @param value C-string to be associated with the key. |
484 | */ |
485 | void vws_kvs_set_cstring(vws_kvs* m, const char* key, const char* value); |
486 | |
487 | /** |
488 | * @brief Retrieves the C-string value associated with the given key using |
489 | * binary search. |
490 | * |
491 | * @param m Pointer to the vws_kvs structure. |
492 | * @param key String key to search for. |
493 | * @return Pointer to the C-string if found, otherwise NULL. |
494 | */ |
495 | cstr vws_kvs_get_cstring(vws_kvs* m, const char* key); |
496 | |
497 | /** |
498 | * @brief Removes the key-value pair associated with the given key. |
499 | * |
500 | * @param m Pointer to the vws_kvs structure. |
501 | * @param key String key to remove. |
502 | * @return 1 if the key was found and removed, 0 otherwise. |
503 | */ |
504 | int vws_kvs_remove(vws_kvs* m, const char* key); |
505 | |
506 | //------------------------------------------------------------------------------ |
507 | // URL |
508 | //------------------------------------------------------------------------------ |
509 | |
510 | /** |
511 | * @brief Represents a parsed URL. |
512 | */ |
513 | typedef struct |
514 | { |
515 | char* scheme; /**< The URL scheme (http, https, etc.) */ |
516 | char* host; /**< The host name or IP address */ |
517 | char* port; /**< The port number */ |
518 | char* path; /**< The path on the host */ |
519 | char* query; /**< The query parameters */ |
520 | char* fragment; /**< The fragment identifier */ |
521 | } vws_url; |
522 | |
523 | /** |
524 | * @brief Parses a URL. |
525 | * |
526 | * @param url The URL to parse |
527 | * @return Returns a vws_url structure with the parsed URL parts |
528 | */ |
529 | vws_url vws_url_parse(const char* url); |
530 | |
531 | /** |
532 | * @brief Builds a URL string from parts. |
533 | * |
534 | * @param parts The URL parts |
535 | * @return Returns a URL string built from the parts |
536 | */ |
537 | char* vws_url_build(const vws_url* parts); |
538 | |
539 | /** |
540 | * @brief Allocates a vws_url structure. |
541 | * |
542 | * @return Returns a new vws_url structure |
543 | */ |
544 | vws_url vws_url_new(); |
545 | |
546 | /** |
547 | * @brief Frees a vws_url structure. |
548 | * |
549 | * @param parts The vws_url structure to free |
550 | */ |
551 | void vws_url_free(vws_url parts); |
552 | |
553 | //------------------------------------------------------------------------------ |
554 | // Utilities |
555 | //------------------------------------------------------------------------------ |
556 | |
557 | /** |
558 | * @brief Sleeps for the specified number of milliseconds. |
559 | * |
560 | * This function provides a platform-independent way to sleep for a specific |
561 | * duration in milliseconds. The behavior is similar to the standard `sleep` |
562 | * function, but with millisecond precision. |
563 | * |
564 | * @param ms The number of milliseconds to sleep. |
565 | * |
566 | * @note This function may not be available on all platforms. Make sure to |
567 | * include the necessary headers and handle any compilation errors or |
568 | * warnings specific to your environment. |
569 | */ |
570 | void vws_msleep(unsigned int ms); |
571 | |
572 | /** |
573 | * @brief Checks if a specific flag is set. |
574 | * |
575 | * @param flags The flags to check |
576 | * @param flag The flag to check for |
577 | * @return Returns true if the flag is set, false otherwise |
578 | */ |
579 | uint8_t vws_is_flag(const uint64_t* flags, uint64_t flag); |
580 | |
581 | /** |
582 | * @brief Sets a specific flag. |
583 | * |
584 | * @param flags The flags to modify |
585 | * @param flag The flag to set |
586 | * @return None |
587 | */ |
588 | void vws_set_flag(uint64_t* flags, uint64_t flag); |
589 | |
590 | /** |
591 | * @brief Clears a specific flag. |
592 | * |
593 | * @param flags The flags to modify |
594 | * @param flag The flag to clear |
595 | * @return None |
596 | */ |
597 | void vws_clear_flag(uint64_t* flags, uint64_t flag); |
598 | |
599 | /** |
600 | * @brief Generates a UUID. |
601 | * |
602 | * @return Returns a UUID string |
603 | */ |
604 | char* vws_generate_uuid(); |
605 | |
606 | /** |
607 | * @brief Encodes data as Base64. |
608 | * |
609 | * @param data The data to encode |
610 | * @param size The size of the data |
611 | * @return Returns a Base64-encoded string |
612 | */ |
613 | char* vws_base64_encode(const unsigned char* data, size_t size); |
614 | |
615 | /** |
616 | * @brief Decodes a Base64-encoded string. |
617 | * |
618 | * @param data The Base64 string to decode |
619 | * @param size Pointer to size which will be filled with the size of the decoded data |
620 | * @return Returns a pointer to the decoded data |
621 | */ |
622 | unsigned char* vws_base64_decode(const char* data, size_t* size); |
623 | |
624 | /** |
625 | * @brief Joins a path a filename to create new path |
626 | * |
627 | * @param root The path |
628 | * @param root The file name |
629 | * @return Returns dynamically allocate string (user must free()) containing the |
630 | * path and file |
631 | */ |
632 | char* vws_file_path(const char* root, const char* filename); |
633 | |
634 | /** |
635 | * @brief Checks to see if integer is valid within string |
636 | * @param str The string to check |
637 | * @param value Pointer to long for output value |
638 | * @return Resturn 1 if valid, 0 otherwise. |
639 | */ |
640 | bool vws_cstr_to_long(cstr str, long* value); |
641 | |
642 | /** |
643 | * @brief Cleans up any allocated memory in vws structure. This is not really |
644 | * required but it is helpful for threads to calls this so that valgrind does no |
645 | * show any memory leaks in the vws.e string. |
646 | */ |
647 | void vws_cleanup(); |
648 | |
649 | #ifdef __cplusplus |
650 | } |
651 | #endif |
652 | |
653 | #endif /* VWS_COMMON_DECLARE */ |
654 | |