exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c (12550B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 2024, 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 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 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c 19 * @brief Implementation of the /aml/$OFFICER_PUB/kyc-statistics/$NAME request 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <microhttpd.h> /* just for HTTP status codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_curl_lib.h> 26 #include "taler/taler_exchange_service.h" 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_handle.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 #include "taler/taler-exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h" 32 33 34 /** 35 * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAMES Handle (new API) 36 */ 37 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle 38 { 39 40 /** 41 * The base URL of the exchange. 42 */ 43 char *base_url; 44 45 /** 46 * The full URL for this request. 47 */ 48 char *url; 49 50 /** 51 * Handle for the request. 52 */ 53 struct GNUNET_CURL_Job *job; 54 55 /** 56 * Function to call with the result. 57 */ 58 TALER_EXCHANGE_GetAmlKycStatisticsCallback cb; 59 60 /** 61 * Closure for @e cb. 62 */ 63 TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls; 64 65 /** 66 * CURL context to use. 67 */ 68 struct GNUNET_CURL_Context *ctx; 69 70 /** 71 * Public key of the AML officer. 72 */ 73 struct TALER_AmlOfficerPublicKeyP officer_pub; 74 75 /** 76 * Private key of the AML officer. 77 */ 78 struct TALER_AmlOfficerPrivateKeyP officer_priv; 79 80 /** 81 * Space-separated list of event type names to count. 82 */ 83 char *names; 84 85 /** 86 * Options for this request. 87 */ 88 struct 89 { 90 /** 91 * Start date for statistics window. Zero means "from the beginning". 92 */ 93 struct GNUNET_TIME_Timestamp start_date; 94 95 /** 96 * End date for statistics window. #GNUNET_TIME_UNIT_FOREVER_ABS means "up to now". 97 */ 98 struct GNUNET_TIME_Timestamp end_date; 99 } options; 100 101 }; 102 103 104 /** 105 * Parse the provided statistics data from the "200 OK" response. 106 * 107 * @param[in,out] aksh handle (callback may be zero'ed out) 108 * @param json json reply 109 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 110 */ 111 static enum GNUNET_GenericReturnValue 112 parse_stats_ok_new ( 113 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, 114 const json_t *json) 115 { 116 struct TALER_EXCHANGE_GetAmlKycStatisticsResponse lr = { 117 .hr.reply = json, 118 .hr.http_status = MHD_HTTP_OK 119 }; 120 const json_t *jstatistics; 121 struct GNUNET_JSON_Specification spec[] = { 122 GNUNET_JSON_spec_array_const ("statistics", 123 &jstatistics), 124 GNUNET_JSON_spec_end () 125 }; 126 127 if (GNUNET_OK != 128 GNUNET_JSON_parse (json, 129 spec, 130 NULL, 131 NULL)) 132 { 133 GNUNET_break_op (0); 134 return GNUNET_SYSERR; 135 } 136 lr.details.ok.statistics_length = json_array_size (jstatistics); 137 { 138 struct TALER_EXCHANGE_GetAmlKycStatisticsEventCounter statistics[ 139 GNUNET_NZL (lr.details.ok.statistics_length)]; 140 json_t *obj; 141 size_t idx; 142 143 memset (statistics, 144 0, 145 sizeof (statistics)); 146 lr.details.ok.statistics = statistics; 147 json_array_foreach (jstatistics, idx, obj) 148 { 149 struct TALER_EXCHANGE_GetAmlKycStatisticsEventCounter *ec 150 = &statistics[idx]; 151 struct GNUNET_JSON_Specification ispec[] = { 152 GNUNET_JSON_spec_string ("event", 153 &ec->name), 154 GNUNET_JSON_spec_uint64 ("counter", 155 &ec->counter), 156 GNUNET_JSON_spec_end () 157 }; 158 159 if (GNUNET_OK != 160 GNUNET_JSON_parse (obj, 161 ispec, 162 NULL, 163 NULL)) 164 { 165 GNUNET_break_op (0); 166 return GNUNET_SYSERR; 167 } 168 } 169 aksh->cb (aksh->cb_cls, 170 &lr); 171 aksh->cb = NULL; 172 } 173 return GNUNET_OK; 174 } 175 176 177 /** 178 * Function called when we're done processing the 179 * HTTP GET /aml/$OFFICER_PUB/kyc-statistics/$NAMES request. 180 * 181 * @param cls the `struct TALER_EXCHANGE_GetAmlKycStatisticsHandle` 182 * @param response_code HTTP response code, 0 on error 183 * @param response parsed JSON result, NULL on error 184 */ 185 static void 186 handle_get_aml_kyc_statistics_finished (void *cls, 187 long response_code, 188 const void *response) 189 { 190 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh = cls; 191 const json_t *j = response; 192 struct TALER_EXCHANGE_GetAmlKycStatisticsResponse lr = { 193 .hr.reply = j, 194 .hr.http_status = (unsigned int) response_code 195 }; 196 197 aksh->job = NULL; 198 switch (response_code) 199 { 200 case 0: 201 lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 202 break; 203 case MHD_HTTP_OK: 204 if (GNUNET_OK != 205 parse_stats_ok_new (aksh, 206 j)) 207 { 208 GNUNET_break_op (0); 209 lr.hr.http_status = 0; 210 lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 211 break; 212 } 213 GNUNET_assert (NULL == aksh->cb); 214 TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh); 215 return; 216 case MHD_HTTP_NO_CONTENT: 217 break; 218 case MHD_HTTP_BAD_REQUEST: 219 lr.hr.ec = TALER_JSON_get_error_code (j); 220 lr.hr.hint = TALER_JSON_get_error_hint (j); 221 /* This should never happen, either us or the exchange is buggy 222 (or API version conflict); just pass JSON reply to the application */ 223 break; 224 case MHD_HTTP_FORBIDDEN: 225 lr.hr.ec = TALER_JSON_get_error_code (j); 226 lr.hr.hint = TALER_JSON_get_error_hint (j); 227 break; 228 case MHD_HTTP_NOT_FOUND: 229 lr.hr.ec = TALER_JSON_get_error_code (j); 230 lr.hr.hint = TALER_JSON_get_error_hint (j); 231 break; 232 case MHD_HTTP_URI_TOO_LONG: 233 lr.hr.ec = TALER_JSON_get_error_code (j); 234 lr.hr.hint = TALER_JSON_get_error_hint (j); 235 break; 236 case MHD_HTTP_INTERNAL_SERVER_ERROR: 237 lr.hr.ec = TALER_JSON_get_error_code (j); 238 lr.hr.hint = TALER_JSON_get_error_hint (j); 239 /* Server had an internal issue; we should retry, but this API 240 leaves this to the application */ 241 break; 242 default: 243 /* unexpected response code */ 244 GNUNET_break_op (0); 245 lr.hr.ec = TALER_JSON_get_error_code (j); 246 lr.hr.hint = TALER_JSON_get_error_hint (j); 247 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 248 "Unexpected response code %u/%d for GET KYC statistics\n", 249 (unsigned int) response_code, 250 (int) lr.hr.ec); 251 break; 252 } 253 if (NULL != aksh->cb) 254 aksh->cb (aksh->cb_cls, 255 &lr); 256 TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh); 257 } 258 259 260 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle * 261 TALER_EXCHANGE_get_aml_kyc_statistics_create ( 262 struct GNUNET_CURL_Context *ctx, 263 const char *url, 264 const struct TALER_AmlOfficerPrivateKeyP *officer_priv, 265 const char *names) 266 { 267 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh; 268 269 aksh = GNUNET_new (struct TALER_EXCHANGE_GetAmlKycStatisticsHandle); 270 aksh->ctx = ctx; 271 aksh->base_url = GNUNET_strdup (url); 272 aksh->officer_priv = *officer_priv; 273 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 274 &aksh->officer_pub.eddsa_pub); 275 aksh->names = GNUNET_strdup (names); 276 /* Default: no start date filter, no end date filter */ 277 aksh->options.start_date = GNUNET_TIME_UNIT_ZERO_TS; 278 aksh->options.end_date = GNUNET_TIME_UNIT_FOREVER_TS; 279 return aksh; 280 } 281 282 283 enum GNUNET_GenericReturnValue 284 TALER_EXCHANGE_get_aml_kyc_statistics_set_options_ ( 285 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, 286 unsigned int num_options, 287 const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *options) 288 { 289 for (unsigned int i = 0; i < num_options; i++) 290 { 291 const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *opt 292 = &options[i]; 293 294 switch (opt->option) 295 { 296 case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END: 297 return GNUNET_OK; 298 case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_START_DATE: 299 aksh->options.start_date = opt->details.start_date; 300 break; 301 case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END_DATE: 302 aksh->options.end_date = opt->details.end_date; 303 break; 304 } 305 } 306 return GNUNET_OK; 307 } 308 309 310 enum TALER_ErrorCode 311 TALER_EXCHANGE_get_aml_kyc_statistics_start ( 312 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, 313 TALER_EXCHANGE_GetAmlKycStatisticsCallback cb, 314 TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls) 315 { 316 struct TALER_AmlOfficerSignatureP officer_sig; 317 CURL *eh; 318 char sd_str[32]; 319 char ed_str[32]; 320 const char *sd = NULL; 321 const char *ed = NULL; 322 323 if (NULL != aksh->job) 324 { 325 GNUNET_break (0); 326 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 327 } 328 aksh->cb = cb; 329 aksh->cb_cls = cb_cls; 330 TALER_officer_aml_query_sign (&aksh->officer_priv, 331 &officer_sig); 332 if (! GNUNET_TIME_absolute_is_zero (aksh->options.start_date.abs_time)) 333 { 334 unsigned long long sec; 335 336 sec = (unsigned long long) GNUNET_TIME_timestamp_to_s ( 337 aksh->options.start_date); 338 GNUNET_snprintf (sd_str, 339 sizeof (sd_str), 340 "%llu", 341 sec); 342 sd = sd_str; 343 } 344 if (! GNUNET_TIME_absolute_is_never (aksh->options.end_date.abs_time)) 345 { 346 unsigned long long sec; 347 348 sec = (unsigned long long) GNUNET_TIME_timestamp_to_s ( 349 aksh->options.end_date); 350 GNUNET_snprintf (ed_str, 351 sizeof (ed_str), 352 "%llu", 353 sec); 354 ed = ed_str; 355 } 356 { 357 char pub_str[sizeof (aksh->officer_pub) * 2]; 358 char arg_str[sizeof (aksh->officer_pub) * 2 + 32]; 359 char *end; 360 361 end = GNUNET_STRINGS_data_to_string ( 362 &aksh->officer_pub, 363 sizeof (aksh->officer_pub), 364 pub_str, 365 sizeof (pub_str)); 366 *end = '\0'; 367 GNUNET_snprintf (arg_str, 368 sizeof (arg_str), 369 "aml/%s/kyc-statistics/%s", 370 pub_str, 371 aksh->names); 372 aksh->url = TALER_url_join (aksh->base_url, 373 arg_str, 374 "start_date", 375 sd, 376 "end_date", 377 ed, 378 NULL); 379 } 380 if (NULL == aksh->url) 381 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 382 eh = TALER_EXCHANGE_curl_easy_get_ (aksh->url); 383 if (NULL == eh) 384 { 385 GNUNET_free (aksh->url); 386 aksh->url = NULL; 387 return TALER_EC_GENERIC_ALLOCATION_FAILURE; 388 } 389 { 390 struct curl_slist *job_headers = NULL; 391 char *hdr; 392 char sig_str[sizeof (officer_sig) * 2]; 393 char *end; 394 395 end = GNUNET_STRINGS_data_to_string ( 396 &officer_sig, 397 sizeof (officer_sig), 398 sig_str, 399 sizeof (sig_str)); 400 *end = '\0'; 401 GNUNET_asprintf (&hdr, 402 "%s: %s", 403 TALER_AML_OFFICER_SIGNATURE_HEADER, 404 sig_str); 405 job_headers = curl_slist_append (NULL, 406 hdr); 407 GNUNET_free (hdr); 408 aksh->job = GNUNET_CURL_job_add2 (aksh->ctx, 409 eh, 410 job_headers, 411 &handle_get_aml_kyc_statistics_finished, 412 aksh); 413 curl_slist_free_all (job_headers); 414 } 415 if (NULL == aksh->job) 416 { 417 GNUNET_free (aksh->url); 418 aksh->url = NULL; 419 return TALER_EC_GENERIC_ALLOCATION_FAILURE; 420 } 421 return TALER_EC_NONE; 422 } 423 424 425 void 426 TALER_EXCHANGE_get_aml_kyc_statistics_cancel ( 427 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh) 428 { 429 if (NULL != aksh->job) 430 { 431 GNUNET_CURL_job_cancel (aksh->job); 432 aksh->job = NULL; 433 } 434 GNUNET_free (aksh->url); 435 GNUNET_free (aksh->base_url); 436 GNUNET_free (aksh->names); 437 GNUNET_free (aksh); 438 } 439 440 441 /* end of exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c */