sink.ctf.fs: honor component's initial log level
[babeltrace.git] / src / plugins / ctf / common / bfcr / bfcr.c
CommitLineData
5cd6d0e5
PP
1/*
2 * Babeltrace - CTF binary field class reader (BFCR)
3 *
4 * Copyright (c) 2015-2016 EfficiOS Inc. and Linux Foundation
5 * Copyright (c) 2015-2016 Philippe Proulx <pproulx@efficios.com>
6 *
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 */
25
80870e64 26#define BT_LOG_OUTPUT_LEVEL (bfcr->log_level)
350ad6c1 27#define BT_LOG_TAG "PLUGIN/CTF/BFCR"
80870e64 28#include "logging/log.h"
5cd6d0e5
PP
29
30#include <stdlib.h>
31#include <stdint.h>
32#include <inttypes.h>
33#include <stdio.h>
34#include <stddef.h>
35#include <stdbool.h>
578e048b 36#include "common/assert.h"
5cd6d0e5 37#include <string.h>
578e048b
MJ
38#include "compat/bitfield.h"
39#include "common/common.h"
3fadfbc0 40#include <babeltrace2/babeltrace.h>
578e048b 41#include "common/align.h"
5cd6d0e5
PP
42#include <glib.h>
43
44#include "bfcr.h"
45#include "../metadata/ctf-meta.h"
46
47#define DIV8(_x) ((_x) >> 3)
48#define BYTES_TO_BITS(_x) ((_x) * 8)
49#define BITS_TO_BYTES_FLOOR(_x) DIV8(_x)
50#define BITS_TO_BYTES_CEIL(_x) DIV8((_x) + 7)
51#define IN_BYTE_OFFSET(_at) ((_at) & 7)
52
53/* A visit stack entry */
54struct stack_entry {
55 /*
56 * Current class of base field, one of:
57 *
58 * * Structure
59 * * Array
60 * * Sequence
61 * * Variant
62 */
63 struct ctf_field_class *base_class;
64
65 /* Length of base field (always 1 for a variant class) */
66 int64_t base_len;
67
68 /* Index of next field to read */
69 int64_t index;
70};
71
80870e64
PP
72struct bt_bfcr;
73
5cd6d0e5
PP
74/* Visit stack */
75struct stack {
80870e64
PP
76 struct bt_bfcr *bfcr;
77
5cd6d0e5
PP
78 /* Entries (struct stack_entry) */
79 GArray *entries;
80
81 /* Number of active entries */
82 size_t size;
83};
84
85/* Reading states */
86enum bfcr_state {
87 BFCR_STATE_NEXT_FIELD,
88 BFCR_STATE_ALIGN_BASIC,
89 BFCR_STATE_ALIGN_COMPOUND,
90 BFCR_STATE_READ_BASIC_BEGIN,
91 BFCR_STATE_READ_BASIC_CONTINUE,
92 BFCR_STATE_DONE,
93};
94
95/* Binary class reader */
96struct bt_bfcr {
80870e64
PP
97 bt_logging_level log_level;
98
99 /* BFCR stack */
5cd6d0e5
PP
100 struct stack *stack;
101
102 /* Current basic field class */
103 struct ctf_field_class *cur_basic_field_class;
104
105 /* Current state */
106 enum bfcr_state state;
107
108 /*
109 * Last basic field class's byte order.
110 *
111 * This is used to detect errors since two contiguous basic
112 * classes for which the common boundary is not the boundary of
113 * a byte cannot have different byte orders.
114 *
115 * This is set to -1 on reset and when the last basic field class
116 * was a string class.
117 */
118 enum ctf_byte_order last_bo;
119
120 /* Current byte order (copied to last_bo after a successful read) */
121 enum ctf_byte_order cur_bo;
122
123 /* Stitch buffer infos */
124 struct {
125 /* Stitch buffer */
126 uint8_t buf[16];
127
128 /* Offset, within stitch buffer, of first bit */
129 size_t offset;
130
131 /* Length (bits) of data in stitch buffer from offset */
132 size_t at;
133 } stitch;
134
135 /* User buffer infos */
136 struct {
137 /* Address */
138 const uint8_t *addr;
139
140 /* Offset of data from address (bits) */
141 size_t offset;
142
143 /* Current position from offset (bits) */
144 size_t at;
145
146 /* Offset of offset within whole packet (bits) */
147 size_t packet_offset;
148
149 /* Data size in buffer (bits) */
150 size_t sz;
151
152 /* Buffer size (bytes) */
153 size_t buf_sz;
154 } buf;
155
156 /* User stuff */
157 struct {
158 /* Callback functions */
159 struct bt_bfcr_cbs cbs;
160
161 /* Private data */
162 void *data;
163 } user;
164};
165
166static inline
167const char *bfcr_state_string(enum bfcr_state state)
168{
169 switch (state) {
170 case BFCR_STATE_NEXT_FIELD:
171 return "BFCR_STATE_NEXT_FIELD";
172 case BFCR_STATE_ALIGN_BASIC:
173 return "BFCR_STATE_ALIGN_BASIC";
174 case BFCR_STATE_ALIGN_COMPOUND:
175 return "BFCR_STATE_ALIGN_COMPOUND";
176 case BFCR_STATE_READ_BASIC_BEGIN:
177 return "BFCR_STATE_READ_BASIC_BEGIN";
178 case BFCR_STATE_READ_BASIC_CONTINUE:
179 return "BFCR_STATE_READ_BASIC_CONTINUE";
180 case BFCR_STATE_DONE:
181 return "BFCR_STATE_DONE";
182 default:
183 return "(unknown)";
184 }
185}
186
187static
80870e64 188struct stack *stack_new(struct bt_bfcr *bfcr)
5cd6d0e5
PP
189{
190 struct stack *stack = NULL;
191
192 stack = g_new0(struct stack, 1);
193 if (!stack) {
194 BT_LOGE_STR("Failed to allocate one stack.");
195 goto error;
196 }
197
80870e64 198 stack->bfcr = bfcr;
5cd6d0e5
PP
199 stack->entries = g_array_new(FALSE, TRUE, sizeof(struct stack_entry));
200 if (!stack->entries) {
201 BT_LOGE_STR("Failed to allocate a GArray.");
202 goto error;
203 }
204
205 BT_LOGD("Created stack: addr=%p", stack);
206 return stack;
207
208error:
209 g_free(stack);
210 return NULL;
211}
212
213static
214void stack_destroy(struct stack *stack)
215{
80870e64
PP
216 struct bt_bfcr *bfcr;
217
5cd6d0e5
PP
218 if (!stack) {
219 return;
220 }
221
80870e64 222 bfcr = stack->bfcr;
5cd6d0e5
PP
223 BT_LOGD("Destroying stack: addr=%p", stack);
224
225 if (stack->entries) {
226 g_array_free(stack->entries, TRUE);
227 }
228
229 g_free(stack);
230}
231
232static
233int stack_push(struct stack *stack, struct ctf_field_class *base_class,
234 size_t base_len)
235{
236 struct stack_entry *entry;
80870e64 237 struct bt_bfcr *bfcr;
5cd6d0e5
PP
238
239 BT_ASSERT(stack);
240 BT_ASSERT(base_class);
80870e64 241 bfcr = stack->bfcr;
5cd6d0e5 242 BT_LOGV("Pushing field class on stack: stack-addr=%p, "
864cad70 243 "fc-addr=%p, fc-type=%d, base-length=%zu, "
5cd6d0e5 244 "stack-size-before=%zu, stack-size-after=%zu",
864cad70 245 stack, base_class, base_class->type,
5cd6d0e5
PP
246 base_len, stack->size, stack->size + 1);
247
248 if (stack->entries->len == stack->size) {
249 g_array_set_size(stack->entries, stack->size + 1);
250 }
251
252 entry = &g_array_index(stack->entries, struct stack_entry, stack->size);
253 entry->base_class = base_class;
254 entry->base_len = base_len;
255 entry->index = 0;
256 stack->size++;
257 return 0;
258}
259
260static inline
261int64_t get_compound_field_class_length(struct bt_bfcr *bfcr,
262 struct ctf_field_class *fc)
263{
264 int64_t length;
265
864cad70
PP
266 switch (fc->type) {
267 case CTF_FIELD_CLASS_TYPE_STRUCT:
5cd6d0e5
PP
268 {
269 struct ctf_field_class_struct *struct_fc = (void *) fc;
270
271 length = (int64_t) struct_fc->members->len;
272 break;
273 }
864cad70 274 case CTF_FIELD_CLASS_TYPE_VARIANT:
5cd6d0e5
PP
275 {
276 /* Variant field classes always "contain" a single class */
277 length = 1;
278 break;
279 }
864cad70 280 case CTF_FIELD_CLASS_TYPE_ARRAY:
5cd6d0e5
PP
281 {
282 struct ctf_field_class_array *array_fc = (void *) fc;
283
284 length = (int64_t) array_fc->length;
285 break;
286 }
864cad70 287 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
5cd6d0e5
PP
288 length = bfcr->user.cbs.query.get_sequence_length(fc,
289 bfcr->user.data);
290 break;
291 default:
292 abort();
293 }
294
295 return length;
296}
297
298static
299int stack_push_with_len(struct bt_bfcr *bfcr, struct ctf_field_class *base_class)
300{
301 int ret;
302 int64_t length = get_compound_field_class_length(bfcr, base_class);
303
304 if (length < 0) {
305 BT_LOGW("Cannot get compound field class's field count: "
864cad70
PP
306 "bfcr-addr=%p, fc-addr=%p, fc-type=%d",
307 bfcr, base_class, base_class->type);
5cd6d0e5
PP
308 ret = BT_BFCR_STATUS_ERROR;
309 goto end;
310 }
311
312 ret = stack_push(bfcr->stack, base_class, (size_t) length);
313
314end:
315 return ret;
316}
317
318static inline
319unsigned int stack_size(struct stack *stack)
320{
321 BT_ASSERT(stack);
322 return stack->size;
323}
324
325static
326void stack_pop(struct stack *stack)
327{
80870e64
PP
328 struct bt_bfcr *bfcr;
329
5cd6d0e5
PP
330 BT_ASSERT(stack);
331 BT_ASSERT(stack_size(stack));
80870e64 332 bfcr = stack->bfcr;
5cd6d0e5
PP
333 BT_LOGV("Popping from stack: "
334 "stack-addr=%p, stack-size-before=%u, stack-size-after=%u",
335 stack, stack->entries->len, stack->entries->len - 1);
336 stack->size--;
337}
338
339static inline
340bool stack_empty(struct stack *stack)
341{
342 return stack_size(stack) == 0;
343}
344
345static
346void stack_clear(struct stack *stack)
347{
348 BT_ASSERT(stack);
349 stack->size = 0;
350}
351
352static inline
353struct stack_entry *stack_top(struct stack *stack)
354{
355 BT_ASSERT(stack);
356 BT_ASSERT(stack_size(stack));
357 return &g_array_index(stack->entries, struct stack_entry,
358 stack->size - 1);
359}
360
361static inline
362size_t available_bits(struct bt_bfcr *bfcr)
363{
364 return bfcr->buf.sz - bfcr->buf.at;
365}
366
367static inline
368void consume_bits(struct bt_bfcr *bfcr, size_t incr)
369{
370 BT_LOGV("Advancing cursor: bfcr-addr=%p, cur-before=%zu, cur-after=%zu",
371 bfcr, bfcr->buf.at, bfcr->buf.at + incr);
372 bfcr->buf.at += incr;
373}
374
375static inline
376bool has_enough_bits(struct bt_bfcr *bfcr, size_t sz)
377{
378 return available_bits(bfcr) >= sz;
379}
380
381static inline
382bool at_least_one_bit_left(struct bt_bfcr *bfcr)
383{
384 return has_enough_bits(bfcr, 1);
385}
386
387static inline
388size_t packet_at(struct bt_bfcr *bfcr)
389{
390 return bfcr->buf.packet_offset + bfcr->buf.at;
391}
392
393static inline
394size_t buf_at_from_addr(struct bt_bfcr *bfcr)
395{
396 /*
397 * Considering this:
398 *
399 * ====== offset ===== (17)
400 *
401 * xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
402 * ^
403 * addr (0) ==== at ==== (12)
404 *
405 * We want this:
406 *
407 * =============================== (29)
408 */
409 return bfcr->buf.offset + bfcr->buf.at;
410}
411
412static
413void stitch_reset(struct bt_bfcr *bfcr)
414{
415 bfcr->stitch.offset = 0;
416 bfcr->stitch.at = 0;
417}
418
419static inline
420size_t stitch_at_from_addr(struct bt_bfcr *bfcr)
421{
422 return bfcr->stitch.offset + bfcr->stitch.at;
423}
424
425static
426void stitch_append_from_buf(struct bt_bfcr *bfcr, size_t sz)
427{
428 size_t stitch_byte_at;
429 size_t buf_byte_at;
430 size_t nb_bytes;
431
432 if (sz == 0) {
433 return;
434 }
435
436 stitch_byte_at =
437 BITS_TO_BYTES_FLOOR(stitch_at_from_addr(bfcr));
438 buf_byte_at = BITS_TO_BYTES_FLOOR(buf_at_from_addr(bfcr));
439 nb_bytes = BITS_TO_BYTES_CEIL(sz);
440 BT_ASSERT(nb_bytes > 0);
441 BT_ASSERT(bfcr->buf.addr);
442 memcpy(&bfcr->stitch.buf[stitch_byte_at], &bfcr->buf.addr[buf_byte_at],
443 nb_bytes);
444 bfcr->stitch.at += sz;
445 consume_bits(bfcr, sz);
446}
447
448static
449void stitch_append_from_remaining_buf(struct bt_bfcr *bfcr)
450{
451 stitch_append_from_buf(bfcr, available_bits(bfcr));
452}
453
454static
455void stitch_set_from_remaining_buf(struct bt_bfcr *bfcr)
456{
457 stitch_reset(bfcr);
458 bfcr->stitch.offset = IN_BYTE_OFFSET(buf_at_from_addr(bfcr));
459 stitch_append_from_remaining_buf(bfcr);
460}
461
462static inline
80870e64 463void read_unsigned_bitfield(struct bt_bfcr *bfcr, const uint8_t *buf, size_t at,
5cd6d0e5
PP
464 unsigned int field_size, enum ctf_byte_order bo,
465 uint64_t *v)
466{
467 switch (bo) {
468 case CTF_BYTE_ORDER_BIG:
469 bt_bitfield_read_be(buf, uint8_t, at, field_size, v);
470 break;
471 case CTF_BYTE_ORDER_LITTLE:
472 bt_bitfield_read_le(buf, uint8_t, at, field_size, v);
473 break;
474 default:
475 abort();
476 }
477
478 BT_LOGV("Read unsigned bit array: cur=%zu, size=%u, "
479 "bo=%d, val=%" PRIu64, at, field_size, bo, *v);
480}
481
482static inline
80870e64 483void read_signed_bitfield(struct bt_bfcr *bfcr, const uint8_t *buf, size_t at,
5cd6d0e5
PP
484 unsigned int field_size, enum ctf_byte_order bo, int64_t *v)
485{
486 switch (bo) {
487 case CTF_BYTE_ORDER_BIG:
488 bt_bitfield_read_be(buf, uint8_t, at, field_size, v);
489 break;
490 case CTF_BYTE_ORDER_LITTLE:
491 bt_bitfield_read_le(buf, uint8_t, at, field_size, v);
492 break;
493 default:
494 abort();
495 }
496
497 BT_LOGV("Read signed bit array: cur=%zu, size=%u, "
498 "bo=%d, val=%" PRId64, at, field_size, bo, *v);
499}
500
501typedef enum bt_bfcr_status (* read_basic_and_call_cb_t)(struct bt_bfcr *,
502 const uint8_t *, size_t);
503
504static inline
505enum bt_bfcr_status validate_contiguous_bo(struct bt_bfcr *bfcr,
506 enum ctf_byte_order next_bo)
507{
508 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
509
510 /* Always valid when at a byte boundary */
511 if (packet_at(bfcr) % 8 == 0) {
512 goto end;
513 }
514
515 /* Always valid if last byte order is unknown */
516 if (bfcr->last_bo == -1) {
517 goto end;
518 }
519
520 /* Always valid if next byte order is unknown */
521 if (next_bo == -1) {
522 goto end;
523 }
524
525 /* Make sure last byte order is compatible with the next byte order */
526 switch (bfcr->last_bo) {
527 case CTF_BYTE_ORDER_BIG:
528 if (next_bo != CTF_BYTE_ORDER_BIG) {
529 status = BT_BFCR_STATUS_ERROR;
530 }
531 break;
532 case CTF_BYTE_ORDER_LITTLE:
533 if (next_bo != CTF_BYTE_ORDER_LITTLE) {
534 status = BT_BFCR_STATUS_ERROR;
535 }
536 break;
537 default:
538 status = BT_BFCR_STATUS_ERROR;
539 }
540
541end:
542 if (status < 0) {
543 BT_LOGW("Cannot read bit array: two different byte orders not at a byte boundary: "
544 "bfcr-addr=%p, last-bo=%d, next-bo=%d",
545 bfcr, bfcr->last_bo, next_bo);
546 }
547
548 return status;
549}
550
551static
552enum bt_bfcr_status read_basic_float_and_call_cb(struct bt_bfcr *bfcr,
553 const uint8_t *buf, size_t at)
554{
555 double dblval;
556 unsigned int field_size;
557 enum ctf_byte_order bo;
558 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
559 struct ctf_field_class_float *fc = (void *) bfcr->cur_basic_field_class;
560
561 BT_ASSERT(fc);
562 field_size = fc->base.size;
563 bo = fc->base.byte_order;
564 bfcr->cur_bo = bo;
565
566 switch (field_size) {
567 case 32:
568 {
569 uint64_t v;
570 union {
571 uint32_t u;
572 float f;
573 } f32;
574
80870e64 575 read_unsigned_bitfield(bfcr, buf, at, field_size, bo, &v);
5cd6d0e5
PP
576 f32.u = (uint32_t) v;
577 dblval = (double) f32.f;
578 break;
579 }
580 case 64:
581 {
582 union {
583 uint64_t u;
584 double d;
585 } f64;
586
80870e64 587 read_unsigned_bitfield(bfcr, buf, at, field_size, bo, &f64.u);
5cd6d0e5
PP
588 dblval = f64.d;
589 break;
590 }
591 default:
592 /* Only 32-bit and 64-bit fields are supported currently */
593 abort();
594 }
595
596 BT_LOGV("Read floating point number value: bfcr=%p, cur=%zu, val=%f",
597 bfcr, at, dblval);
598
599 if (bfcr->user.cbs.classes.floating_point) {
600 BT_LOGV("Calling user function (floating point number).");
601 status = bfcr->user.cbs.classes.floating_point(dblval,
602 bfcr->cur_basic_field_class, bfcr->user.data);
603 BT_LOGV("User function returned: status=%s",
604 bt_bfcr_status_string(status));
605 if (status != BT_BFCR_STATUS_OK) {
606 BT_LOGW("User function failed: bfcr-addr=%p, status=%s",
607 bfcr, bt_bfcr_status_string(status));
608 }
609 }
610
611 return status;
612}
613
614static inline
615enum bt_bfcr_status read_basic_int_and_call_cb(struct bt_bfcr *bfcr,
616 const uint8_t *buf, size_t at)
617{
618 unsigned int field_size;
619 enum ctf_byte_order bo;
620 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
621 struct ctf_field_class_int *fc = (void *) bfcr->cur_basic_field_class;
622
623 field_size = fc->base.size;
624 bo = fc->base.byte_order;
625
626 /*
627 * Update current byte order now because we could be reading
628 * the integer value of an enumeration class, and thus we know
629 * here the actual supporting integer class's byte order.
630 */
631 bfcr->cur_bo = bo;
632
633 if (fc->is_signed) {
634 int64_t v;
635
80870e64 636 read_signed_bitfield(bfcr, buf, at, field_size, bo, &v);
5cd6d0e5
PP
637
638 if (bfcr->user.cbs.classes.signed_int) {
639 BT_LOGV("Calling user function (signed integer).");
640 status = bfcr->user.cbs.classes.signed_int(v,
641 bfcr->cur_basic_field_class, bfcr->user.data);
642 BT_LOGV("User function returned: status=%s",
643 bt_bfcr_status_string(status));
644 if (status != BT_BFCR_STATUS_OK) {
645 BT_LOGW("User function failed: "
646 "bfcr-addr=%p, status=%s",
647 bfcr, bt_bfcr_status_string(status));
648 }
649 }
650 } else {
651 uint64_t v;
652
80870e64 653 read_unsigned_bitfield(bfcr, buf, at, field_size, bo, &v);
5cd6d0e5
PP
654
655 if (bfcr->user.cbs.classes.unsigned_int) {
656 BT_LOGV("Calling user function (unsigned integer).");
657 status = bfcr->user.cbs.classes.unsigned_int(v,
658 bfcr->cur_basic_field_class, bfcr->user.data);
659 BT_LOGV("User function returned: status=%s",
660 bt_bfcr_status_string(status));
661 if (status != BT_BFCR_STATUS_OK) {
662 BT_LOGW("User function failed: "
663 "bfcr-addr=%p, status=%s",
664 bfcr, bt_bfcr_status_string(status));
665 }
666 }
667 }
668
669 return status;
670}
671
672static inline
673enum bt_bfcr_status read_bit_array_class_and_call_continue(struct bt_bfcr *bfcr,
674 read_basic_and_call_cb_t read_basic_and_call_cb)
675{
676 size_t available;
677 size_t needed_bits;
678 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
679 struct ctf_field_class_bit_array *fc =
680 (void *) bfcr->cur_basic_field_class;
681
682 if (!at_least_one_bit_left(bfcr)) {
683 BT_LOGV("Reached end of data: bfcr-addr=%p", bfcr);
684 status = BT_BFCR_STATUS_EOF;
685 goto end;
686 }
687
688 available = available_bits(bfcr);
689 needed_bits = fc->size - bfcr->stitch.at;
690 BT_LOGV("Continuing basic field decoding: "
fca9fd7d 691 "bfcr-addr=%p, field-size=%u, needed-size=%zu, "
5cd6d0e5
PP
692 "available-size=%zu",
693 bfcr, fc->size, needed_bits, available);
694 if (needed_bits <= available) {
695 /* We have all the bits; append to stitch, then decode */
696 stitch_append_from_buf(bfcr, needed_bits);
697 status = read_basic_and_call_cb(bfcr, bfcr->stitch.buf,
698 bfcr->stitch.offset);
699 if (status != BT_BFCR_STATUS_OK) {
700 BT_LOGW("Cannot read basic field: "
701 "bfcr-addr=%p, fc-addr=%p, status=%s",
702 bfcr, bfcr->cur_basic_field_class,
703 bt_bfcr_status_string(status));
704 goto end;
705 }
706
707 if (stack_empty(bfcr->stack)) {
708 /* Root is a basic class */
709 bfcr->state = BFCR_STATE_DONE;
710 } else {
711 /* Go to next field */
712 stack_top(bfcr->stack)->index++;
713 bfcr->state = BFCR_STATE_NEXT_FIELD;
714 bfcr->last_bo = bfcr->cur_bo;
715 }
716 goto end;
717 }
718
719 /* We are here; it means we don't have enough data to decode this */
720 BT_LOGV_STR("Not enough data to read the next basic field: appending to stitch buffer.");
721 stitch_append_from_remaining_buf(bfcr);
722 status = BT_BFCR_STATUS_EOF;
723
724end:
725 return status;
726}
727
728static inline
729enum bt_bfcr_status read_bit_array_class_and_call_begin(struct bt_bfcr *bfcr,
730 read_basic_and_call_cb_t read_basic_and_call_cb)
731{
732 size_t available;
733 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
734 struct ctf_field_class_bit_array *fc =
735 (void *) bfcr->cur_basic_field_class;
736
737 if (!at_least_one_bit_left(bfcr)) {
738 BT_LOGV("Reached end of data: bfcr-addr=%p", bfcr);
739 status = BT_BFCR_STATUS_EOF;
740 goto end;
741 }
742
743 status = validate_contiguous_bo(bfcr, fc->byte_order);
744 if (status != BT_BFCR_STATUS_OK) {
745 /* validate_contiguous_bo() logs errors */
746 goto end;
747 }
748
749 available = available_bits(bfcr);
750
751 if (fc->size <= available) {
752 /* We have all the bits; decode and set now */
753 BT_ASSERT(bfcr->buf.addr);
754 status = read_basic_and_call_cb(bfcr, bfcr->buf.addr,
755 buf_at_from_addr(bfcr));
756 if (status != BT_BFCR_STATUS_OK) {
757 BT_LOGW("Cannot read basic field: "
758 "bfcr-addr=%p, fc-addr=%p, status=%s",
759 bfcr, bfcr->cur_basic_field_class,
760 bt_bfcr_status_string(status));
761 goto end;
762 }
763
764 consume_bits(bfcr, fc->size);
765
766 if (stack_empty(bfcr->stack)) {
767 /* Root is a basic class */
768 bfcr->state = BFCR_STATE_DONE;
769 } else {
770 /* Go to next field */
771 stack_top(bfcr->stack)->index++;
772 bfcr->state = BFCR_STATE_NEXT_FIELD;
773 bfcr->last_bo = bfcr->cur_bo;
774 }
775
776 goto end;
777 }
778
779 /* We are here; it means we don't have enough data to decode this */
780 BT_LOGV_STR("Not enough data to read the next basic field: setting stitch buffer.");
781 stitch_set_from_remaining_buf(bfcr);
782 bfcr->state = BFCR_STATE_READ_BASIC_CONTINUE;
783 status = BT_BFCR_STATUS_EOF;
784
785end:
786 return status;
787}
788
789static inline
790enum bt_bfcr_status read_basic_int_class_and_call_begin(
791 struct bt_bfcr *bfcr)
792{
793 return read_bit_array_class_and_call_begin(bfcr, read_basic_int_and_call_cb);
794}
795
796static inline
797enum bt_bfcr_status read_basic_int_class_and_call_continue(
798 struct bt_bfcr *bfcr)
799{
800 return read_bit_array_class_and_call_continue(bfcr,
801 read_basic_int_and_call_cb);
802}
803
804static inline
805enum bt_bfcr_status read_basic_float_class_and_call_begin(
806 struct bt_bfcr *bfcr)
807{
808 return read_bit_array_class_and_call_begin(bfcr,
809 read_basic_float_and_call_cb);
810}
811
812static inline
813enum bt_bfcr_status read_basic_float_class_and_call_continue(
814 struct bt_bfcr *bfcr)
815{
816 return read_bit_array_class_and_call_continue(bfcr,
817 read_basic_float_and_call_cb);
818}
819
820static inline
821enum bt_bfcr_status read_basic_string_class_and_call(
822 struct bt_bfcr *bfcr, bool begin)
823{
824 size_t buf_at_bytes;
825 const uint8_t *result;
826 size_t available_bytes;
827 const uint8_t *first_chr;
828 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
829
830 if (!at_least_one_bit_left(bfcr)) {
831 BT_LOGV("Reached end of data: bfcr-addr=%p", bfcr);
832 status = BT_BFCR_STATUS_EOF;
833 goto end;
834 }
835
836 BT_ASSERT(buf_at_from_addr(bfcr) % 8 == 0);
837 available_bytes = BITS_TO_BYTES_FLOOR(available_bits(bfcr));
838 buf_at_bytes = BITS_TO_BYTES_FLOOR(buf_at_from_addr(bfcr));
839 BT_ASSERT(bfcr->buf.addr);
840 first_chr = &bfcr->buf.addr[buf_at_bytes];
841 result = memchr(first_chr, '\0', available_bytes);
842
843 if (begin && bfcr->user.cbs.classes.string_begin) {
844 BT_LOGV("Calling user function (string, beginning).");
845 status = bfcr->user.cbs.classes.string_begin(
846 bfcr->cur_basic_field_class, bfcr->user.data);
847 BT_LOGV("User function returned: status=%s",
848 bt_bfcr_status_string(status));
849 if (status != BT_BFCR_STATUS_OK) {
850 BT_LOGW("User function failed: bfcr-addr=%p, status=%s",
851 bfcr, bt_bfcr_status_string(status));
852 goto end;
853 }
854 }
855
856 if (!result) {
857 /* No null character yet */
858 if (bfcr->user.cbs.classes.string) {
859 BT_LOGV("Calling user function (substring).");
860 status = bfcr->user.cbs.classes.string(
861 (const char *) first_chr,
862 available_bytes, bfcr->cur_basic_field_class,
863 bfcr->user.data);
864 BT_LOGV("User function returned: status=%s",
865 bt_bfcr_status_string(status));
866 if (status != BT_BFCR_STATUS_OK) {
867 BT_LOGW("User function failed: "
868 "bfcr-addr=%p, status=%s",
869 bfcr, bt_bfcr_status_string(status));
870 goto end;
871 }
872 }
873
874 consume_bits(bfcr, BYTES_TO_BITS(available_bytes));
875 bfcr->state = BFCR_STATE_READ_BASIC_CONTINUE;
876 status = BT_BFCR_STATUS_EOF;
877 } else {
878 /* Found the null character */
879 size_t result_len = (size_t) (result - first_chr);
880
881 if (bfcr->user.cbs.classes.string && result_len) {
882 BT_LOGV("Calling user function (substring).");
883 status = bfcr->user.cbs.classes.string(
884 (const char *) first_chr,
885 result_len, bfcr->cur_basic_field_class,
886 bfcr->user.data);
887 BT_LOGV("User function returned: status=%s",
888 bt_bfcr_status_string(status));
889 if (status != BT_BFCR_STATUS_OK) {
890 BT_LOGW("User function failed: "
891 "bfcr-addr=%p, status=%s",
892 bfcr, bt_bfcr_status_string(status));
893 goto end;
894 }
895 }
896
897 if (bfcr->user.cbs.classes.string_end) {
898 BT_LOGV("Calling user function (string, end).");
899 status = bfcr->user.cbs.classes.string_end(
900 bfcr->cur_basic_field_class, bfcr->user.data);
901 BT_LOGV("User function returned: status=%s",
902 bt_bfcr_status_string(status));
903 if (status != BT_BFCR_STATUS_OK) {
904 BT_LOGW("User function failed: "
905 "bfcr-addr=%p, status=%s",
906 bfcr, bt_bfcr_status_string(status));
907 goto end;
908 }
909 }
910
911 consume_bits(bfcr, BYTES_TO_BITS(result_len + 1));
912
913 if (stack_empty(bfcr->stack)) {
914 /* Root is a basic class */
915 bfcr->state = BFCR_STATE_DONE;
916 } else {
917 /* Go to next field */
918 stack_top(bfcr->stack)->index++;
919 bfcr->state = BFCR_STATE_NEXT_FIELD;
920 bfcr->last_bo = bfcr->cur_bo;
921 }
922 }
923
924end:
925 return status;
926}
927
928static inline
929enum bt_bfcr_status read_basic_begin_state(struct bt_bfcr *bfcr)
930{
931 enum bt_bfcr_status status;
932
933 BT_ASSERT(bfcr->cur_basic_field_class);
934
864cad70
PP
935 switch (bfcr->cur_basic_field_class->type) {
936 case CTF_FIELD_CLASS_TYPE_INT:
937 case CTF_FIELD_CLASS_TYPE_ENUM:
5cd6d0e5
PP
938 status = read_basic_int_class_and_call_begin(bfcr);
939 break;
864cad70 940 case CTF_FIELD_CLASS_TYPE_FLOAT:
5cd6d0e5
PP
941 status = read_basic_float_class_and_call_begin(bfcr);
942 break;
864cad70 943 case CTF_FIELD_CLASS_TYPE_STRING:
5cd6d0e5
PP
944 status = read_basic_string_class_and_call(bfcr, true);
945 break;
946 default:
947 abort();
948 }
949
950 return status;
951}
952
953static inline
954enum bt_bfcr_status read_basic_continue_state(struct bt_bfcr *bfcr)
955{
956 enum bt_bfcr_status status;
957
958 BT_ASSERT(bfcr->cur_basic_field_class);
959
864cad70
PP
960 switch (bfcr->cur_basic_field_class->type) {
961 case CTF_FIELD_CLASS_TYPE_INT:
962 case CTF_FIELD_CLASS_TYPE_ENUM:
5cd6d0e5
PP
963 status = read_basic_int_class_and_call_continue(bfcr);
964 break;
864cad70 965 case CTF_FIELD_CLASS_TYPE_FLOAT:
5cd6d0e5
PP
966 status = read_basic_float_class_and_call_continue(bfcr);
967 break;
864cad70 968 case CTF_FIELD_CLASS_TYPE_STRING:
5cd6d0e5
PP
969 status = read_basic_string_class_and_call(bfcr, false);
970 break;
971 default:
972 abort();
973 }
974
975 return status;
976}
977
978static inline
979size_t bits_to_skip_to_align_to(struct bt_bfcr *bfcr, size_t align)
980{
981 size_t aligned_packet_at;
982
983 aligned_packet_at = ALIGN(packet_at(bfcr), align);
984 return aligned_packet_at - packet_at(bfcr);
985}
986
987static inline
988enum bt_bfcr_status align_class_state(struct bt_bfcr *bfcr,
989 struct ctf_field_class *field_class, enum bfcr_state next_state)
990{
991 unsigned int field_alignment;
992 size_t skip_bits;
993 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
994
995 /* Get field's alignment */
996 field_alignment = field_class->alignment;
997
998 /*
999 * 0 means "undefined" for variants; what we really want is 1
1000 * (always aligned)
1001 */
1002 BT_ASSERT(field_alignment >= 1);
1003
1004 /* Compute how many bits we need to skip */
1005 skip_bits = bits_to_skip_to_align_to(bfcr, (size_t) field_alignment);
1006
1007 /* Nothing to skip? aligned */
1008 if (skip_bits == 0) {
1009 bfcr->state = next_state;
1010 goto end;
1011 }
1012
1013 /* Make sure there's at least one bit left */
1014 if (!at_least_one_bit_left(bfcr)) {
1015 status = BT_BFCR_STATUS_EOF;
1016 goto end;
1017 }
1018
1019 /* Consume as many bits as possible in what's left */
1020 consume_bits(bfcr, MIN(available_bits(bfcr), skip_bits));
1021
1022 /* Are we done now? */
1023 skip_bits = bits_to_skip_to_align_to(bfcr, field_alignment);
1024 if (skip_bits == 0) {
1025 /* Yes: go to next state */
1026 bfcr->state = next_state;
1027 goto end;
1028 } else {
1029 /* No: need more data */
1030 BT_LOGV("Reached end of data when aligning: bfcr-addr=%p", bfcr);
1031 status = BT_BFCR_STATUS_EOF;
1032 }
1033
1034end:
1035 return status;
1036}
1037
1038static inline
1039enum bt_bfcr_status next_field_state(struct bt_bfcr *bfcr)
1040{
1041 int ret;
1042 struct stack_entry *top;
1043 struct ctf_field_class *next_field_class = NULL;
1044 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
1045
1046 if (stack_empty(bfcr->stack)) {
1047 goto end;
1048 }
1049
1050 top = stack_top(bfcr->stack);
1051
1052 /* Are we done with this base class? */
1053 while (top->index == top->base_len) {
1054 if (bfcr->user.cbs.classes.compound_end) {
1055 BT_LOGV("Calling user function (compound, end).");
1056 status = bfcr->user.cbs.classes.compound_end(
1057 top->base_class, bfcr->user.data);
1058 BT_LOGV("User function returned: status=%s",
1059 bt_bfcr_status_string(status));
1060 if (status != BT_BFCR_STATUS_OK) {
1061 BT_LOGW("User function failed: bfcr-addr=%p, status=%s",
1062 bfcr, bt_bfcr_status_string(status));
1063 goto end;
1064 }
1065 }
1066
1067 stack_pop(bfcr->stack);
1068
1069 /* Are we done with the root class? */
1070 if (stack_empty(bfcr->stack)) {
1071 bfcr->state = BFCR_STATE_DONE;
1072 goto end;
1073 }
1074
1075 top = stack_top(bfcr->stack);
1076 top->index++;
1077 }
1078
1079 /* Get next field's class */
864cad70
PP
1080 switch (top->base_class->type) {
1081 case CTF_FIELD_CLASS_TYPE_STRUCT:
5cd6d0e5
PP
1082 next_field_class = ctf_field_class_struct_borrow_member_by_index(
1083 (void *) top->base_class, (uint64_t) top->index)->fc;
1084 break;
864cad70
PP
1085 case CTF_FIELD_CLASS_TYPE_ARRAY:
1086 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
5cd6d0e5
PP
1087 {
1088 struct ctf_field_class_array_base *array_fc =
1089 (void *) top->base_class;
1090
1091 next_field_class = array_fc->elem_fc;
1092 break;
1093 }
864cad70 1094 case CTF_FIELD_CLASS_TYPE_VARIANT:
5cd6d0e5
PP
1095 /* Variant classes are dynamic: the user should know! */
1096 next_field_class =
1097 bfcr->user.cbs.query.borrow_variant_selected_field_class(
1098 top->base_class, bfcr->user.data);
1099 break;
1100 default:
1101 break;
1102 }
1103
1104 if (!next_field_class) {
1105 BT_LOGW("Cannot get the field class of the next field: "
864cad70 1106 "bfcr-addr=%p, base-fc-addr=%p, base-fc-type=%d, "
5cd6d0e5 1107 "index=%" PRId64,
864cad70
PP
1108 bfcr, top->base_class, top->base_class->type,
1109 top->index);
5cd6d0e5
PP
1110 status = BT_BFCR_STATUS_ERROR;
1111 goto end;
1112 }
1113
1114 if (next_field_class->is_compound) {
1115 if (bfcr->user.cbs.classes.compound_begin) {
1116 BT_LOGV("Calling user function (compound, begin).");
1117 status = bfcr->user.cbs.classes.compound_begin(
1118 next_field_class, bfcr->user.data);
1119 BT_LOGV("User function returned: status=%s",
1120 bt_bfcr_status_string(status));
1121 if (status != BT_BFCR_STATUS_OK) {
1122 BT_LOGW("User function failed: bfcr-addr=%p, status=%s",
1123 bfcr, bt_bfcr_status_string(status));
1124 goto end;
1125 }
1126 }
1127
1128 ret = stack_push_with_len(bfcr, next_field_class);
1129 if (ret) {
1130 /* stack_push_with_len() logs errors */
1131 status = BT_BFCR_STATUS_ERROR;
1132 goto end;
1133 }
1134
1135 /* Next state: align a compound class */
1136 bfcr->state = BFCR_STATE_ALIGN_COMPOUND;
1137 } else {
1138 /* Replace current basic field class */
1139 BT_LOGV("Replacing current basic field class: "
1140 "bfcr-addr=%p, cur-basic-fc-addr=%p, "
1141 "next-basic-fc-addr=%p",
1142 bfcr, bfcr->cur_basic_field_class, next_field_class);
1143 bfcr->cur_basic_field_class = next_field_class;
1144
1145 /* Next state: align a basic class */
1146 bfcr->state = BFCR_STATE_ALIGN_BASIC;
1147 }
1148
1149end:
1150 return status;
1151}
1152
1153static inline
1154enum bt_bfcr_status handle_state(struct bt_bfcr *bfcr)
1155{
1156 enum bt_bfcr_status status = BT_BFCR_STATUS_OK;
1157
1158 BT_LOGV("Handling state: bfcr-addr=%p, state=%s",
1159 bfcr, bfcr_state_string(bfcr->state));
1160
1161 switch (bfcr->state) {
1162 case BFCR_STATE_NEXT_FIELD:
1163 status = next_field_state(bfcr);
1164 break;
1165 case BFCR_STATE_ALIGN_BASIC:
1166 status = align_class_state(bfcr, bfcr->cur_basic_field_class,
1167 BFCR_STATE_READ_BASIC_BEGIN);
1168 break;
1169 case BFCR_STATE_ALIGN_COMPOUND:
1170 status = align_class_state(bfcr, stack_top(bfcr->stack)->base_class,
1171 BFCR_STATE_NEXT_FIELD);
1172 break;
1173 case BFCR_STATE_READ_BASIC_BEGIN:
1174 status = read_basic_begin_state(bfcr);
1175 break;
1176 case BFCR_STATE_READ_BASIC_CONTINUE:
1177 status = read_basic_continue_state(bfcr);
1178 break;
1179 case BFCR_STATE_DONE:
1180 break;
1181 }
1182
1183 BT_LOGV("Handled state: bfcr-addr=%p, status=%s",
1184 bfcr, bt_bfcr_status_string(status));
1185 return status;
1186}
1187
1188BT_HIDDEN
80870e64
PP
1189struct bt_bfcr *bt_bfcr_create(struct bt_bfcr_cbs cbs, void *data,
1190 bt_logging_level log_level)
5cd6d0e5
PP
1191{
1192 struct bt_bfcr *bfcr;
1193
80870e64
PP
1194 BT_LOG_WRITE_CUR_LVL(BT_LOG_DEBUG, log_level, BT_LOG_TAG,
1195 "Creating binary field class reader (BFCR).");
5cd6d0e5
PP
1196 bfcr = g_new0(struct bt_bfcr, 1);
1197 if (!bfcr) {
80870e64
PP
1198 BT_LOG_WRITE_CUR_LVL(BT_LOG_ERROR, log_level, BT_LOG_TAG,
1199 "Failed to allocate one binary class reader.");
5cd6d0e5
PP
1200 goto end;
1201 }
1202
80870e64
PP
1203 bfcr->log_level = log_level;
1204 bfcr->stack = stack_new(bfcr);
5cd6d0e5
PP
1205 if (!bfcr->stack) {
1206 BT_LOGE_STR("Cannot create BFCR's stack.");
1207 bt_bfcr_destroy(bfcr);
1208 bfcr = NULL;
1209 goto end;
1210 }
1211
1212 bfcr->state = BFCR_STATE_NEXT_FIELD;
1213 bfcr->user.cbs = cbs;
1214 bfcr->user.data = data;
1215 BT_LOGD("Created BFCR: addr=%p", bfcr);
1216
1217end:
1218 return bfcr;
1219}
1220
1221BT_HIDDEN
1222void bt_bfcr_destroy(struct bt_bfcr *bfcr)
1223{
1224 if (bfcr->stack) {
1225 stack_destroy(bfcr->stack);
1226 }
1227
1228 BT_LOGD("Destroying BFCR: addr=%p", bfcr);
1229 g_free(bfcr);
1230}
1231
1232static
1233void reset(struct bt_bfcr *bfcr)
1234{
1235 BT_LOGD("Resetting BFCR: addr=%p", bfcr);
1236 stack_clear(bfcr->stack);
1237 stitch_reset(bfcr);
1238 bfcr->buf.addr = NULL;
1239 bfcr->last_bo = -1;
1240}
1241
1242static
1243void update_packet_offset(struct bt_bfcr *bfcr)
1244{
1245 BT_LOGV("Updating packet offset for next call: "
1246 "bfcr-addr=%p, cur-packet-offset=%zu, next-packet-offset=%zu",
1247 bfcr, bfcr->buf.packet_offset,
1248 bfcr->buf.packet_offset + bfcr->buf.at);
1249 bfcr->buf.packet_offset += bfcr->buf.at;
1250}
1251
1252BT_HIDDEN
1253size_t bt_bfcr_start(struct bt_bfcr *bfcr,
1254 struct ctf_field_class *cls, const uint8_t *buf,
1255 size_t offset, size_t packet_offset, size_t sz,
1256 enum bt_bfcr_status *status)
1257{
1258 BT_ASSERT(bfcr);
1259 BT_ASSERT(BYTES_TO_BITS(sz) >= offset);
1260 reset(bfcr);
1261 bfcr->buf.addr = buf;
1262 bfcr->buf.offset = offset;
1263 bfcr->buf.at = 0;
1264 bfcr->buf.packet_offset = packet_offset;
1265 bfcr->buf.buf_sz = sz;
1266 bfcr->buf.sz = BYTES_TO_BITS(sz) - offset;
1267 *status = BT_BFCR_STATUS_OK;
1268
1269 BT_LOGV("Starting decoding: bfcr-addr=%p, fc-addr=%p, "
1270 "buf-addr=%p, buf-size=%zu, offset=%zu, "
1271 "packet-offset=%zu",
1272 bfcr, cls, buf, sz, offset, packet_offset);
1273
1274 /* Set root class */
1275 if (cls->is_compound) {
1276 /* Compound class: push on visit stack */
1277 int stack_ret;
1278
1279 if (bfcr->user.cbs.classes.compound_begin) {
1280 BT_LOGV("Calling user function (compound, begin).");
1281 *status = bfcr->user.cbs.classes.compound_begin(
1282 cls, bfcr->user.data);
1283 BT_LOGV("User function returned: status=%s",
1284 bt_bfcr_status_string(*status));
1285 if (*status != BT_BFCR_STATUS_OK) {
1286 BT_LOGW("User function failed: bfcr-addr=%p, status=%s",
1287 bfcr, bt_bfcr_status_string(*status));
1288 goto end;
1289 }
1290 }
1291
1292 stack_ret = stack_push_with_len(bfcr, cls);
1293 if (stack_ret) {
1294 /* stack_push_with_len() logs errors */
1295 *status = BT_BFCR_STATUS_ERROR;
1296 goto end;
1297 }
1298
1299 bfcr->state = BFCR_STATE_ALIGN_COMPOUND;
1300 } else {
1301 /* Basic class: set as current basic class */
1302 bfcr->cur_basic_field_class = cls;
1303 bfcr->state = BFCR_STATE_ALIGN_BASIC;
1304 }
1305
1306 /* Run the machine! */
1307 BT_LOGV_STR("Running the state machine.");
1308
1309 while (true) {
1310 *status = handle_state(bfcr);
1311 if (*status != BT_BFCR_STATUS_OK ||
1312 bfcr->state == BFCR_STATE_DONE) {
1313 break;
1314 }
1315 }
1316
1317 /* Update packet offset for next time */
1318 update_packet_offset(bfcr);
1319
1320end:
1321 return bfcr->buf.at;
1322}
1323
1324BT_HIDDEN
1325size_t bt_bfcr_continue(struct bt_bfcr *bfcr, const uint8_t *buf, size_t sz,
1326 enum bt_bfcr_status *status)
1327{
1328 BT_ASSERT(bfcr);
1329 BT_ASSERT(buf);
1330 BT_ASSERT(sz > 0);
1331 bfcr->buf.addr = buf;
1332 bfcr->buf.offset = 0;
1333 bfcr->buf.at = 0;
1334 bfcr->buf.buf_sz = sz;
1335 bfcr->buf.sz = BYTES_TO_BITS(sz);
1336 *status = BT_BFCR_STATUS_OK;
1337
1338 BT_LOGV("Continuing decoding: bfcr-addr=%p, buf-addr=%p, buf-size=%zu",
1339 bfcr, buf, sz);
1340
1341 /* Continue running the machine */
1342 BT_LOGV_STR("Running the state machine.");
1343
1344 while (true) {
1345 *status = handle_state(bfcr);
1346 if (*status != BT_BFCR_STATUS_OK ||
1347 bfcr->state == BFCR_STATE_DONE) {
1348 break;
1349 }
1350 }
1351
1352 /* Update packet offset for next time */
1353 update_packet_offset(bfcr);
1354 return bfcr->buf.at;
1355}
1356
1357BT_HIDDEN
1358void bt_bfcr_set_unsigned_int_cb(struct bt_bfcr *bfcr,
1359 bt_bfcr_unsigned_int_cb_func cb)
1360{
1361 BT_ASSERT(bfcr);
1362 BT_ASSERT(cb);
1363 bfcr->user.cbs.classes.unsigned_int = cb;
1364}
This page took 0.084009 seconds and 4 git commands to generate.