exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c (12404B)
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 ("name", 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_INTERNAL_SERVER_ERROR: 229 lr.hr.ec = TALER_JSON_get_error_code (j); 230 lr.hr.hint = TALER_JSON_get_error_hint (j); 231 /* Server had an internal issue; we should retry, but this API 232 leaves this to the application */ 233 break; 234 default: 235 /* unexpected response code */ 236 GNUNET_break_op (0); 237 lr.hr.ec = TALER_JSON_get_error_code (j); 238 lr.hr.hint = TALER_JSON_get_error_hint (j); 239 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 240 "Unexpected response code %u/%d for GET KYC statistics\n", 241 (unsigned int) response_code, 242 (int) lr.hr.ec); 243 break; 244 } 245 if (NULL != aksh->cb) 246 aksh->cb (aksh->cb_cls, 247 &lr); 248 TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh); 249 } 250 251 252 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle * 253 TALER_EXCHANGE_get_aml_kyc_statistics_create ( 254 struct GNUNET_CURL_Context *ctx, 255 const char *url, 256 const struct TALER_AmlOfficerPrivateKeyP *officer_priv, 257 const char *names) 258 { 259 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh; 260 261 aksh = GNUNET_new (struct TALER_EXCHANGE_GetAmlKycStatisticsHandle); 262 aksh->ctx = ctx; 263 aksh->base_url = GNUNET_strdup (url); 264 aksh->officer_priv = *officer_priv; 265 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 266 &aksh->officer_pub.eddsa_pub); 267 aksh->names = GNUNET_strdup (names); 268 /* Default: no start date filter, no end date filter */ 269 aksh->options.start_date = GNUNET_TIME_UNIT_ZERO_TS; 270 aksh->options.end_date = GNUNET_TIME_UNIT_FOREVER_TS; 271 return aksh; 272 } 273 274 275 enum GNUNET_GenericReturnValue 276 TALER_EXCHANGE_get_aml_kyc_statistics_set_options_ ( 277 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, 278 unsigned int num_options, 279 const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *options) 280 { 281 for (unsigned int i = 0; i < num_options; i++) 282 { 283 const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *opt 284 = &options[i]; 285 286 switch (opt->option) 287 { 288 case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END: 289 return GNUNET_OK; 290 case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_START_DATE: 291 aksh->options.start_date = opt->details.start_date; 292 break; 293 case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END_DATE: 294 aksh->options.end_date = opt->details.end_date; 295 break; 296 } 297 } 298 return GNUNET_OK; 299 } 300 301 302 enum TALER_ErrorCode 303 TALER_EXCHANGE_get_aml_kyc_statistics_start ( 304 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, 305 TALER_EXCHANGE_GetAmlKycStatisticsCallback cb, 306 TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls) 307 { 308 struct TALER_AmlOfficerSignatureP officer_sig; 309 CURL *eh; 310 char sd_str[32]; 311 char ed_str[32]; 312 const char *sd = NULL; 313 const char *ed = NULL; 314 315 if (NULL != aksh->job) 316 { 317 GNUNET_break (0); 318 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 319 } 320 aksh->cb = cb; 321 aksh->cb_cls = cb_cls; 322 TALER_officer_aml_query_sign (&aksh->officer_priv, 323 &officer_sig); 324 if (! GNUNET_TIME_absolute_is_zero (aksh->options.start_date.abs_time)) 325 { 326 unsigned long long sec; 327 328 sec = (unsigned long long) GNUNET_TIME_timestamp_to_s ( 329 aksh->options.start_date); 330 GNUNET_snprintf (sd_str, 331 sizeof (sd_str), 332 "%llu", 333 sec); 334 sd = sd_str; 335 } 336 if (! GNUNET_TIME_absolute_is_never (aksh->options.end_date.abs_time)) 337 { 338 unsigned long long sec; 339 340 sec = (unsigned long long) GNUNET_TIME_timestamp_to_s ( 341 aksh->options.end_date); 342 GNUNET_snprintf (ed_str, 343 sizeof (ed_str), 344 "%llu", 345 sec); 346 ed = ed_str; 347 } 348 { 349 char pub_str[sizeof (aksh->officer_pub) * 2]; 350 char arg_str[sizeof (aksh->officer_pub) * 2 + 32]; 351 char *end; 352 353 end = GNUNET_STRINGS_data_to_string ( 354 &aksh->officer_pub, 355 sizeof (aksh->officer_pub), 356 pub_str, 357 sizeof (pub_str)); 358 *end = '\0'; 359 GNUNET_snprintf (arg_str, 360 sizeof (arg_str), 361 "aml/%s/kyc-statistics/%s", 362 pub_str, 363 aksh->names); 364 aksh->url = TALER_url_join (aksh->base_url, 365 arg_str, 366 "start_date", 367 sd, 368 "end_date", 369 ed, 370 NULL); 371 } 372 if (NULL == aksh->url) 373 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 374 eh = TALER_EXCHANGE_curl_easy_get_ (aksh->url); 375 if (NULL == eh) 376 { 377 GNUNET_free (aksh->url); 378 aksh->url = NULL; 379 return TALER_EC_GENERIC_ALLOCATION_FAILURE; 380 } 381 { 382 struct curl_slist *job_headers = NULL; 383 char *hdr; 384 char sig_str[sizeof (officer_sig) * 2]; 385 char *end; 386 387 end = GNUNET_STRINGS_data_to_string ( 388 &officer_sig, 389 sizeof (officer_sig), 390 sig_str, 391 sizeof (sig_str)); 392 *end = '\0'; 393 GNUNET_asprintf (&hdr, 394 "%s: %s", 395 TALER_AML_OFFICER_SIGNATURE_HEADER, 396 sig_str); 397 job_headers = curl_slist_append (NULL, 398 hdr); 399 GNUNET_free (hdr); 400 job_headers = curl_slist_append (job_headers, 401 "Content-type: application/json"); 402 aksh->job = GNUNET_CURL_job_add2 (aksh->ctx, 403 eh, 404 job_headers, 405 &handle_get_aml_kyc_statistics_finished, 406 aksh); 407 curl_slist_free_all (job_headers); 408 } 409 if (NULL == aksh->job) 410 { 411 GNUNET_free (aksh->url); 412 aksh->url = NULL; 413 return TALER_EC_GENERIC_ALLOCATION_FAILURE; 414 } 415 return TALER_EC_NONE; 416 } 417 418 419 void 420 TALER_EXCHANGE_get_aml_kyc_statistics_cancel ( 421 struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh) 422 { 423 if (NULL != aksh->job) 424 { 425 GNUNET_CURL_job_cancel (aksh->job); 426 aksh->job = NULL; 427 } 428 GNUNET_free (aksh->url); 429 GNUNET_free (aksh->base_url); 430 GNUNET_free (aksh->names); 431 GNUNET_free (aksh); 432 } 433 434 435 /* end of exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c */