@@ -168,8 +168,12 @@ void fossil_jellyfish_dump(const fossil_jellyfish_chain *chain) {
168168 }
169169}
170170
171+ static void skip_whitespace (const char * * ptr ) {
172+ while (isspace (* * ptr )) (* ptr )++ ;
173+ }
174+
171175static bool match_key (const char * * ptr , const char * key ) {
172- while ( * * ptr && ( * * ptr == ' ' || * * ptr == '\n' || * * ptr == '\r' || * * ptr == '\t' )) ( * ptr ) ++ ;
176+ skip_whitespace ( ptr ) ;
173177 size_t len = strlen (key );
174178 if (strncmp (* ptr , key , len ) == 0 ) {
175179 * ptr += len ;
@@ -178,43 +182,48 @@ static bool match_key(const char **ptr, const char *key) {
178182 return false;
179183}
180184
181- static void skip_whitespace (const char * * ptr ) {
182- while (* * ptr && (* * ptr == ' ' || * * ptr == '\n' || * * ptr == '\r' || * * ptr == '\t' )) (* ptr )++ ;
183- }
184-
185185static bool parse_string (const char * * ptr , char * out , size_t maxlen ) {
186186 skip_whitespace (ptr );
187- if (* * ptr != '\ "' ) return false;
187+ if (* * ptr != '"' ) return false;
188188 (* ptr )++ ;
189189 size_t i = 0 ;
190- while (* * ptr && * * ptr != '\"' && i < maxlen - 1 ) {
190+ while (* * ptr && * * ptr != '"' && i < maxlen - 1 ) {
191+ if (* * ptr == '\\' ) (* ptr )++ ; // Skip escape char
191192 out [i ++ ] = * (* ptr )++ ;
192193 }
193- if (* * ptr != '\ "' ) return false;
194+ if (* * ptr != '"' ) return false;
194195 (* ptr )++ ;
195196 out [i ] = '\0' ;
196197 return true;
197198}
198199
199- static bool parse_number (const char * * ptr , double * out_double , long * out_long ) {
200+ static bool parse_hash (const char * * ptr , uint8_t * hash_out ) {
201+ char hexstr [FOSSIL_JELLYFISH_HASH_SIZE * 2 + 1 ] = {0 };
202+ if (!parse_string (ptr , hexstr , sizeof (hexstr ))) return false;
203+
204+ if (strlen (hexstr ) != FOSSIL_JELLYFISH_HASH_SIZE * 2 ) return false;
205+
206+ for (size_t i = 0 ; i < FOSSIL_JELLYFISH_HASH_SIZE ; ++ i ) {
207+ char byte [3 ] = { hexstr [i * 2 ], hexstr [i * 2 + 1 ], 0 };
208+ hash_out [i ] = (uint8_t )strtoul (byte , NULL , 16 );
209+ }
210+ return true;
211+ }
212+
213+ static bool parse_number (const char * * ptr , double * out_d , uint64_t * out_u64 , int * out_i , uint32_t * out_u32 ) {
200214 skip_whitespace (ptr );
201215 char buffer [64 ];
202216 size_t i = 0 ;
203- while ((* * ptr >= '0' && * * ptr <= '9' ) || * * ptr == '.' || * * ptr == '-' ) {
204- if (i < sizeof (buffer ) - 1 )
205- buffer [i ++ ] = * (* ptr )++ ;
206- else
207- return false;
208- }
217+ while ((isdigit (* * ptr ) || * * ptr == '.' || * * ptr == '-' ) && i < sizeof (buffer ) - 1 )
218+ buffer [i ++ ] = * (* ptr )++ ;
209219 buffer [i ] = '\0' ;
210220
211- if (strchr (buffer , '.' )) {
212- if (out_double ) * out_double = atof (buffer );
213- } else {
214- if (out_long ) * out_long = atol (buffer );
215- }
221+ if (out_d ) * out_d = atof (buffer );
222+ if (out_u64 ) * out_u64 = strtoull (buffer , NULL , 10 );
223+ if (out_i ) * out_i = atoi (buffer );
224+ if (out_u32 ) * out_u32 = (uint32_t )strtoul (buffer , NULL , 10 );
216225
217- return true ;
226+ return i > 0 ;
218227}
219228
220229int fossil_jellyfish_load (fossil_jellyfish_chain * chain , const char * filepath ) {
@@ -232,131 +241,121 @@ int fossil_jellyfish_load(fossil_jellyfish_chain *chain, const char *filepath) {
232241 }
233242
234243 fread (data , 1 , fsize , fp );
235- fclose (fp );
236244 data [fsize ] = '\0' ;
245+ fclose (fp );
237246
238247 const char * ptr = data ;
239248 skip_whitespace (& ptr );
240249
241- // Look for signature
242- if (!match_key (& ptr , "{\"signature\":" )) {
243- free (data );
244- return 0 ;
245- }
250+ if (!match_key (& ptr , "{\"signature\":" )) { free (data ); return 0 ; }
246251
247252 char sig [8 ];
248253 if (!parse_string (& ptr , sig , sizeof (sig )) || strcmp (sig , "JFS1" ) != 0 ) {
249254 free (data );
250255 return 0 ;
251256 }
252257
253- if (!match_key (& ptr , ",\"blocks\":[" )) {
254- free (data );
255- return 0 ;
256- }
258+ if (!match_key (& ptr , ",\"blocks\":[" )) { free (data ); return 0 ; }
257259
258260 size_t count = 0 ;
261+ bool ok = true;
262+
259263 while (* ptr && * ptr != ']' ) {
260264 if (count >= FOSSIL_JELLYFISH_MAX_MEM ) break ;
261265
262- if (!match_key (& ptr , "{" )) {
263- free (data );
264- return 0 ;
265- }
266+ fossil_jellyfish_block * b = & chain -> memory [count ];
267+ memset (b , 0 , sizeof (* b ));
266268
267- fossil_jellyfish_block * block = & chain -> memory [count ];
268- memset (block , 0 , sizeof (* block ));
269+ if (!match_key (& ptr , "{" )) { ok = false; break ; }
269270
270- if (!match_key (& ptr , "\"input\":" )) {
271- free (data );
272- return 0 ;
273- }
274- if (!parse_string (& ptr , block -> input , sizeof (block -> input ))) {
275- free (data );
276- return 0 ;
277- }
271+ if (!match_key (& ptr , "\"input\":" )) { ok = false; break ; }
272+ if (!parse_string (& ptr , b -> input , sizeof (b -> input ))) { ok = false; break ; }
278273
279- if (!match_key (& ptr , ",\"output\":" )) {
280- free (data );
281- return 0 ;
282- }
283- if (!parse_string (& ptr , block -> output , sizeof (block -> output ))) {
284- free (data );
285- return 0 ;
286- }
274+ if (!match_key (& ptr , ",\"output\":" )) { ok = false; break ; }
275+ if (!parse_string (& ptr , b -> output , sizeof (b -> output ))) { ok = false; break ; }
287276
288- if (!match_key (& ptr , ",\"timestamp\":" )) {
289- free (data );
290- return 0 ;
291- }
292- if (!parse_number (& ptr , NULL , & block -> timestamp )) {
293- free (data );
294- return 0 ;
295- }
277+ if (!match_key (& ptr , ",\"hash\":" )) { ok = false; break ; }
278+ if (!parse_hash (& ptr , b -> hash )) { ok = false; break ; }
296279
297- if (!match_key (& ptr , ",\"confidence\":" )) {
298- free (data );
299- return 0 ;
300- }
301- if (!parse_number (& ptr , & block -> confidence , NULL )) {
302- free (data );
303- return 0 ;
304- }
280+ if (!match_key (& ptr , ",\"timestamp\":" )) { ok = false; break ; }
281+ if (!parse_number (& ptr , NULL , & b -> timestamp , NULL , NULL )) { ok = false; break ; }
305282
306- if (!match_key (& ptr , "}" )) {
307- free (data );
308- return 0 ;
309- }
283+ if (!match_key (& ptr , ",\"valid\":" )) { ok = false; break ; }
284+ if (!parse_number (& ptr , NULL , NULL , & b -> valid , NULL )) { ok = false; break ; }
310285
311- count ++ ;
286+ if (!match_key (& ptr , ",\"confidence\":" )) { ok = false; break ; }
287+ if (!parse_number (& ptr , & b -> confidence , NULL , NULL , NULL )) { ok = false; break ; }
288+
289+ if (!match_key (& ptr , ",\"usage_count\":" )) { ok = false; break ; }
290+ if (!parse_number (& ptr , NULL , NULL , NULL , & b -> usage_count )) { ok = false; break ; }
291+
292+ if (!match_key (& ptr , "}" )) { ok = false; break ; }
312293
294+ count ++ ;
313295 skip_whitespace (& ptr );
314296 if (* ptr == ',' ) ptr ++ ;
315297 }
316298
317- chain -> count = count ;
318299 free (data );
319- return 1 ;
300+ chain -> count = ok ? count : 0 ;
301+ return ok ? 1 : 0 ;
302+ }
303+
304+ static void hex_encode (const uint8_t * hash , size_t len , char * out , size_t out_size ) {
305+ const char * hex = "0123456789abcdef" ;
306+ if (out_size < len * 2 + 1 ) return ;
307+ for (size_t i = 0 ; i < len ; ++ i ) {
308+ out [i * 2 ] = hex [(hash [i ] >> 4 ) & 0xF ];
309+ out [i * 2 + 1 ] = hex [hash [i ] & 0xF ];
310+ }
311+ out [len * 2 ] = '\0' ;
320312}
321313
322314int fossil_jellyfish_save (const fossil_jellyfish_chain * chain , const char * filepath ) {
323315 FILE * fp = fopen (filepath , "wb" );
324316 if (!fp ) return 0 ;
325317
326- fprintf (fp , "{\n" );
327- fprintf (fp , " \"signature\": \"JFS1\",\n" );
328- fprintf (fp , " \"blocks\": [\n" );
318+ fprintf (fp , "{\n \"signature\": \"JFS1\",\n \"blocks\": [\n" );
329319
330320 for (size_t i = 0 ; i < chain -> count ; ++ i ) {
331- const fossil_jellyfish_block * block = & chain -> memory [i ];
321+ const fossil_jellyfish_block * b = & chain -> memory [i ];
322+
323+ // Escape input/output strings (basic)
324+ char input_escaped [FOSSIL_JELLYFISH_INPUT_SIZE * 2 ] = {0 };
325+ char output_escaped [FOSSIL_JELLYFISH_OUTPUT_SIZE * 2 ] = {0 };
332326
333- // Escape JSON strings (only for quote and backslash for simplicity)
334- char input_escaped [512 ] = {0 }, output_escaped [512 ] = {0 };
335327 char * dst = input_escaped ;
336- for (const char * src = block -> input ; * src && (dst - input_escaped ) < sizeof (input_escaped ) - 2 ; ++ src ) {
328+ for (const char * src = b -> input ; * src && (dst - input_escaped ) < sizeof (input_escaped ) - 2 ; ++ src ) {
337329 if (* src == '"' || * src == '\\' ) * dst ++ = '\\' ;
338330 * dst ++ = * src ;
339331 }
340332 * dst = '\0' ;
341333
342334 dst = output_escaped ;
343- for (const char * src = block -> output ; * src && (dst - output_escaped ) < sizeof (output_escaped ) - 2 ; ++ src ) {
335+ for (const char * src = b -> output ; * src && (dst - output_escaped ) < sizeof (output_escaped ) - 2 ; ++ src ) {
344336 if (* src == '"' || * src == '\\' ) * dst ++ = '\\' ;
345337 * dst ++ = * src ;
346338 }
347339 * dst = '\0' ;
348340
349- fprintf (fp , " {\n" );
350- fprintf (fp , " \"input\": \"%s\",\n" , input_escaped );
351- fprintf (fp , " \"output\": \"%s\",\n" , output_escaped );
352- fprintf (fp , " \"timestamp\": %llu,\n" , block -> timestamp );
353- fprintf (fp , " \"confidence\": %.6f\n" , block -> confidence );
354- fprintf (fp , " }%s\n" , (i < chain -> count - 1 ) ? "," : "" );
355- }
341+ char hash_hex [FOSSIL_JELLYFISH_HASH_SIZE * 2 + 1 ];
342+ hex_encode (b -> hash , FOSSIL_JELLYFISH_HASH_SIZE , hash_hex , sizeof (hash_hex ));
356343
357- fprintf (fp , " ]\n" );
358- fprintf (fp , "}\n" );
344+ fprintf (fp ,
345+ " {\n"
346+ " \"input\": \"%s\",\n"
347+ " \"output\": \"%s\",\n"
348+ " \"hash\": \"%s\",\n"
349+ " \"timestamp\": %" PRIu64 ",\n"
350+ " \"valid\": %d,\n"
351+ " \"confidence\": %.6f,\n"
352+ " \"usage_count\": %u\n"
353+ " }%s\n" ,
354+ input_escaped , output_escaped , hash_hex , b -> timestamp , b -> valid , b -> confidence , b -> usage_count ,
355+ (i < chain -> count - 1 ) ? "," : "" );
356+ }
359357
358+ fprintf (fp , " ]\n}\n" );
360359 fclose (fp );
361360 return 1 ;
362361}
0 commit comments