blob: fe477e5f9b4ddb0f8fa7a639343a0d74df90e049 [file] [edit]
#include "tiny_gltf_v3.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ===== Digest helpers (used to compare v1 vs v3 parses) ===================== */
static uint64_t fnv64(const uint8_t *data, uint64_t n) {
uint64_t h = 0xcbf29ce484222325ULL;
uint64_t i;
for (i = 0; i < n; ++i) { h ^= data[i]; h *= 0x100000001b3ULL; }
return h;
}
static void d_str(const tg3_str *s) {
uint32_t i;
putchar('"');
if (s && s->data) {
for (i = 0; i < s->len; ++i) {
unsigned char c = (unsigned char)s->data[i];
if (c == '"' || c == '\\') { putchar('\\'); putchar((char)c); }
else if (c < 0x20 || c >= 0x7f) putchar('?');
else putchar((char)c);
}
}
putchar('"');
}
static void d_dbl(double v) { printf("%.7g", v); }
static void d_dbl_arr(const double *v, uint32_t n) {
uint32_t i;
putchar('[');
for (i = 0; i < n; ++i) { if (i) putchar(','); d_dbl(v[i]); }
putchar(']');
}
static int cmp_str_int_pair(const void *a, const void *b) {
const tg3_str_int_pair *pa = (const tg3_str_int_pair *)a;
const tg3_str_int_pair *pb = (const tg3_str_int_pair *)b;
uint32_t la = pa->key.len, lb = pb->key.len;
uint32_t m = la < lb ? la : lb;
int r = memcmp(pa->key.data, pb->key.data, m);
if (r) return r;
return (la < lb) ? -1 : (la > lb ? 1 : 0);
}
static void d_attrs(const tg3_str_int_pair *attrs, uint32_t n) {
tg3_str_int_pair *sorted;
uint32_t i;
if (n == 0) { fputs("[]", stdout); return; }
sorted = (tg3_str_int_pair *)malloc(n * sizeof(*sorted));
memcpy(sorted, attrs, n * sizeof(*sorted));
qsort(sorted, n, sizeof(*sorted), cmp_str_int_pair);
putchar('[');
for (i = 0; i < n; ++i) {
if (i) putchar(',');
printf("%.*s:%d", (int)sorted[i].key.len, sorted[i].key.data, sorted[i].value);
}
putchar(']');
free(sorted);
}
static void print_digest(const tg3_model *m) {
uint32_t i, j;
printf("DIGEST_BEGIN\n");
printf("asset version=");
d_str(&m->asset.version);
printf(" generator=");
d_str(&m->asset.generator);
printf("\n");
for (i = 0; i < m->buffers_count; ++i) {
const tg3_buffer *b = &m->buffers[i];
uint64_t h = b->data.data ? fnv64(b->data.data, b->data.count) : 0;
printf("buffer %u byte_length=%llu fnv64=0x%016llx\n",
i, (unsigned long long)b->data.count, (unsigned long long)h);
}
for (i = 0; i < m->buffer_views_count; ++i) {
const tg3_buffer_view *bv = &m->buffer_views[i];
printf("buffer_view %u buffer=%d byte_offset=%llu byte_length=%llu byte_stride=%u\n",
i, bv->buffer, (unsigned long long)bv->byte_offset,
(unsigned long long)bv->byte_length, bv->byte_stride);
}
for (i = 0; i < m->accessors_count; ++i) {
const tg3_accessor *a = &m->accessors[i];
printf("accessor %u buffer_view=%d byte_offset=%llu component_type=%d count=%llu type=%d normalized=%d min=",
i, a->buffer_view, (unsigned long long)a->byte_offset, a->component_type,
(unsigned long long)a->count, a->type, a->normalized);
d_dbl_arr(a->min_values, a->min_values_count);
printf(" max=");
d_dbl_arr(a->max_values, a->max_values_count);
printf(" sparse=%d\n", a->sparse.is_sparse);
}
for (i = 0; i < m->meshes_count; ++i) {
const tg3_mesh *me = &m->meshes[i];
printf("mesh %u primitives_count=%u weights_count=%u\n",
i, me->primitives_count, me->weights_count);
for (j = 0; j < me->primitives_count; ++j) {
const tg3_primitive *p = &me->primitives[j];
printf("prim %u %u indices=%d material=%d mode=%d attrs=", i, j,
p->indices, p->material, p->mode);
d_attrs(p->attributes, p->attributes_count);
printf(" targets_count=%u\n", p->targets_count);
}
}
for (i = 0; i < m->nodes_count; ++i) {
const tg3_node *n = &m->nodes[i];
printf("node %u mesh=%d skin=%d camera=%d light=%d children_count=%u has_matrix=%d t=",
i, n->mesh, n->skin, n->camera, n->light, n->children_count, n->has_matrix);
d_dbl_arr(n->translation, 3);
printf(" r=");
d_dbl_arr(n->rotation, 4);
printf(" s=");
d_dbl_arr(n->scale, 3);
printf(" matrix=");
d_dbl_arr(n->matrix, 16);
printf(" weights_count=%u\n", n->weights_count);
}
for (i = 0; i < m->materials_count; ++i) {
const tg3_material *mat = &m->materials[i];
printf("material %u alpha_mode=", i);
d_str(&mat->alpha_mode);
printf(" alpha_cutoff=");
d_dbl(mat->alpha_cutoff);
printf(" double_sided=%d emissive=", mat->double_sided);
d_dbl_arr(mat->emissive_factor, 3);
printf(" base_color_factor=");
d_dbl_arr(mat->pbr_metallic_roughness.base_color_factor, 4);
printf(" metallic=");
d_dbl(mat->pbr_metallic_roughness.metallic_factor);
printf(" roughness=");
d_dbl(mat->pbr_metallic_roughness.roughness_factor);
printf(" base_color_tex=%d normal_tex=%d occlusion_tex=%d emissive_tex=%d\n",
mat->pbr_metallic_roughness.base_color_texture.index,
mat->normal_texture.index,
mat->occlusion_texture.index,
mat->emissive_texture.index);
}
for (i = 0; i < m->textures_count; ++i) {
const tg3_texture *t = &m->textures[i];
printf("texture %u source=%d sampler=%d\n", i, t->source, t->sampler);
}
for (i = 0; i < m->samplers_count; ++i) {
const tg3_sampler *s = &m->samplers[i];
printf("sampler %u min_filter=%d mag_filter=%d wrap_s=%d wrap_t=%d\n",
i, s->min_filter, s->mag_filter, s->wrap_s, s->wrap_t);
}
for (i = 0; i < m->images_count; ++i) {
const tg3_image *im = &m->images[i];
/* mime_type and uri normalization differ between v1/v3 (data URIs,
extension inference); buffer_view reference is the parse-fidelity bit. */
printf("image %u buffer_view=%d\n", i, im->buffer_view);
}
for (i = 0; i < m->skins_count; ++i) {
const tg3_skin *s = &m->skins[i];
printf("skin %u inverse_bind_matrices=%d skeleton=%d joints_count=%u\n",
i, s->inverse_bind_matrices, s->skeleton, s->joints_count);
}
for (i = 0; i < m->animations_count; ++i) {
const tg3_animation *a = &m->animations[i];
printf("animation %u channels_count=%u samplers_count=%u\n",
i, a->channels_count, a->samplers_count);
for (j = 0; j < a->channels_count; ++j) {
const tg3_animation_channel *c = &a->channels[j];
printf("chan %u %u sampler=%d target_node=%d target_path=", i, j,
c->sampler, c->target.node);
d_str(&c->target.path);
printf("\n");
}
for (j = 0; j < a->samplers_count; ++j) {
const tg3_animation_sampler *as = &a->samplers[j];
printf("samp %u %u input=%d output=%d interpolation=", i, j,
as->input, as->output);
d_str(&as->interpolation);
printf("\n");
}
}
for (i = 0; i < m->cameras_count; ++i) {
const tg3_camera *c = &m->cameras[i];
int is_persp = (c->type.len == 11 && memcmp(c->type.data, "perspective", 11) == 0);
printf("camera %u type=", i);
d_str(&c->type);
if (is_persp) {
printf(" yfov=");
d_dbl(c->perspective.yfov);
printf(" znear=");
d_dbl(c->perspective.znear);
printf(" zfar=");
d_dbl(c->perspective.zfar);
printf(" aspect=");
d_dbl(c->perspective.aspect_ratio);
} else {
printf(" xmag=");
d_dbl(c->orthographic.xmag);
printf(" ymag=");
d_dbl(c->orthographic.ymag);
printf(" znear=");
d_dbl(c->orthographic.znear);
printf(" zfar=");
d_dbl(c->orthographic.zfar);
}
printf("\n");
}
for (i = 0; i < m->scenes_count; ++i) {
const tg3_scene *s = &m->scenes[i];
printf("scene %u nodes_count=%u\n", i, s->nodes_count);
}
printf("DIGEST_END\n");
}
/* ============================================================================ */
static int mem_contains(const uint8_t *data, uint64_t size, const char *needle) {
size_t needle_len = strlen(needle);
uint64_t i;
if (needle_len == 0 || size < (uint64_t)needle_len) {
return 0;
}
for (i = 0; i + (uint64_t)needle_len <= size; ++i) {
if (memcmp(data + i, needle, needle_len) == 0) {
return 1;
}
}
return 0;
}
static int check_minimal_parse(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"scene\":0,"
"\"scenes\":[{\"nodes\":[0]}],"
"\"nodes\":[{\"name\":\"root\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_auto(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0,
&opts);
if (err != TG3_OK) {
fprintf(stderr, "tg3_parse_auto failed: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
if (model.default_scene != 0 || model.scenes_count != 1 || model.nodes_count != 1) {
fprintf(stderr, "unexpected parsed model shape\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (!model.nodes || !model.nodes[0].name.data ||
!tg3_str_equals_cstr(model.nodes[0].name, "root")) {
fprintf(stderr, "node name mismatch\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_minimal_write_roundtrip(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"scene\":0,"
"\"scenes\":[{\"nodes\":[0]}],"
"\"nodes\":[{\"name\":\"root\"}]}";
tg3_model model;
tg3_model roundtrip;
tg3_error_stack errors;
tg3_parse_options parse_opts;
tg3_write_options write_opts;
tg3_error_code err;
uint8_t *out = NULL;
uint64_t out_size = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&parse_opts);
tg3_write_options_init(&write_opts);
err = tg3_parse_auto(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0,
&parse_opts);
if (err != TG3_OK) {
fprintf(stderr, "initial parse failed: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts);
if (err != TG3_OK || !out || out_size == 0) {
fprintf(stderr, "tg3_write_to_memory failed: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (!mem_contains(out, out_size, "\"asset\"") ||
!mem_contains(out, out_size, "\"root\"")) {
fprintf(stderr, "serialized JSON missing expected fields\n");
tg3_write_free(out, &write_opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
err = tg3_parse_auto(&roundtrip, &errors, out, out_size, "", 0, &parse_opts);
tg3_write_free(out, &write_opts);
if (err != TG3_OK) {
fprintf(stderr, "roundtrip parse failed: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (roundtrip.default_scene != 0 || roundtrip.nodes_count != 1 ||
!roundtrip.nodes || !roundtrip.nodes[0].name.data ||
!tg3_str_equals_cstr(roundtrip.nodes[0].name, "root")) {
fprintf(stderr, "roundtrip model mismatch\n");
tg3_model_free(&roundtrip);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&roundtrip);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_parse_file_failure_initializes_model(void) {
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
memset(&model, 0xA5, sizeof(model));
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_file(&model, &errors,
"tg3-tester-nonexistent-path.gltf", 32, &opts);
if (err != TG3_ERR_FS_NOT_AVAILABLE && err != TG3_ERR_FILE_NOT_FOUND) {
fprintf(stderr, "tg3_parse_file unexpected error: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
if (model.default_scene != -1) {
fprintf(stderr, "tg3_parse_file did not initialize model on failure\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_non_object_root_rejected(void) {
static const uint8_t json[] = "\"not an object\"";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_JSON_PARSE) {
fprintf(stderr, "non-object root returned unexpected error: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (model.default_scene != -1) {
fprintf(stderr, "non-object root left model in unexpected state\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_huge_integer_field_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"scene\":6.66667e+70}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_JSON_PARSE) {
fprintf(stderr, "huge integer-like field returned unexpected error: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
/* ===== Security regression tests ============================================ */
/* fs read_file callback that records calls into *(int *)user_data and never
* succeeds — used to verify path-traversal URIs never reach the filesystem. */
static int32_t recording_read_file(uint8_t **out_data, uint64_t *out_size,
const char *path, uint32_t path_len,
void *user_data) {
(void)out_data; (void)out_size; (void)path; (void)path_len;
if (user_data) *(int *)user_data += 1;
return 0;
}
static int check_path_traversal_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"../../etc/passwd\",\"byteLength\":4}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int fs_calls = 0;
uint32_t i;
int saw = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.fs.read_file = recording_read_file;
opts.fs.user_data = &fs_calls;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1),
"/some/base", 10, &opts);
if (err == TG3_OK) {
fprintf(stderr, "path traversal NOT rejected\n");
goto fail;
}
if (fs_calls != 0) {
fprintf(stderr, "fs.read_file called %d times for traversal URI\n", fs_calls);
goto fail;
}
for (i = 0; i < errors.count; ++i) {
if (errors.entries[i].code == TG3_ERR_INVALID_VALUE) saw = 1;
}
if (!saw) {
fprintf(stderr, "expected TG3_ERR_INVALID_VALUE on traversal\n");
goto fail;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
fail:
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
static int check_absolute_uri_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"/etc/passwd\",\"byteLength\":4}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int fs_calls = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.fs.read_file = recording_read_file;
opts.fs.user_data = &fs_calls;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1),
"/base", 5, &opts);
if (err == TG3_OK || fs_calls != 0) {
fprintf(stderr, "absolute URI not rejected (rc=%d, fs_calls=%d)\n",
(int)err, fs_calls);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_negative_byte_stride_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4,\"byteStride\":-1}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err == TG3_OK) {
fprintf(stderr, "negative byteStride NOT rejected\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_oob_index_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4}],"
"\"accessors\":[{\"bufferView\":1000000,\"componentType\":5121,"
"\"count\":1,\"type\":\"SCALAR\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_INDEX) {
fprintf(stderr, "OOB index expected TG3_ERR_INVALID_INDEX, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_oob_index_opt_in(void) {
/* When validate_indices=0, parse should accept the same out-of-range index. */
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4}],"
"\"accessors\":[{\"bufferView\":1000000,\"componentType\":5121,"
"\"count\":1,\"type\":\"SCALAR\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.validate_indices = 0;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_OK) {
fprintf(stderr, "validate_indices=0 should accept OOB index, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (model.accessors_count != 1 || model.accessors[0].buffer_view != 1000000) {
fprintf(stderr, "OOB index not preserved when validation off\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_extension_index_oob_rejected(void) {
/* MSFT_lod and KHR_audio index fields must be validated when
* validate_indices=1, otherwise downstream consumers can read OOB. */
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"nodes\":[{\"extensions\":{\"MSFT_lod\":{\"ids\":[99999]}}}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_INDEX) {
fprintf(stderr, "MSFT_lod OOB index expected TG3_ERR_INVALID_INDEX, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_error_messages_survive_parse_failure(void) {
/* Regression: parse failure must not invalidate arena-allocated error
* message strings on the user's tg3_error_stack before model_free. */
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4,\"byteStride\":-1}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
uint32_t i;
int seen_stride_msg = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err == TG3_OK) goto fail;
for (i = 0; i < errors.count; ++i) {
const char *m = errors.entries[i].message;
if (m && strstr(m, "byteStride")) seen_stride_msg = 1;
}
if (!seen_stride_msg) {
fprintf(stderr, "byteStride error message missing or unreadable after parse\n");
goto fail;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
fail:
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
static int parse_file_arg(const char *path) {
FILE *fp = fopen(path, "rb");
uint8_t *buf;
long sz;
size_t got;
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int ok;
const char *slash;
size_t base_len;
if (!fp) {
fprintf(stderr, "open failed: %s\n", path);
return 0;
}
if (fseek(fp, 0, SEEK_END) != 0 || (sz = ftell(fp)) < 0 ||
fseek(fp, 0, SEEK_SET) != 0) {
fprintf(stderr, "seek failed: %s\n", path);
fclose(fp);
return 0;
}
buf = (uint8_t *)malloc((size_t)sz);
if (!buf) {
fprintf(stderr, "alloc failed: %s\n", path);
fclose(fp);
return 0;
}
got = fread(buf, 1, (size_t)sz, fp);
fclose(fp);
if (got != (size_t)sz) {
fprintf(stderr, "short read: %s\n", path);
free(buf);
return 0;
}
slash = strrchr(path, '/');
base_len = slash ? (size_t)(slash - path) : 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_auto(&model, &errors, buf, (uint64_t)sz,
path, (uint32_t)base_len, &opts);
ok = (err == TG3_OK);
if (!ok) {
uint32_t i;
fprintf(stderr, "parse failed (%d): %s\n", (int)err, path);
for (i = 0; i < errors.count; ++i) {
fprintf(stderr, " [%u] code=%d sev=%d path=%s msg=%s offset=%lld\n",
i, (int)errors.entries[i].code, (int)errors.entries[i].severity,
errors.entries[i].json_path ? errors.entries[i].json_path : "",
errors.entries[i].message ? errors.entries[i].message : "",
(long long)errors.entries[i].byte_offset);
}
} else {
printf("COUNTS"
" accessors=%u animations=%u buffers=%u bufferViews=%u"
" cameras=%u images=%u materials=%u meshes=%u nodes=%u"
" samplers=%u scenes=%u skins=%u textures=%u lights=%u\n",
model.accessors_count, model.animations_count,
model.buffers_count, model.buffer_views_count,
model.cameras_count, model.images_count,
model.materials_count, model.meshes_count,
model.nodes_count, model.samplers_count,
model.scenes_count, model.skins_count,
model.textures_count, model.lights_count);
print_digest(&model);
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
free(buf);
return ok;
}
int main(int argc, char **argv) {
if (argc > 1) {
int i;
for (i = 1; i < argc; ++i) {
if (!parse_file_arg(argv[i])) return 1;
}
return 0;
}
if (!check_minimal_parse()) {
return 1;
}
if (!check_minimal_write_roundtrip()) {
return 1;
}
if (!check_parse_file_failure_initializes_model()) {
return 1;
}
if (!check_non_object_root_rejected()) {
return 1;
}
if (!check_huge_integer_field_rejected()) {
return 1;
}
if (!check_path_traversal_rejected()) {
return 1;
}
if (!check_absolute_uri_rejected()) {
return 1;
}
if (!check_negative_byte_stride_rejected()) {
return 1;
}
if (!check_oob_index_rejected()) {
return 1;
}
if (!check_oob_index_opt_in()) {
return 1;
}
if (!check_extension_index_oob_rejected()) {
return 1;
}
if (!check_error_messages_survive_parse_failure()) {
return 1;
}
return 0;
}