@@ -42,9 +42,48 @@ static const char *load_string_f(const char *obj, tobj_scene_f *sc,
4242 return tobj_result_string (r );
4343}
4444
45+ typedef struct mem_io {
46+ const uint8_t * data ;
47+ size_t len ;
48+ size_t pos ;
49+ size_t chunk ;
50+ int closed ;
51+ } mem_io ;
52+
53+ static tobj_result mem_io_read (void * ud , uint8_t * dst , size_t dst_size ,
54+ size_t * bytes_read ) {
55+ mem_io * m = (mem_io * )ud ;
56+ size_t left = m -> len - m -> pos ;
57+ size_t n = left < dst_size ? left : dst_size ;
58+ if (m -> chunk && n > m -> chunk ) n = m -> chunk ;
59+ if (n ) memcpy (dst , m -> data + m -> pos , n );
60+ m -> pos += n ;
61+ * bytes_read = n ;
62+ return TOBJ_OK ;
63+ }
64+
65+ static void mem_io_close (void * ud ) { ((mem_io * )ud )-> closed = 1 ; }
66+
67+ static tobj_result io_overreport_read (void * ud , uint8_t * dst , size_t dst_size ,
68+ size_t * bytes_read ) {
69+ (void )ud ;
70+ if (dst_size ) dst [0 ] = 'x' ;
71+ * bytes_read = dst_size + 1 ;
72+ return TOBJ_OK ;
73+ }
74+
75+ static tobj_result io_error_read (void * ud , uint8_t * dst , size_t dst_size ,
76+ size_t * bytes_read ) {
77+ (void )ud ;
78+ (void )dst ;
79+ (void )dst_size ;
80+ * bytes_read = 0 ;
81+ return TOBJ_ERR_IO ;
82+ }
83+
4584/* ---- corpus ------------------------------------------------------------ */
4685
47- /* Only files committed to the repo (CI checks out a clean tree; sandbox/*.obj
86+ /* Only files committed to the repo (CI checks out a clean tree; sandbox files
4887 * are local scratch files and are intentionally not used here). The
4988 * pathological-geometry triangulation cases are covered directly by
5089 * tests/tess_tester.c. */
@@ -205,6 +244,143 @@ static void test_mtl_standalone(void) {
205244 tobj_material_list_free_f (& ml );
206245}
207246
247+ static void test_io_callbacks (void ) {
248+ const char * obj = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n" ;
249+ mem_io mio ;
250+ mio .data = (const uint8_t * )obj ;
251+ mio .len = strlen (obj );
252+ mio .pos = 0 ;
253+ mio .chunk = 5 ;
254+ mio .closed = 0 ;
255+ tobj_io_callbacks io ;
256+ io .read = mem_io_read ;
257+ io .close = mem_io_close ;
258+ io .user_data = & mio ;
259+ tobj_load_config cfg = tobj_default_config ();
260+ tobj_scene_f sc ;
261+ tobj_result r = tobj_load_obj_from_io_f (& sc , & io , & cfg , NULL );
262+ TEST_CHECK (r == TOBJ_OK );
263+ TEST_CHECK (mio .closed == 1 );
264+ TEST_CHECK (sc .num_shapes == 1 );
265+ TEST_CHECK (sc .shapes [0 ].mesh .num_indices == 3 );
266+ tobj_scene_free_f (& sc );
267+ }
268+
269+ static void test_allocator_limit_failure (void ) {
270+ const char * obj = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n" ;
271+ tobj_load_config cfg = tobj_default_config ();
272+ cfg .allocator = tobj_default_allocator ();
273+ cfg .allocator .max_alloc_size = 16 ;
274+ tobj_scene_f sc ;
275+ tobj_result r =
276+ tobj_load_obj_from_memory_f (& sc , (const uint8_t * )obj , strlen (obj ), & cfg ,
277+ NULL );
278+ TEST_CHECK (r == TOBJ_ERR_ALLOC );
279+ TEST_CHECK (sc .num_shapes == 0 );
280+ }
281+
282+ static void test_input_limit_failure (void ) {
283+ const char * obj = "v 0 0 0\n" ;
284+ tobj_load_config cfg = tobj_default_config ();
285+ cfg .max_input_bytes = 4 ;
286+ tobj_scene_f sc ;
287+ tobj_result r =
288+ tobj_load_obj_from_memory_f (& sc , (const uint8_t * )obj , strlen (obj ), & cfg ,
289+ NULL );
290+ TEST_CHECK (r == TOBJ_ERR_LIMIT_EXCEEDED );
291+ }
292+
293+ static void test_io_input_limit_exact_and_over (void ) {
294+ const char * obj = "v 0 0 0\n" ;
295+ tobj_load_config cfg = tobj_default_config ();
296+ cfg .max_input_bytes = strlen (obj );
297+
298+ mem_io exact ;
299+ exact .data = (const uint8_t * )obj ;
300+ exact .len = strlen (obj );
301+ exact .pos = 0 ;
302+ exact .chunk = 3 ;
303+ exact .closed = 0 ;
304+ tobj_io_callbacks io ;
305+ io .read = mem_io_read ;
306+ io .close = mem_io_close ;
307+ io .user_data = & exact ;
308+ tobj_scene_f sc ;
309+ tobj_result r = tobj_load_obj_from_io_f (& sc , & io , & cfg , NULL );
310+ TEST_CHECK (r == TOBJ_OK );
311+ TEST_CHECK (exact .closed == 1 );
312+ tobj_scene_free_f (& sc );
313+
314+ const char * too_big = "v 0 0 0\n# extra\n" ;
315+ mem_io over ;
316+ over .data = (const uint8_t * )too_big ;
317+ over .len = strlen (too_big );
318+ over .pos = 0 ;
319+ over .chunk = 0 ;
320+ over .closed = 0 ;
321+ io .user_data = & over ;
322+ r = tobj_load_obj_from_io_f (& sc , & io , & cfg , NULL );
323+ TEST_CHECK (r == TOBJ_ERR_LIMIT_EXCEEDED );
324+ TEST_CHECK (over .closed == 1 );
325+ TEST_CHECK (sc .num_shapes == 0 );
326+ }
327+
328+ static void test_io_callback_errors (void ) {
329+ tobj_io_callbacks io ;
330+ io .close = mem_io_close ;
331+
332+ mem_io state ;
333+ state .data = NULL ;
334+ state .len = 0 ;
335+ state .pos = 0 ;
336+ state .chunk = 0 ;
337+ state .closed = 0 ;
338+ io .user_data = & state ;
339+ io .read = io_overreport_read ;
340+ tobj_scene_f sc ;
341+ tobj_result r = tobj_load_obj_from_io_f (& sc , & io , NULL , NULL );
342+ TEST_CHECK (r == TOBJ_ERR_IO );
343+ TEST_CHECK (state .closed == 1 );
344+
345+ state .closed = 0 ;
346+ io .read = io_error_read ;
347+ r = tobj_load_obj_from_io_f (& sc , & io , NULL , NULL );
348+ TEST_CHECK (r == TOBJ_ERR_IO );
349+ TEST_CHECK (state .closed == 1 );
350+ }
351+
352+ static void test_invalid_allocator_rejected (void ) {
353+ const char * obj = "v 0 0 0\n" ;
354+ tobj_load_config cfg = tobj_default_config ();
355+ cfg .allocator = tobj_default_allocator ();
356+ cfg .allocator .free = NULL ;
357+ tobj_scene_f sc ;
358+ tobj_result r =
359+ tobj_load_obj_from_memory_f (& sc , (const uint8_t * )obj , strlen (obj ), & cfg ,
360+ NULL );
361+ TEST_CHECK (r == TOBJ_ERR_INVALID_ARG );
362+ TEST_CHECK (sc .num_shapes == 0 );
363+ }
364+
365+ static void test_allocator_without_calloc_or_realloc (void ) {
366+ const char * obj =
367+ "v 0 0 0\nv 1 0 0\nv 2 0 0\nv 3 0 0\nv 4 0 0\n"
368+ "v 5 0 0\nv 6 0 0\nv 7 0 0\nv 8 0 0\n"
369+ "f 1 2 3\nf 4 5 6\nf 7 8 9\n" ;
370+ tobj_load_config cfg = tobj_default_config ();
371+ cfg .allocator = tobj_default_allocator ();
372+ cfg .allocator .calloc = NULL ;
373+ cfg .allocator .realloc = NULL ;
374+ tobj_scene_f sc ;
375+ tobj_result r =
376+ tobj_load_obj_from_memory_f (& sc , (const uint8_t * )obj , strlen (obj ), & cfg ,
377+ NULL );
378+ TEST_CHECK (r == TOBJ_OK );
379+ TEST_CHECK (sc .attrib .vertices .count == 27 );
380+ TEST_CHECK (sc .shapes [0 ].mesh .num_indices == 9 );
381+ tobj_scene_free_f (& sc );
382+ }
383+
208384TEST_LIST = {
209385 {"corpus_loads" , test_corpus_loads },
210386 {"both_precisions" , test_both_precisions },
@@ -218,4 +394,12 @@ TEST_LIST = {
218394 {"degenerate_face_skipped" , test_degenerate_face_skipped },
219395 {"empty_and_garbage" , test_empty_and_garbage },
220396 {"mtl_standalone" , test_mtl_standalone },
397+ {"io_callbacks" , test_io_callbacks },
398+ {"allocator_limit_failure" , test_allocator_limit_failure },
399+ {"input_limit_failure" , test_input_limit_failure },
400+ {"io_input_limit_exact_and_over" , test_io_input_limit_exact_and_over },
401+ {"io_callback_errors" , test_io_callback_errors },
402+ {"invalid_allocator_rejected" , test_invalid_allocator_rejected },
403+ {"allocator_without_calloc_or_realloc" ,
404+ test_allocator_without_calloc_or_realloc },
221405 {NULL , NULL }};
0 commit comments