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