merchant_api_get-private-statistics-amount-SLUG.c (15693B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, 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 Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file src/lib/merchant_api_get-private-statistics-amount-SLUG.c 19 * @brief Implementation of the GET /private/statistics-amount/$SLUG request 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include <taler/merchant/get-private-statistics-amount-SLUG.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of statistics entries we return. 35 */ 36 #define MAX_STATISTICS 1024 37 38 39 /** 40 * Handle for a GET /private/statistics-amount/$SLUG operation. 41 */ 42 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle 43 { 44 /** 45 * Base URL of the merchant backend. 46 */ 47 char *base_url; 48 49 /** 50 * The full URL for this request. 51 */ 52 char *url; 53 54 /** 55 * Handle for the request. 56 */ 57 struct GNUNET_CURL_Job *job; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb; 63 64 /** 65 * Closure for @a cb. 66 */ 67 TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Statistics slug. 76 */ 77 char *slug; 78 79 /** 80 * Aggregation mode. 81 */ 82 enum TALER_MERCHANT_StatisticsType stype; 83 84 /** 85 * Whether stype was explicitly set. 86 */ 87 bool have_stype; 88 }; 89 90 91 /** 92 * Parse interval and bucket data from the JSON response. 93 * 94 * @param json overall JSON reply 95 * @param jbuckets JSON array with bucket data 96 * @param buckets_description human-readable description for buckets 97 * @param jintervals JSON array with interval data 98 * @param intervals_description human-readable description for intervals 99 * @param sah operation handle 100 * @return #GNUNET_OK on success 101 */ 102 static enum GNUNET_GenericReturnValue 103 parse_intervals_and_buckets_amt ( 104 const json_t *json, 105 const json_t *jbuckets, 106 const char *buckets_description, 107 const json_t *jintervals, 108 const char *intervals_description, 109 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah) 110 { 111 unsigned int resp_buckets_len = json_array_size (jbuckets); 112 unsigned int resp_intervals_len = json_array_size (jintervals); 113 size_t total_amounts = 0; 114 size_t amounts_pos = 0; 115 116 if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) || 117 (json_array_size (jintervals) != (size_t) resp_intervals_len) || 118 (resp_buckets_len > MAX_STATISTICS) || 119 (resp_intervals_len > MAX_STATISTICS) ) 120 { 121 GNUNET_break (0); 122 return GNUNET_SYSERR; 123 } 124 { 125 size_t index; 126 json_t *value; 127 128 json_array_foreach (jintervals, index, value) { 129 const json_t *amounts_arr; 130 struct GNUNET_JSON_Specification spec[] = { 131 GNUNET_JSON_spec_array_const ("cumulative_amounts", 132 &amounts_arr), 133 GNUNET_JSON_spec_end () 134 }; 135 136 if (GNUNET_OK != 137 GNUNET_JSON_parse (value, 138 spec, 139 NULL, NULL)) 140 { 141 GNUNET_break_op (0); 142 return GNUNET_SYSERR; 143 } 144 total_amounts += json_array_size (amounts_arr); 145 } 146 147 json_array_foreach (jbuckets, index, value) { 148 const json_t *amounts_arr; 149 struct GNUNET_JSON_Specification spec[] = { 150 GNUNET_JSON_spec_array_const ("cumulative_amounts", 151 &amounts_arr), 152 GNUNET_JSON_spec_end () 153 }; 154 155 if (GNUNET_OK != 156 GNUNET_JSON_parse (value, 157 spec, 158 NULL, NULL)) 159 { 160 GNUNET_break_op (0); 161 return GNUNET_SYSERR; 162 } 163 total_amounts += json_array_size (amounts_arr); 164 } 165 if (total_amounts > MAX_STATISTICS * (size_t) MAX_STATISTICS) 166 { 167 /* Bound the stack VLA declared below by an untrusted response. */ 168 GNUNET_break_op (0); 169 return GNUNET_SYSERR; 170 } 171 } 172 { 173 struct TALER_Amount *glob_amt_arr 174 = GNUNET_new_array (GNUNET_NZL (total_amounts), 175 struct TALER_Amount); 176 struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket resp_buckets[ 177 GNUNET_NZL (resp_buckets_len)]; 178 struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval resp_intervals[ 179 GNUNET_NZL (resp_intervals_len)]; 180 size_t index; 181 json_t *value; 182 enum GNUNET_GenericReturnValue ret; 183 184 ret = GNUNET_OK; 185 json_array_foreach (jintervals, index, value) { 186 struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval *jinterval 187 = &resp_intervals[index]; 188 const json_t *amounts_arr; 189 size_t amounts_len; 190 struct GNUNET_JSON_Specification spec[] = { 191 GNUNET_JSON_spec_timestamp ("start_time", 192 &jinterval->start_time), 193 GNUNET_JSON_spec_array_const ("cumulative_amounts", 194 &amounts_arr), 195 GNUNET_JSON_spec_end () 196 }; 197 198 if (GNUNET_OK != 199 GNUNET_JSON_parse (value, 200 spec, 201 NULL, NULL)) 202 { 203 GNUNET_break_op (0); 204 GNUNET_free (glob_amt_arr); 205 return GNUNET_SYSERR; 206 } 207 amounts_len = json_array_size (amounts_arr); 208 { 209 struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos]; 210 size_t aindex; 211 json_t *avalue; 212 213 amounts_pos += amounts_len; 214 jinterval->cumulative_amount_len = amounts_len; 215 jinterval->cumulative_amounts = amt_arr; 216 json_array_foreach (amounts_arr, aindex, avalue) { 217 if (! json_is_string (avalue)) 218 { 219 GNUNET_break_op (0); 220 GNUNET_free (glob_amt_arr); 221 return GNUNET_SYSERR; 222 } 223 if (GNUNET_OK != 224 TALER_string_to_amount (json_string_value (avalue), 225 &amt_arr[aindex])) 226 { 227 GNUNET_break_op (0); 228 GNUNET_free (glob_amt_arr); 229 return GNUNET_SYSERR; 230 } 231 } 232 } 233 } 234 json_array_foreach (jbuckets, index, value) { 235 struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket *jbucket 236 = &resp_buckets[index]; 237 const json_t *amounts_arr; 238 size_t amounts_len; 239 struct GNUNET_JSON_Specification spec[] = { 240 GNUNET_JSON_spec_timestamp ("start_time", 241 &jbucket->start_time), 242 GNUNET_JSON_spec_timestamp ("end_time", 243 &jbucket->end_time), 244 GNUNET_JSON_spec_string ("range", 245 &jbucket->range), 246 GNUNET_JSON_spec_array_const ("cumulative_amounts", 247 &amounts_arr), 248 GNUNET_JSON_spec_end () 249 }; 250 251 if (GNUNET_OK != 252 GNUNET_JSON_parse (value, 253 spec, 254 NULL, NULL)) 255 { 256 GNUNET_break_op (0); 257 ret = GNUNET_SYSERR; 258 break; 259 } 260 amounts_len = json_array_size (amounts_arr); 261 { 262 struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos]; 263 size_t aindex; 264 json_t *avalue; 265 266 amounts_pos += amounts_len; 267 jbucket->cumulative_amount_len = amounts_len; 268 jbucket->cumulative_amounts = amt_arr; 269 json_array_foreach (amounts_arr, aindex, avalue) { 270 if (! json_is_string (avalue)) 271 { 272 GNUNET_break_op (0); 273 GNUNET_free (glob_amt_arr); 274 return GNUNET_SYSERR; 275 } 276 if (GNUNET_OK != 277 TALER_string_to_amount (json_string_value (avalue), 278 &amt_arr[aindex])) 279 { 280 GNUNET_break_op (0); 281 GNUNET_free (glob_amt_arr); 282 return GNUNET_SYSERR; 283 } 284 } 285 } 286 } 287 if (GNUNET_OK == ret) 288 { 289 struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = { 290 .hr.http_status = MHD_HTTP_OK, 291 .hr.reply = json, 292 .details.ok.buckets_length = resp_buckets_len, 293 .details.ok.buckets = resp_buckets, 294 .details.ok.buckets_description = buckets_description, 295 .details.ok.intervals_length = resp_intervals_len, 296 .details.ok.intervals = resp_intervals, 297 .details.ok.intervals_description = intervals_description, 298 }; 299 300 sah->cb (sah->cb_cls, 301 &sagr); 302 sah->cb = NULL; 303 } 304 GNUNET_free (glob_amt_arr); 305 return ret; 306 } 307 } 308 309 310 /** 311 * Function called when we're done processing the 312 * HTTP GET /private/statistics-amount/$SLUG request. 313 * 314 * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle` 315 * @param response_code HTTP response code, 0 on error 316 * @param response response body, NULL if not in JSON 317 */ 318 static void 319 handle_get_statistics_amount_finished (void *cls, 320 long response_code, 321 const void *response) 322 { 323 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah = cls; 324 const json_t *json = response; 325 struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = { 326 .hr.http_status = (unsigned int) response_code, 327 .hr.reply = json 328 }; 329 330 sah->job = NULL; 331 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 332 "Got /private/statistics-amount/$SLUG response with status code %u\n", 333 (unsigned int) response_code); 334 switch (response_code) 335 { 336 case MHD_HTTP_OK: 337 { 338 const json_t *buckets; 339 const json_t *intervals; 340 const char *buckets_description = NULL; 341 const char *intervals_description = NULL; 342 struct GNUNET_JSON_Specification spec[] = { 343 GNUNET_JSON_spec_array_const ("buckets", 344 &buckets), 345 GNUNET_JSON_spec_mark_optional ( 346 GNUNET_JSON_spec_string ("buckets_description", 347 &buckets_description), 348 NULL), 349 GNUNET_JSON_spec_array_const ("intervals", 350 &intervals), 351 GNUNET_JSON_spec_mark_optional ( 352 GNUNET_JSON_spec_string ("intervals_description", 353 &intervals_description), 354 NULL), 355 GNUNET_JSON_spec_end () 356 }; 357 358 if (GNUNET_OK != 359 GNUNET_JSON_parse (json, 360 spec, 361 NULL, NULL)) 362 { 363 sagr.hr.http_status = 0; 364 sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 365 break; 366 } 367 if (GNUNET_OK == 368 parse_intervals_and_buckets_amt (json, 369 buckets, 370 buckets_description, 371 intervals, 372 intervals_description, 373 sah)) 374 { 375 TALER_MERCHANT_get_private_statistics_amount_cancel (sah); 376 return; 377 } 378 sagr.hr.http_status = 0; 379 sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 380 break; 381 } 382 case MHD_HTTP_UNAUTHORIZED: 383 sagr.hr.ec = TALER_JSON_get_error_code (json); 384 sagr.hr.hint = TALER_JSON_get_error_hint (json); 385 break; 386 case MHD_HTTP_NOT_FOUND: 387 sagr.hr.ec = TALER_JSON_get_error_code (json); 388 sagr.hr.hint = TALER_JSON_get_error_hint (json); 389 break; 390 default: 391 sagr.hr.ec = TALER_JSON_get_error_code (json); 392 sagr.hr.hint = TALER_JSON_get_error_hint (json); 393 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 394 "Unexpected response code %u/%d\n", 395 (unsigned int) response_code, 396 (int) sagr.hr.ec); 397 break; 398 } 399 sah->cb (sah->cb_cls, 400 &sagr); 401 TALER_MERCHANT_get_private_statistics_amount_cancel (sah); 402 } 403 404 405 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle * 406 TALER_MERCHANT_get_private_statistics_amount_create ( 407 struct GNUNET_CURL_Context *ctx, 408 const char *url, 409 const char *slug) 410 { 411 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah; 412 413 sah = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle); 414 sah->ctx = ctx; 415 sah->base_url = GNUNET_strdup (url); 416 sah->slug = GNUNET_strdup (slug); 417 sah->stype = TALER_MERCHANT_STATISTICS_ALL; 418 return sah; 419 } 420 421 422 enum GNUNET_GenericReturnValue 423 TALER_MERCHANT_get_private_statistics_amount_set_options_ ( 424 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah, 425 unsigned int num_options, 426 const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *options) 427 { 428 for (unsigned int i = 0; i < num_options; i++) 429 { 430 const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *opt = 431 &options[i]; 432 433 switch (opt->option) 434 { 435 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_END: 436 return GNUNET_OK; 437 case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_TYPE: 438 sah->stype = opt->details.type; 439 sah->have_stype = true; 440 break; 441 default: 442 GNUNET_break (0); 443 return GNUNET_NO; 444 } 445 } 446 return GNUNET_OK; 447 } 448 449 450 enum TALER_ErrorCode 451 TALER_MERCHANT_get_private_statistics_amount_start ( 452 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah, 453 TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb, 454 TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls) 455 { 456 CURL *eh; 457 458 sah->cb = cb; 459 sah->cb_cls = cb_cls; 460 { 461 const char *filter = NULL; 462 char *path; 463 464 switch (sah->stype) 465 { 466 case TALER_MERCHANT_STATISTICS_BY_BUCKET: 467 filter = "bucket"; 468 break; 469 case TALER_MERCHANT_STATISTICS_BY_INTERVAL: 470 filter = "interval"; 471 break; 472 case TALER_MERCHANT_STATISTICS_ALL: 473 filter = NULL; 474 break; 475 } 476 GNUNET_asprintf (&path, 477 "private/statistics-amount/%s", 478 sah->slug); 479 sah->url = TALER_url_join (sah->base_url, 480 path, 481 "by", 482 filter, 483 NULL); 484 GNUNET_free (path); 485 } 486 if (NULL == sah->url) 487 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 488 eh = TALER_MERCHANT_curl_easy_get_ (sah->url); 489 if (NULL == eh) 490 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 491 sah->job = GNUNET_CURL_job_add (sah->ctx, 492 eh, 493 &handle_get_statistics_amount_finished, 494 sah); 495 if (NULL == sah->job) 496 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 497 return TALER_EC_NONE; 498 } 499 500 501 void 502 TALER_MERCHANT_get_private_statistics_amount_cancel ( 503 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah) 504 { 505 if (NULL != sah->job) 506 { 507 GNUNET_CURL_job_cancel (sah->job); 508 sah->job = NULL; 509 } 510 GNUNET_free (sah->url); 511 GNUNET_free (sah->base_url); 512 GNUNET_free (sah->slug); 513 GNUNET_free (sah); 514 } 515 516 517 /* end of merchant_api_get-private-statistics-amount-SLUG-new.c */