Index

lognestmonster / 8802d97

A general-purpose single-header C logging library and parser for event-based logs. (Incomplete)

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
18704 Feb 2020 19:458802d97Add lnm_registry_flush_item function for readabilityJosh Stockin11917G

Blob @ lognestmonster / src / c / lognestmonster.h

text/plain20038 bytesdownload raw
1/* lognestmonster Copyright (c) 2020 Joshua 'joshuas3' Stockin
2 * <https://joshstock.in>
3 * <https://github.com/JoshuaS3/lognestmonster>
4 *
5 * This software is licensed and distributed under the terms of the MIT License:
6 * ----- BEGIN LICENSE -----
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 * ----- END LICENSE -----
25 *
26 * This comment block and its contents, including this disclaimer, MUST be
27 * preserved in all copies or distributions of this software's source.
28 */
29
30// lognestmonster.h
31// C header file for implementation of the lognestmonster logging library
32
33
34#ifdef __cplusplus
35extern "C" {
36#endif
37
38
39#ifndef LOGNESTMONSTER_H
40#define LOGNESTMONSTER_H 1
41
42
43#include <stdint.h> // necessary include for type declaration
44
45
46enum lnmVerbosityLevel {lnmInfo, lnmDebug, lnmVerbose, lnmVeryVerbose, lnmWarning, lnmError};
47typedef uint8_t * lnmItem;
48typedef uint8_t * lnmQueue;
49
50
51lnmQueue lnmQueueInit(char * name, char * out_path);
52lnmQueue lnmQueueByName(char * name);
53
54lnmItem lnmStatement(enum lnmVerbosityLevel verbosity, char * tag, char * message);
55
56lnmItem lnmEvent(char * tag);
57lnmItem lnmEventI(char * event_tag, lnmItem item);
58lnmItem lnmEventS(char * event_tag, enum lnmVerbosityLevel verbosity, char * statement_tag, char * message);
59void lnmEventPush(lnmItem event, lnmItem item);
60void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, char * tag, char * message);
61
62
63#ifdef LNM_ALL // expose private utilities
64
65// type definitions
66typedef struct lnm_pushable lnm_pushable;
67typedef struct lnm_log_event lnm_log_event;
68typedef struct lnm_log_statement lnm_log_statement;
69typedef struct lnm_queue lnm_queue;
70
71// lnm_pushable utilities
72lnm_pushable * lnm_new_pushable(void);
73void lnm_pushable_realloc(lnm_pushable * pushable);
74void lnm_pushable_push(lnm_pushable * pushable, lnmItem item);
75void lnm_pushable_pop(lnm_pushable * pushable);
76void lnm_pushable_remove(lnm_pushable * pushable, uint32_t index);
77void lnm_pushable_free(lnm_pushable * pushable);
78
79// misc utilities
80unsigned long lnm_getus(void);
81
82// registry utilities
83void lnm_registry_update(void);
84
85// memory management utilities
86void lnm_free_item(lnmItem item);
87void lnm_free_registry(void);
88void lnm_free_queue(lnmQueue queue);
89
90#endif // LNM_ALL
91
92
93#if defined(LNM_DEBUG) || defined(LNM_ALL)
94void lnm_debug_tabs(int tab_count);
95void lnm_debug_parse_item(lnmItem item, int tab_count);
96void lnm_debug_parse_registry(void);
97void lnm_debug_parse_queue(lnmQueue queue);
98#endif // LNM_DEBUG || LNM_ALL
99
100
101#ifdef LNM_INIT // define the library
102
103
104#include <stdint.h>
105#include <stdio.h>
106#include <stdlib.h>
107#include <string.h>
108
109
110static const uint8_t LNM_STATEMENT = 0;
111static const uint8_t LNM_EVENT = 1;
112
113
114// lnm_pushable utilities
115
116
117typedef struct lnm_pushable {
118 uint32_t capacity;
119 uint32_t length;
120 lnmItem * frame;
121} lnm_pushable;
122
123
124void lnm_pushable_realloc(lnm_pushable * pushable) {
125 if (pushable->length > pushable->capacity) {
126 if (pushable->capacity > UINT32_MAX / 2) {
127 printf("lognestmonster (lnm_pushable_realloc): pushable reached max capacity of 2^32-1. exiting...\n");
128 abort();
129 }
130 pushable->frame = realloc(pushable->frame, sizeof(lnmItem) * (pushable->capacity *= 2));
131 if (pushable->frame == NULL) {
132 printf("lognestmonster (lnm_pushable_realloc): call to realloc() returned NULL. exiting...\n");
133 abort();
134 }
135 } else if (pushable->length < (pushable->capacity / 2)) {
136 if (pushable->capacity > 8) {
137 pushable->frame = realloc(pushable->frame, sizeof(lnmItem) * (pushable->capacity /= 2));
138 if (pushable->frame == NULL) {
139 printf("lognestmonster (lnm_pushable_realloc): call to realloc() returned NULL. exiting...\n");
140 abort();
141 }
142 }
143 }
144}
145
146
147lnm_pushable * lnm_new_pushable(void) {
148 lnm_pushable * new_pushable = malloc(sizeof(lnm_pushable));
149 if (new_pushable == NULL) {
150 printf("lognestmonster (lnm_new_pushable): call to malloc() returned NULL. exiting...\n");
151 abort();
152 }
153 new_pushable->capacity = 8;
154 new_pushable->length = 0;
155 new_pushable->frame = malloc(sizeof(lnmItem) * 8);
156 if (new_pushable->frame == NULL) {
157 printf("lognestmonster (lnm_new_pushable): call to malloc() returned NULL. exiting...\n");
158 abort();
159 }
160 return new_pushable;
161}
162
163
164void lnm_pushable_push(lnm_pushable * pushable, lnmItem item) {
165 pushable->length++;
166 lnm_pushable_realloc(pushable);
167 pushable->frame[pushable->length - 1] = item;
168}
169
170
171void lnm_pushable_pop(lnm_pushable * pushable) {
172 pushable->length--;
173 lnm_pushable_realloc(pushable);
174}
175
176
177void lnm_pushable_remove(lnm_pushable * pushable, uint32_t index) {
178 if (index >= pushable->length) {
179 printf("lognestmonster (lnm_pushable_remove): attempt to remove index out of pushable bounds. exiting...\n");
180 abort();
181 }
182 if (index == pushable->length - 1) {
183 lnm_pushable_pop(pushable);
184 } else {
185 // shift entire array from index until end
186 for (uint32_t iter = index; iter < pushable->length - 1; iter++) {
187 pushable->frame[iter] = pushable->frame[iter + 1];
188 }
189 pushable->length--;
190 lnm_pushable_realloc(pushable);
191 }
192}
193
194
195void lnm_pushable_free(lnm_pushable * pushable) {
196 free(pushable->frame);
197 free(pushable);
198}
199
200
201// log event and log statement definitions
202
203
204typedef struct lnm_log_event {
205 // word 1, 1 byte data 7 bytes padding
206 uint8_t type:1; // used internally; 0 = statement, 1 = event
207 uint8_t pushed:1; // whether or not this log item has been pushed
208 uint8_t verbosity:3; // lnmVerbosityLevel, 0-5
209
210 // word 2, 8 bytes data
211 char * tag; // null-terminated tag string
212
213 // word 3, 8 bytes data
214 lnm_pushable * pushable; // pushable of lnmItems
215} lnm_log_event;
216
217
218typedef struct lnm_log_statement {
219 // word 1, 4 bytes data 4 bytes padding
220 uint8_t type:1; // used internally; 0 = statement, 1 = event
221 uint8_t pushed:1; // whether or not this log item has been pushed
222 uint8_t verbosity:3; // lnmVerbosityLevel, 0-5
223 uint8_t tag_size; // character length of the tag
224 uint16_t message_size; // character length of the message
225
226 // word 2, 8 bytes data
227 uint64_t timestamp; // 64-bit millisecond timestamp
228
229 // word 3, 8 bytes data
230 char * log; // non-null-terminated tag string + non-null-terminated message string
231} lnm_log_statement;
232
233
234// queue structure definition
235
236
237typedef struct lnm_queue {
238 char * name;
239 char * out_path;
240 uint64_t timestamp;
241 lnm_pushable * pushable;
242} lnm_queue;
243
244
245// time utilities
246
247
248#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
249#include <sys/time.h>
250static struct timeval lnm_current_time;
251#elif defined(_WIN32) || defined(__WINDOWS__)
252#include <sysinfoapi.h>
253static FILETIME lnm_win32_filetime;
254#else
255#error lognestmonster: Windows NT or a POSIX-compliant system were not detected. Implement your own system time functions or compile on a compliant system.
256#endif
257
258
259uint64_t lnm_getus(void) {
260 uint64_t us;
261#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
262 gettimeofday(&lnm_current_time, NULL);
263 us = (lnm_current_time.tv_sec*1000000+lnm_current_time.tv_usec);
264#elif defined(_WIN32) || defined(__WINDOWS__)
265 // get system time in ticks
266 GetSystemTimeAsFileTime(&lnm_win32_filetime);
267 // load time from two 32-bit words into one 64-bit integer
268 us = lnm_win32_filetime.dwHighDateTime << 32;
269 us |= lnm_win32_filetime.dwLowDateTime;
270 // convert to microseconds
271 us /= 10;
272 // convert from time since Windows NT epoch to time since Unix epoch
273 us -= 11644473600000000ULL;
274#endif
275 return us;
276}
277
278
279// item registry utils
280
281
282void lnm_free_item(lnmItem item);
283static lnm_pushable * lnm_registered_queues;
284static lnm_pushable * lnm_registered_items;
285static int lnm_registry_update_count;
286
287
288void lnm_registry_update(void) {
289 // iterate through registry
290 for (uint32_t iter = 0; iter < lnm_registered_items->length; iter++) {
291 lnm_log_statement * item = (lnm_log_statement *)(lnm_registered_items->frame[iter]);
292 // if the registered item has been pushed elsewhere, remove it from the top level of the registry
293 if (item->pushed) {
294 lnm_registry_update_count++;
295 lnm_pushable_remove(lnm_registered_items, iter);
296 iter--;
297 }
298 }
299}
300
301
302void lnm_registry_free() {
303 for (uint32_t iter = 0; iter < lnm_registered_items->length; iter++) {
304 lnm_free_item(lnm_registered_items->frame[iter]);
305 }
306}
307
308
309void lnm_registry_flush_item(lnmItem item) {
310 lnm_log_statement * item_cast = (lnm_log_statement *)item;
311 if (!item->pushed) {
312 item->pushed = 1;
313 lnm_registry_update();
314 }
315}
316
317
318// core library utilities
319
320
321int lnm_item_type(lnmItem item) {
322 return ((lnm_log_statement *)item)->type;
323}
324
325void lnm_free_item(lnmItem item) {
326 if (lnm_item_type(item) == LNM_STATEMENT) {
327 // cast item
328 lnm_log_statement * statement = (lnm_log_statement *)item;
329 // flush item out of registry
330 lnm_registry_flush_item(item);
331 // free item and its contents
332 free(statement->log);
333 free(statement);
334 } else if (lnm_item_type(item) == LNM_EVENT) {
335 // create breadcrumb navigation array with root 'item'
336 lnm_pushable * breadcrumb = lnm_new_pushable();
337 lnm_pushable_push(breadcrumb, item);
338 // continually iterate breadcrumb until it's empty
339 while (breadcrumb->length > 0) {
340 // get current item (deepest element of the breadcrumb nav, aka 'z' in 'x > y > z')
341 lnmItem current = breadcrumb->frame[breadcrumb->length - 1];
342 // flush item out of registry
343 lnm_registry_flush_item(current);
344 if (lnm_item_type(current) == LNM_STATEMENT) {
345 // cast current item
346 lnm_log_statement * current_statement = (lnm_log_statement *)current;
347 // free statement and its contents
348 free(current_statement->log);
349 free(current_statement);
350 // pop it from the breadcrumb nav
351 lnm_pushable_pop(breadcrumb);
352 // loop back
353 } else if (lnm_item_type(current) == LNM_EVENT) {
354 // cast current item
355 lnm_log_event * current_event = (lnm_log_event *)current;
356 if (current_event->pushable->length > 0) {
357 // the event has children
358 for (uint32_t iter = 0; iter < current_event->pushable->length; iter++) {
359 // add every child to the breadcrumb nav to free them
360 lnmItem current_event_child = current_event->pushable->frame[iter];
361 lnm_pushable_push(breadcrumb, current_event_child);
362 }
363 // set the current event as having no children so they're not erroneously counted twice
364 current_event->pushable->length = 0;
365 } else {
366 // the event is empty, so we can safely free it
367 lnm_pushable_free(current_event->pushable);
368 free(current_event->tag);
369 free(current_event);
370 // and remove from breadcrumb nav
371 lnm_pushable_pop(breadcrumb);
372 }
373 // loop back
374 } else {
375 printf("lognestmonster (lnm_free_item): item in log tree has non-statement and non-event type. exiting...\n");
376 abort();
377 }
378 }
379 } else {
380 printf("lognestmonster (lnm_free_item): log tree is non-statement and non-event type. exiting...\n");
381 abort();
382 }
383}
384
385
386// queue utilities
387
388
389void lnm_free_queue(lnmQueue queue) {
390 lnm_queue * queue_cast = (lnm_queue *)queue;
391 for (uint32_t iter = 0; iter < queue_cast->pushable->length; iter++) {
392 lnm_free_item(queue_cast->pushable->frame[iter]);
393 lnm_pushable_remove(queue_cast->pushable, iter);
394 iter--;
395 }
396}
397
398
399lnmQueue lnmQueueInit(char * name, char * out_path) {
400 // create queue and item registries if not created
401 if (lnm_registered_queues == NULL) {
402 lnm_registered_queues = lnm_new_pushable();
403 }
404 if (lnm_registered_items == NULL) {
405 lnm_registered_items = lnm_new_pushable();
406 }
407 // allocate and populate a new Queue object
408 lnm_queue * new_queue = malloc(sizeof(lnm_queue));
409 if (new_queue == NULL) {
410 printf("lognestmonster (lnmQueueInit): call to malloc() returned NULL. exiting...\n");
411 abort();
412 }
413 new_queue->name = malloc(strlen(name)+1);
414 new_queue->out_path = malloc(strlen(out_path)+1);
415 if (new_queue->name == NULL || new_queue->out_path == NULL) {
416 printf("lognestmonster (lnmQueueInit): call to malloc() returned NULL. exiting...\n");
417 abort();
418 }
419 strcpy(new_queue->name, name);
420 strcpy(new_queue->out_path, out_path);
421 new_queue->timestamp = lnm_getus();
422 new_queue->pushable = lnm_new_pushable();
423 // enter new Queue into registry
424 lnm_pushable_push(lnm_registered_queues, (lnmQueue)new_queue);
425 return (lnmQueue)new_queue;
426}
427
428
429lnmQueue lnmQueueByName(char * name) {
430 if (lnm_registered_queues == NULL) {
431 printf("lognestmonster (lnmQueueByName): queue registry is nonexistant. exiting...\n");
432 abort();
433 }
434 if (lnm_registered_queues->length == 0) {
435 printf("lognestmonster (lnmQueueByName): queue registry is empty. exiting...\n");
436 abort();
437 }
438 for (uint32_t iter = 0; iter<lnm_registered_queues->length; iter++) {
439 lnm_queue * queue = (lnm_queue *)lnm_registered_queues->frame[iter];
440 if (strcmp(queue->name, name) == 0) {
441 return (lnmQueue)queue;
442 }
443 }
444 printf("lognestmonster (lnmQueueByName): queue not found in registry. exiting...\n");
445 abort();
446}
447
448
449void lnmQueuePush(lnmQueue queue, lnmItem item) {
450 lnm_log_statement * statement = (lnm_log_statement *)item;
451 if (statement->pushed == 1) {
452 printf("lognestmonster (lnmQueuePush): attempt to push an already-pushed log item. exiting...\n");
453 abort();
454 }
455 // flush out of registry
456 statement->pushed = 1;
457 lnm_registry_update();
458 // add to queue
459 lnm_pushable_push(((lnm_queue *)queue)->pushable, item);
460}
461
462
463lnmItem lnmStatement(enum lnmVerbosityLevel verbosity, char * tag, char * message) {
464 lnm_log_statement * new_statement = malloc(sizeof(lnm_log_statement));
465 if (new_statement == NULL) {
466 printf("lognestmonster (lnmStatement): call to malloc() returned NULL. exiting...\n");
467 abort();
468 }
469 new_statement->type = LNM_STATEMENT;
470 new_statement->verbosity = verbosity;
471 new_statement->timestamp = lnm_getus();
472 // enforce string lengths
473 int tag_len = strlen(tag);
474 if (tag_len > 256 || tag_len < 0) {
475 printf("lognestmonster (lnmStatement): tag length %i is longer than the cap 256 characters. exiting...\n", tag_len);
476 abort();
477 }
478 int message_len = strlen(message);
479 if (message_len > 65536 || message_len < 0) {
480 printf("lognestmonster (lnmStatement): message length %i is longer than the cap 65536 characters. exiting...\n", message_len);
481 abort();
482 }
483 new_statement->tag_size = tag_len;
484 new_statement->message_size = message_len;
485 // concat tag + message to new_statement->log
486 new_statement->log = malloc(tag_len + message_len + 1);
487 if (new_statement->log == NULL) {
488 printf("lognestmonster (lnmStatement): call to malloc() returned NULL. exiting...\n");
489 abort();
490 }
491 strcpy(new_statement->log, tag);
492 strcat(new_statement->log, message);
493 // add to registry
494 lnm_pushable_push(lnm_registered_items, (lnmItem)new_statement);
495 return (lnmItem)new_statement;
496}
497
498
499lnmItem lnmEvent(char * tag) {
500 lnm_log_event * new_event = malloc(sizeof(lnm_log_event));
501 if (new_event == NULL) {
502 printf("lognestmonster (lnmEvent): call to malloc() returned NULL. exiting...\n");
503 abort();
504 }
505 new_event->type = LNM_EVENT;
506 new_event->pushable = lnm_new_pushable();
507 // copy tag to event
508 int tag_len = strlen(tag);
509 new_event->tag = malloc(tag_len + 1);
510 if (new_event->tag == NULL) {
511 printf("lognestmonster (lnmEvent): call to malloc() returned NULL. exiting...\n");
512 abort();
513 }
514 strcpy(new_event->tag, tag);
515 // add to registry
516 lnm_pushable_push(lnm_registered_items, (lnmItem)new_event);
517 return (lnmItem)new_event;
518}
519
520
521void lnmEventPush(lnmItem event, lnmItem item) {
522 if (event == item) {
523 printf("lognestmonster (lnmEventPush): attempt to push event to self. exiting...\n");
524 abort();
525 }
526 lnm_log_statement * item_cast = (lnm_log_statement *)item;
527 if (item_cast->pushed == 1) {
528 printf("lognestmonster (lnmEventPush): attempt to push an already-pushed log item. exiting...\n");
529 abort();
530 }
531 if (lnm_item_type(event) != LNM_EVENT) {
532 printf("lognestmonster (lnmEventPush): cannot cast non-event to event type. exiting...\n");
533 abort();
534 }
535 lnm_log_event * event_cast = (lnm_log_event *)event;
536 lnm_pushable_push(event_cast->pushable, item);
537 item_cast->pushed = 1;
538 lnm_registry_update();
539}
540
541
542void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, char * statement_tag, char * message) {
543 lnmItem statement = lnmStatement(verbosity, statement_tag, message);
544 lnmEventPush(event, statement);
545}
546
547
548lnmItem lnmEventI(char * event_tag, lnmItem item) {
549 lnmItem event = lnmEvent(event_tag);
550 lnmEventPush(event, item);
551 return event;
552}
553
554
555lnmItem lnmEventS(char * event_tag, enum lnmVerbosityLevel verbosity, char * statement_tag, char * message) {
556 lnmItem event = lnmEvent(event_tag);
557 lnmEventPushS(event, verbosity, statement_tag, message);
558 return event;
559}
560
561
562#ifdef LNM_DEBUG
563#include <inttypes.h>
564
565void lnm_debug_tabs(int tab_count) {
566 for (int i = 0; i < tab_count; i++) {
567 printf(" ");
568 }
569}
570
571
572void lnm_debug_parse_item(lnmItem item, int tab_count) {
573 if (lnm_item_type(item) == LNM_STATEMENT) {
574 lnm_log_statement * statement = (lnm_log_statement *) item;
575 lnm_debug_tabs(tab_count);
576
577 char * verbosity;
578 switch (statement->verbosity) {
579 case 0:
580 verbosity = "INFO";
581 break;
582 case 1:
583 verbosity = "DEBUG";
584 break;
585 case 2:
586 verbosity = "VERBOSE";
587 break;
588 case 3:
589 verbosity = "VERYVERBOSE";
590 break;
591 case 4:
592 verbosity = "WARNING";
593 break;
594 case 5:
595 verbosity = "ERROR";
596 break;
597 }
598
599 char tag[statement->tag_size+1];
600 strncpy(tag, statement->log, statement->tag_size);
601 tag[statement->tag_size] = '\0';
602
603 char message[statement->message_size+1];
604 strncpy(message, statement->log+statement->tag_size, statement->message_size);
605 message[statement->message_size] = '\0';
606
607 printf("%" PRIu64 " (%s) %s :: %s\n", statement->timestamp, verbosity, tag, message);
608 } else if (lnm_item_type(item) == LNM_EVENT) {
609 lnm_log_event * event = (lnm_log_event *) item;
610 lnm_debug_tabs(tab_count);
611 printf("Event (%" PRIu32 "/%" PRIu32 ") %s [\n", event->pushable->length, event->pushable->capacity, event->tag);
612 for (uint32_t iter = 0; iter < event->pushable->length; iter++) {
613 lnmItem item = event->pushable->frame[iter];
614 lnm_debug_parse_item(item, tab_count + 1);
615 }
616 lnm_debug_tabs(tab_count);
617 printf("]\n");
618 } else {
619 printf("lognestmonster (lnm_debug_parse_item): unknown item type. exiting...\n");
620 abort();
621 }
622}
623
624
625void lnm_debug_parse_registry(void) {
626 printf("Top level registry (%" PRIu32 "/%" PRIu32 ") [\n", lnm_registered_items->length, lnm_registered_items->capacity);
627 for (uint32_t iter = 0; iter < lnm_registered_items->length; iter++) {
628 lnm_debug_parse_item(lnm_registered_items->frame[iter], 1);
629 }
630 printf("]\n");
631}
632
633
634void lnm_debug_parse_queue(lnmQueue queue) {
635 lnm_queue * queue_cast = (lnm_queue *)queue;
636 printf("Queue \"%s\" at %s (%" PRIu32 "/%" PRIu32 ") [\n", queue_cast->name, queue_cast->out_path, queue_cast->pushable->length, queue_cast->pushable->capacity);
637 for (uint32_t iter = 0; iter < queue_cast->pushable->length; iter++) {
638 lnm_debug_parse_item((lnmItem)queue_cast->pushable->frame[iter], 1);
639 }
640 printf("]\n");
641}
642#endif // LNM_DEBUG
643#endif // LNM_INIT
644#endif // LOGNESTMONSTER_H
645
646
647#ifdef __cplusplus
648}
649#endif
650