json.c (23029B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014, 2015, 2016, 2020, 2021 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file json/json.c 18 * @brief helper functions for JSON processing using libjansson 19 * @author Sree Harsha Totakura <sreeharsha@totakura.in> 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <gnunet/gnunet_util_lib.h> 24 #include "taler/taler_util.h" 25 #include "taler/taler_json_lib.h" 26 #include <unistr.h> 27 28 29 /** 30 * Check if @a json contains a 'real' value anywhere. 31 * 32 * @param json json to check 33 * @return true if a real is in it somewhere 34 */ 35 static bool 36 contains_real (const json_t *json) 37 { 38 if (json_is_real (json)) 39 return true; 40 if (json_is_object (json)) 41 { 42 json_t *member; 43 const char *name; 44 45 json_object_foreach ((json_t *) json, name, member) 46 if (contains_real (member)) 47 return true; 48 return false; 49 } 50 if (json_is_array (json)) 51 { 52 json_t *member; 53 size_t index; 54 55 json_array_foreach ((json_t *) json, index, member) 56 if (contains_real (member)) 57 return true; 58 return false; 59 } 60 return false; 61 } 62 63 64 /** 65 * Dump the @a json to a string and hash it. 66 * 67 * @param json value to hash 68 * @param salt salt value to include when using HKDF, 69 * NULL to not use any salt and to use SHA512 70 * @param[out] hc where to store the hash 71 * @return #GNUNET_OK on success, 72 * #GNUNET_NO if @a json was not hash-able 73 * #GNUNET_SYSERR on failure 74 */ 75 static enum GNUNET_GenericReturnValue 76 dump_and_hash (const json_t *json, 77 const char *salt, 78 struct GNUNET_HashCode *hc) 79 { 80 char *wire_enc; 81 size_t len; 82 83 if (NULL == json) 84 { 85 GNUNET_break_op (0); 86 return GNUNET_NO; 87 } 88 if (contains_real (json)) 89 { 90 GNUNET_break_op (0); 91 return GNUNET_NO; 92 } 93 if (NULL == (wire_enc = json_dumps (json, 94 JSON_ENCODE_ANY 95 | JSON_COMPACT 96 | JSON_SORT_KEYS))) 97 { 98 GNUNET_break (0); 99 return GNUNET_SYSERR; 100 } 101 len = TALER_rfc8785encode (&wire_enc); 102 if (NULL == salt) 103 { 104 GNUNET_CRYPTO_hash (wire_enc, 105 len, 106 hc); 107 } 108 else 109 { 110 if (GNUNET_YES != 111 GNUNET_CRYPTO_kdf (hc, 112 sizeof (*hc), 113 salt, 114 strlen (salt) + 1, 115 wire_enc, 116 len, 117 NULL, 118 0)) 119 { 120 GNUNET_break (0); 121 free (wire_enc); 122 return GNUNET_SYSERR; 123 } 124 } 125 free (wire_enc); 126 return GNUNET_OK; 127 } 128 129 130 /** 131 * Replace "forgettable" parts of a JSON object with their salted hash. 132 * 133 * @param[in] in some JSON value 134 * @param[out] out resulting JSON value 135 * @return #GNUNET_OK on success, 136 * #GNUNET_NO if @a json was not hash-able 137 * #GNUNET_SYSERR on failure 138 */ 139 static enum GNUNET_GenericReturnValue 140 forget (const json_t *in, 141 json_t **out) 142 { 143 if (json_is_real (in)) 144 { 145 /* floating point is not allowed! */ 146 GNUNET_break_op (0); 147 return GNUNET_NO; 148 } 149 if (json_is_array (in)) 150 { 151 /* array is a JSON array */ 152 size_t index; 153 json_t *value; 154 json_t *ret; 155 156 ret = json_array (); 157 if (NULL == ret) 158 { 159 GNUNET_break (0); 160 return GNUNET_SYSERR; 161 } 162 json_array_foreach (in, index, value) { 163 enum GNUNET_GenericReturnValue iret; 164 json_t *t; 165 166 iret = forget (value, 167 &t); 168 if (GNUNET_OK != iret) 169 { 170 json_decref (ret); 171 return iret; 172 } 173 if (0 != json_array_append_new (ret, 174 t)) 175 { 176 GNUNET_break (0); 177 json_decref (ret); 178 return GNUNET_SYSERR; 179 } 180 } 181 *out = ret; 182 return GNUNET_OK; 183 } 184 if (json_is_object (in)) 185 { 186 json_t *ret; 187 const char *key; 188 json_t *value; 189 json_t *fg; 190 json_t *rx; 191 192 fg = json_object_get (in, 193 "$forgettable"); 194 rx = json_object_get (in, 195 "$forgotten"); 196 if (NULL != rx) 197 { 198 rx = json_deep_copy (rx); /* should be shallow 199 by structure, but 200 deep copy is safer */ 201 if (NULL == rx) 202 { 203 GNUNET_break (0); 204 return GNUNET_SYSERR; 205 } 206 } 207 ret = json_object (); 208 if (NULL == ret) 209 { 210 GNUNET_break (0); 211 return GNUNET_SYSERR; 212 } 213 json_object_foreach ((json_t*) in, key, value) { 214 json_t *t; 215 json_t *salt; 216 enum GNUNET_GenericReturnValue iret; 217 218 if (fg == value) 219 continue; /* skip! */ 220 if (rx == value) 221 continue; /* skip! */ 222 if ( (NULL != rx) && 223 (NULL != 224 json_object_get (rx, 225 key)) ) 226 { 227 (void) json_object_del (ret, 228 key); 229 continue; /* already forgotten earlier */ 230 } 231 iret = forget (value, 232 &t); 233 if (GNUNET_OK != iret) 234 { 235 json_decref (ret); 236 json_decref (rx); 237 return iret; 238 } 239 if ( (NULL != fg) && 240 (NULL != (salt = json_object_get (fg, 241 key))) ) 242 { 243 /* 't' is to be forgotten! */ 244 struct GNUNET_HashCode hc; 245 246 if (! json_is_string (salt)) 247 { 248 GNUNET_break_op (0); 249 json_decref (ret); 250 json_decref (rx); 251 json_decref (t); 252 return GNUNET_NO; 253 } 254 iret = dump_and_hash (t, 255 json_string_value (salt), 256 &hc); 257 if (GNUNET_OK != iret) 258 { 259 json_decref (ret); 260 json_decref (rx); 261 json_decref (t); 262 return iret; 263 } 264 json_decref (t); 265 /* scrub salt */ 266 if (0 != 267 json_object_del (fg, 268 key)) 269 { 270 GNUNET_break_op (0); 271 json_decref (ret); 272 json_decref (rx); 273 return GNUNET_NO; 274 } 275 if (NULL == rx) 276 rx = json_object (); 277 if (NULL == rx) 278 { 279 GNUNET_break (0); 280 json_decref (ret); 281 return GNUNET_SYSERR; 282 } 283 if (0 != 284 json_object_set_new (rx, 285 key, 286 GNUNET_JSON_from_data_auto (&hc))) 287 { 288 GNUNET_break (0); 289 json_decref (ret); 290 json_decref (rx); 291 return GNUNET_SYSERR; 292 } 293 } 294 else 295 { 296 /* 't' to be used without 'forgetting' */ 297 if (0 != 298 json_object_set_new (ret, 299 key, 300 t)) 301 { 302 GNUNET_break (0); 303 json_decref (ret); 304 json_decref (rx); 305 return GNUNET_SYSERR; 306 } 307 } 308 } /* json_object_foreach */ 309 if ( (NULL != rx) && 310 (0 != 311 json_object_set_new (ret, 312 "$forgotten", 313 rx)) ) 314 { 315 GNUNET_break (0); 316 json_decref (ret); 317 return GNUNET_SYSERR; 318 } 319 *out = ret; 320 return GNUNET_OK; 321 } 322 *out = json_incref ((json_t *) in); 323 return GNUNET_OK; 324 } 325 326 327 enum GNUNET_GenericReturnValue 328 TALER_JSON_contract_hash (const json_t *json, 329 struct TALER_PrivateContractHashP *hc) 330 { 331 enum GNUNET_GenericReturnValue ret; 332 json_t *cjson; 333 json_t *dc; 334 335 dc = json_deep_copy (json); 336 ret = forget (dc, 337 &cjson); 338 json_decref (dc); 339 if (GNUNET_OK != ret) 340 return ret; 341 ret = dump_and_hash (cjson, 342 NULL, 343 &hc->hash); 344 json_decref (cjson); 345 return ret; 346 } 347 348 349 enum GNUNET_GenericReturnValue 350 TALER_JSON_contract_mark_forgettable (json_t *json, 351 const char *field) 352 { 353 json_t *fg; 354 struct GNUNET_ShortHashCode salt; 355 356 if (! json_is_object (json)) 357 { 358 GNUNET_break (0); 359 return GNUNET_SYSERR; 360 } 361 /* check field name is legal for forgettable field */ 362 for (const char *f = field; '\0' != *f; f++) 363 { 364 char c = *f; 365 366 if ( (c >= 'a') && (c <= 'z') ) 367 continue; 368 if ( (c >= 'A') && (c <= 'Z') ) 369 continue; 370 if ( (c >= '0') && (c <= '9') ) 371 continue; 372 if ('_' == c) 373 continue; 374 GNUNET_break (0); 375 return GNUNET_SYSERR; 376 } 377 if (NULL == json_object_get (json, 378 field)) 379 { 380 /* field must exist */ 381 GNUNET_break (0); 382 return GNUNET_SYSERR; 383 } 384 fg = json_object_get (json, 385 "$forgettable"); 386 if (NULL == fg) 387 { 388 fg = json_object (); 389 if (0 != 390 json_object_set_new (json, 391 "$forgettable", 392 fg)) 393 { 394 GNUNET_break (0); 395 return GNUNET_SYSERR; 396 } 397 } 398 399 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 400 &salt, 401 sizeof (salt)); 402 if (0 != 403 json_object_set_new (fg, 404 field, 405 GNUNET_JSON_from_data_auto (&salt))) 406 { 407 GNUNET_break (0); 408 return GNUNET_SYSERR; 409 } 410 return GNUNET_OK; 411 } 412 413 414 enum GNUNET_GenericReturnValue 415 TALER_JSON_contract_part_forget (json_t *json, 416 const char *field) 417 { 418 json_t *fg; 419 const json_t *part; 420 json_t *fp; 421 json_t *rx; 422 struct GNUNET_HashCode hc; 423 const char *salt; 424 enum GNUNET_GenericReturnValue ret; 425 426 if (! json_is_object (json)) 427 { 428 GNUNET_break (0); 429 return GNUNET_SYSERR; 430 } 431 if (NULL == (part = json_object_get (json, 432 field))) 433 { 434 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 435 "Did not find field `%s' we were asked to forget\n", 436 field); 437 return GNUNET_SYSERR; 438 } 439 fg = json_object_get (json, 440 "$forgettable"); 441 if (NULL == fg) 442 { 443 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 444 "Did not find '$forgettable' attribute trying to forget field `%s'\n", 445 field); 446 return GNUNET_SYSERR; 447 } 448 rx = json_object_get (json, 449 "$forgotten"); 450 if (NULL == rx) 451 { 452 rx = json_object (); 453 if (0 != 454 json_object_set_new (json, 455 "$forgotten", 456 rx)) 457 { 458 GNUNET_break (0); 459 return GNUNET_SYSERR; 460 } 461 } 462 if (NULL != 463 json_object_get (rx, 464 field)) 465 { 466 if (! json_is_null (json_object_get (json, 467 field))) 468 { 469 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 470 "Field `%s' market as forgotten, but still exists!\n", 471 field); 472 return GNUNET_SYSERR; 473 } 474 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 475 "Already forgot field `%s'\n", 476 field); 477 return GNUNET_NO; 478 } 479 salt = json_string_value (json_object_get (fg, 480 field)); 481 if (NULL == salt) 482 { 483 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 484 "Did not find required salt to forget field `%s'\n", 485 field); 486 return GNUNET_SYSERR; 487 } 488 489 /* need to recursively forget to compute 'hc' */ 490 ret = forget (part, 491 &fp); 492 if (GNUNET_OK != ret) 493 return ret; 494 if (GNUNET_OK != 495 dump_and_hash (fp, 496 salt, 497 &hc)) 498 { 499 json_decref (fp); 500 GNUNET_break (0); 501 return GNUNET_SYSERR; 502 } 503 json_decref (fp); 504 /* drop salt */ 505 if (0 != 506 json_object_del (fg, 507 field)) 508 { 509 json_decref (fp); 510 GNUNET_break (0); 511 return GNUNET_SYSERR; 512 } 513 514 /* remember field as 'forgotten' */ 515 if (0 != 516 json_object_set_new (rx, 517 field, 518 GNUNET_JSON_from_data_auto (&hc))) 519 { 520 GNUNET_break (0); 521 return GNUNET_SYSERR; 522 } 523 /* finally, set 'forgotten' field to null */ 524 if (0 != 525 json_object_del (json, 526 field)) 527 { 528 GNUNET_break (0); 529 return GNUNET_SYSERR; 530 } 531 return GNUNET_OK; 532 } 533 534 535 /** 536 * Loop over all of the values of a '$forgettable' object. Replace 'True' 537 * values with proper random salts. Fails if any forgettable values are 538 * neither 'True' nor valid salts (strings). 539 * 540 * @param[in,out] f JSON to transform 541 * @return #GNUNET_OK on success 542 */ 543 static enum GNUNET_GenericReturnValue 544 seed_forgettable (json_t *f) 545 { 546 const char *key; 547 json_t *val; 548 549 json_object_foreach (f, 550 key, 551 val) 552 { 553 if (json_is_string (val)) 554 continue; 555 if (json_is_true (val)) 556 { 557 struct GNUNET_ShortHashCode sh; 558 559 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 560 &sh, 561 sizeof (sh)); 562 if (0 != 563 json_object_set_new (f, 564 key, 565 GNUNET_JSON_from_data_auto (&sh))) 566 { 567 GNUNET_break (0); 568 return GNUNET_SYSERR; 569 } 570 continue; 571 } 572 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 573 "Forgettable field `%s' has invalid value\n", 574 key); 575 return GNUNET_SYSERR; 576 } 577 return GNUNET_OK; 578 } 579 580 581 enum GNUNET_GenericReturnValue 582 TALER_JSON_contract_seed_forgettable (const json_t *spec, 583 json_t *contract) 584 { 585 if (json_is_object (spec)) 586 { 587 const char *key; 588 json_t *val; 589 590 json_object_foreach ((json_t *) spec, 591 key, 592 val) 593 { 594 json_t *cval = json_object_get (contract, 595 key); 596 597 if (0 == strcmp ("$forgettable", 598 key)) 599 { 600 json_t *xval = json_deep_copy (val); 601 602 if (GNUNET_OK != 603 seed_forgettable (xval)) 604 { 605 json_decref (xval); 606 return GNUNET_SYSERR; 607 } 608 GNUNET_assert (0 == 609 json_object_set_new (contract, 610 "$forgettable", 611 xval)); 612 continue; 613 } 614 if (NULL == cval) 615 continue; 616 if (GNUNET_OK != 617 TALER_JSON_contract_seed_forgettable (val, 618 cval)) 619 return GNUNET_SYSERR; 620 } 621 } 622 if (json_is_array (spec)) 623 { 624 size_t index; 625 json_t *val; 626 627 json_array_foreach ((json_t *) spec, 628 index, 629 val) 630 { 631 json_t *ival = json_array_get (contract, 632 index); 633 634 if (NULL == ival) 635 continue; 636 if (GNUNET_OK != 637 TALER_JSON_contract_seed_forgettable (val, 638 ival)) 639 return GNUNET_SYSERR; 640 } 641 } 642 return GNUNET_OK; 643 } 644 645 646 /** 647 * Parse a json path. 648 * 649 * @param obj the object that the path is relative to. 650 * @param prev the parent of @e obj. 651 * @param path the path to parse. 652 * @param cb the callback to call, if we get to the end of @e path. 653 * @param cb_cls the closure for the callback. 654 * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed. 655 */ 656 static enum GNUNET_GenericReturnValue 657 parse_path (json_t *obj, 658 json_t *prev, 659 const char *path, 660 TALER_JSON_ExpandPathCallback cb, 661 void *cb_cls) 662 { 663 char *id = GNUNET_strdup (path); 664 char *next_id = strchr (id, 665 '.'); 666 char *next_path; 667 char *bracket; 668 json_t *next_obj = NULL; 669 char *next_dot; 670 671 GNUNET_assert (NULL != id); /* make stupid compiler happy */ 672 if (NULL == next_id) 673 { 674 cb (cb_cls, 675 id, 676 prev); 677 GNUNET_free (id); 678 return GNUNET_OK; 679 } 680 bracket = strchr (next_id, 681 '['); 682 *next_id = '\0'; 683 next_id++; 684 next_path = GNUNET_strdup (next_id); 685 next_dot = strchr (next_id, 686 '.'); 687 if (NULL != next_dot) 688 *next_dot = '\0'; 689 /* If this is the first time this is called, make sure id is "$" */ 690 if ( (NULL == prev) && 691 (0 != strcmp (id, 692 "$"))) 693 { 694 GNUNET_free (id); 695 GNUNET_free (next_path); 696 return GNUNET_SYSERR; 697 } 698 699 /* Check for bracketed indices */ 700 if (NULL != bracket) 701 { 702 char *end_bracket = strchr (bracket, 703 ']'); 704 json_t *array; 705 706 if (NULL == end_bracket) 707 { 708 GNUNET_free (id); 709 GNUNET_free (next_path); 710 return GNUNET_SYSERR; 711 } 712 *end_bracket = '\0'; 713 *bracket = '\0'; 714 bracket++; 715 array = json_object_get (obj, 716 next_id); 717 if (0 == strcmp (bracket, 718 "*")) 719 { 720 size_t index; 721 json_t *value; 722 int ret = GNUNET_OK; 723 724 json_array_foreach (array, index, value) { 725 ret = parse_path (value, 726 obj, 727 next_path, 728 cb, 729 cb_cls); 730 if (GNUNET_OK != ret) 731 { 732 GNUNET_free (id); 733 GNUNET_free (next_path); 734 return ret; 735 } 736 } 737 } 738 else 739 { 740 unsigned int index; 741 char dummy; 742 743 if (1 != sscanf (bracket, 744 "%u%c", 745 &index, 746 &dummy)) 747 { 748 GNUNET_free (id); 749 GNUNET_free (next_path); 750 return GNUNET_SYSERR; 751 } 752 next_obj = json_array_get (array, 753 index); 754 } 755 } 756 else 757 { 758 /* No brackets, so just fetch the object by name */ 759 next_obj = json_object_get (obj, 760 next_id); 761 } 762 763 if (NULL != next_obj) 764 { 765 int ret = parse_path (next_obj, 766 obj, 767 next_path, 768 cb, 769 cb_cls); 770 GNUNET_free (id); 771 GNUNET_free (next_path); 772 return ret; 773 } 774 GNUNET_free (id); 775 GNUNET_free (next_path); 776 return GNUNET_OK; 777 } 778 779 780 enum GNUNET_GenericReturnValue 781 TALER_JSON_expand_path (json_t *json, 782 const char *path, 783 TALER_JSON_ExpandPathCallback cb, 784 void *cb_cls) 785 { 786 return parse_path (json, 787 NULL, 788 path, 789 cb, 790 cb_cls); 791 } 792 793 794 enum TALER_ErrorCode 795 TALER_JSON_get_error_code (const json_t *json) 796 { 797 const json_t *jc; 798 799 if (NULL == json) 800 return TALER_EC_GENERIC_INVALID_RESPONSE; 801 jc = json_object_get (json, "code"); 802 /* The caller already knows that the JSON represents an error, 803 so we are dealing with a missing error code here. */ 804 if (NULL == jc) 805 { 806 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 807 "Expected Taler error code `code' in JSON, but field does not exist!\n"); 808 return TALER_EC_INVALID; 809 } 810 if (json_is_integer (jc)) 811 return (enum TALER_ErrorCode) (int) json_integer_value (jc); 812 GNUNET_break_op (0); 813 return TALER_EC_INVALID; 814 } 815 816 817 const char * 818 TALER_JSON_get_error_hint (const json_t *json) 819 { 820 const json_t *jc; 821 822 if (NULL == json) 823 return NULL; 824 jc = json_object_get (json, 825 "hint"); 826 if (NULL == jc) 827 return NULL; /* no hint, is allowed */ 828 if (! json_is_string (jc)) 829 { 830 /* Hints must be strings */ 831 GNUNET_break_op (0); 832 return NULL; 833 } 834 return json_string_value (jc); 835 } 836 837 838 enum TALER_ErrorCode 839 TALER_JSON_get_error_code2 (const void *data, 840 size_t data_size) 841 { 842 json_t *json; 843 enum TALER_ErrorCode ec; 844 json_error_t err; 845 846 json = json_loadb (data, 847 data_size, 848 JSON_REJECT_DUPLICATES, 849 &err); 850 if (NULL == json) 851 return TALER_EC_INVALID; 852 ec = TALER_JSON_get_error_code (json); 853 json_decref (json); 854 if (ec == TALER_EC_NONE) 855 return TALER_EC_INVALID; 856 return ec; 857 } 858 859 860 void 861 TALER_deposit_policy_hash (const json_t *policy, 862 struct TALER_ExtensionPolicyHashP *ech) 863 { 864 GNUNET_assert (GNUNET_OK == 865 dump_and_hash (policy, 866 "taler-extensions-policy", 867 &ech->hash)); 868 } 869 870 871 char * 872 TALER_JSON_canonicalize (const json_t *input) 873 { 874 char *wire_enc; 875 876 if (NULL == (wire_enc = json_dumps (input, 877 JSON_ENCODE_ANY 878 | JSON_COMPACT 879 | JSON_SORT_KEYS))) 880 { 881 GNUNET_break (0); 882 return NULL; 883 } 884 TALER_rfc8785encode (&wire_enc); 885 return wire_enc; 886 } 887 888 889 enum GNUNET_GenericReturnValue 890 TALER_JSON_extensions_manifests_hash (const json_t *manifests, 891 struct TALER_ExtensionManifestsHashP *ech) 892 { 893 return dump_and_hash (manifests, 894 "taler-extensions-manifests", 895 &ech->hash); 896 } 897 898 899 json_t * 900 TALER_JSON_currency_specs_to_json ( 901 const struct TALER_CurrencySpecification *cspec) 902 { 903 json_t *ca; 904 905 ca = json_array (); 906 GNUNET_assert (NULL != ca); 907 for (unsigned int i = 0; i<cspec->num_common_amounts; i++) 908 GNUNET_assert ( 909 0 == 910 json_array_append_new ( 911 ca, 912 TALER_JSON_from_amount (&cspec->common_amounts[i]))); 913 return GNUNET_JSON_PACK ( 914 GNUNET_JSON_pack_string ("name", 915 cspec->name), 916 /* 'currency' is deprecated as of exchange v18 and merchant v6; 917 remove this line once current-age > 6*/ 918 GNUNET_JSON_pack_string ("currency", 919 cspec->currency), 920 GNUNET_JSON_pack_array_steal ("common_amounts", 921 ca), 922 GNUNET_JSON_pack_uint64 ("num_fractional_input_digits", 923 cspec->num_fractional_input_digits), 924 GNUNET_JSON_pack_uint64 ("num_fractional_normal_digits", 925 cspec->num_fractional_normal_digits), 926 GNUNET_JSON_pack_uint64 ("num_fractional_trailing_zero_digits", 927 cspec->num_fractional_trailing_zero_digits), 928 GNUNET_JSON_pack_object_incref ("alt_unit_names", 929 cspec->map_alt_unit_names)); 930 } 931 932 933 /* End of json/json.c */