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