donau_api_batch_issue_receipts.c (10676B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file lib/donau_api_batch_issue_receipts.c 22 * @brief Implementation of the "handle" component of the donau's HTTP API 23 * @author Lukas Matyja 24 */ 25 #include <gnunet/gnunet_curl_lib.h> 26 #include <taler/taler_json_lib.h> 27 #include <taler/taler_curl_lib.h> 28 #include "donau_service.h" 29 #include "donau_util.h" 30 #include "donau_api_curl_defaults.h" 31 #include "donau_json_lib.h" 32 33 34 /** 35 * Handle for a POST /batch-issue/$CHARITY_ID request. 36 */ 37 struct DONAU_BatchIssueReceiptHandle 38 { 39 /** 40 * The url for the /batch-issue/$CHARITY_ID request. 41 */ 42 char *url; 43 44 /** 45 * Minor context that holds body and headers. 46 */ 47 struct TALER_CURL_PostContext post_ctx; 48 49 /** 50 * Entry for this request with the `struct GNUNET_CURL_Context`. 51 */ 52 struct GNUNET_CURL_Job *job; 53 54 /** 55 * Function to call with the result. 56 */ 57 DONAU_BatchIssueReceiptsCallback cb; 58 59 /** 60 * BUDI-key-pair signature. 61 */ 62 struct DONAU_CharitySignatureP charity_sig; 63 64 /** 65 * number of requested signatures. 66 */ 67 size_t num_blinded_sigs; 68 69 /** 70 * Closure to pass to @e cb. 71 */ 72 void *cb_cls; 73 74 /** 75 * Reference to the execution context. 76 */ 77 struct GNUNET_CURL_Context *ctx; 78 79 }; 80 81 82 /** 83 * Decode the JSON in @a resp_obj from the /batch-issue/$CHARITY_ID response 84 * and store the data in the @a biresp. 85 * 86 * @param[in] resp_obj JSON object to parse 87 * @param[in] birh contains the callback function 88 * @param[out] biresp where to store the results we decoded 89 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 90 * (malformed JSON) 91 */ 92 static enum GNUNET_GenericReturnValue 93 handle_batch_issue_ok (const json_t *resp_obj, 94 struct DONAU_BatchIssueReceiptHandle *birh, 95 struct DONAU_BatchIssueResponse *biresp) 96 { 97 const json_t *j_blind_signatures; 98 struct GNUNET_JSON_Specification spec[] = { 99 TALER_JSON_spec_amount_any ("issued_amount", 100 &biresp->details.ok.issued_amount), 101 GNUNET_JSON_spec_array_const ("blind_signatures", 102 &j_blind_signatures), 103 GNUNET_JSON_spec_end () 104 }; 105 106 if (GNUNET_OK != 107 GNUNET_JSON_parse (resp_obj, 108 spec, 109 NULL, 110 NULL)) 111 { 112 GNUNET_break_op (0); 113 return GNUNET_SYSERR; 114 } 115 if ( (NULL == j_blind_signatures) || 116 (! json_is_array (j_blind_signatures)) ) 117 { 118 GNUNET_break (0); 119 return GNUNET_SYSERR; 120 } 121 biresp->details.ok.num_blinded_sigs 122 = json_array_size (j_blind_signatures); 123 biresp->details.ok.blinded_sigs = 124 GNUNET_new_array (birh->num_blinded_sigs, 125 struct DONAU_BlindedDonationUnitSignature); 126 { 127 size_t index; 128 json_t *du_sig_obj; 129 130 json_array_foreach (j_blind_signatures, 131 index, 132 du_sig_obj) 133 { 134 struct GNUNET_JSON_Specification ispec[] = { 135 DONAU_JSON_spec_blinded_donation_unit_sig ( 136 "blinded_signature", 137 &biresp->details.ok.blinded_sigs[index]), 138 GNUNET_JSON_spec_end () 139 }; 140 141 if (GNUNET_OK != 142 GNUNET_JSON_parse (du_sig_obj, 143 ispec, 144 NULL, 145 NULL)) 146 { 147 GNUNET_break_op (0); 148 return GNUNET_SYSERR; 149 } 150 } 151 } 152 birh->cb (birh->cb_cls, 153 biresp); 154 birh->cb = NULL; 155 for (unsigned int i=0; i<biresp->details.ok.num_blinded_sigs; i++) 156 { 157 struct DONAU_BlindedDonationUnitSignature *sig 158 = &biresp->details.ok.blinded_sigs[i]; 159 160 GNUNET_CRYPTO_blinded_sig_decref (sig->blinded_sig); 161 } 162 GNUNET_free (biresp->details.ok.blinded_sigs); 163 return GNUNET_OK; 164 } 165 166 167 /** 168 * Transform issue receipt request into JSON. 169 * 170 * @param num_bkp number of budi-key-pairs in @bkp 171 * @param bkp budi-key-pair array 172 * @param year corresponding year 173 * @param charity_sig signature from charity over @bkp 174 */ 175 static json_t * 176 issue_receipt_body_to_json ( 177 const unsigned int num_bkp, 178 const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp, 179 const uint64_t year, 180 const struct DONAU_CharitySignatureP *charity_sig) 181 { 182 json_t *budikeypairs = json_array (); 183 184 GNUNET_assert (NULL != budikeypairs); 185 for (size_t i = 0; i < num_bkp; i++) 186 { 187 json_t *budikeypair = GNUNET_JSON_PACK ( 188 GNUNET_JSON_pack_data_auto ("h_donation_unit_pub", 189 &bkp[i].h_donation_unit_pub.hash), 190 DONAU_JSON_pack_blinded_donation_identifier ("blinded_udi", 191 &bkp[i].blinded_udi)); 192 GNUNET_assert (0 == 193 json_array_append_new (budikeypairs, 194 budikeypair)); 195 } 196 return GNUNET_JSON_PACK ( 197 GNUNET_JSON_pack_array_steal ("budikeypairs", 198 budikeypairs), 199 GNUNET_JSON_pack_data_auto ("charity_sig", 200 &charity_sig->eddsa_sig), 201 GNUNET_JSON_pack_uint64 ("year", 202 year)); 203 } 204 205 206 /** 207 * Function called when we're done processing the 208 * HTTP POST /batch-issue/$CHARITY_ID request. 209 * 210 * @param cls the `struct KeysRequest` 211 * @param response_code HTTP response code, 0 on error 212 * @param resp_obj parsed JSON result, NULL on error 213 */ 214 static void 215 handle_batch_issue_finished (void *cls, 216 long response_code, 217 const void *resp_obj) 218 { 219 struct DONAU_BatchIssueReceiptHandle *birh = cls; 220 const json_t *j = resp_obj; 221 struct DONAU_BatchIssueResponse biresp = { 222 .hr.reply = j, 223 .hr.http_status = (unsigned int) response_code 224 }; 225 226 birh->job = NULL; 227 switch (response_code) 228 { 229 case MHD_HTTP_OK: 230 if (GNUNET_OK != 231 handle_batch_issue_ok (j, 232 birh, 233 &biresp)) 234 { 235 biresp.hr.http_status = 0; 236 biresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 237 } 238 break; 239 case MHD_HTTP_NO_CONTENT: 240 biresp.hr.ec = TALER_JSON_get_error_code (j); 241 biresp.hr.hint = TALER_JSON_get_error_hint (j); 242 break; 243 // invalid charity signature 244 case MHD_HTTP_FORBIDDEN: 245 biresp.hr.ec = TALER_JSON_get_error_code (j); 246 biresp.hr.hint = TALER_JSON_get_error_hint (j); 247 break; 248 // one or more donation units are not known to the Donau 249 case MHD_HTTP_NOT_FOUND: 250 biresp.hr.ec = TALER_JSON_get_error_code (j); 251 biresp.hr.hint = TALER_JSON_get_error_hint (j); 252 break; 253 case MHD_HTTP_CONTENT_TOO_LARGE: 254 biresp.hr.ec = TALER_JSON_get_error_code (j); 255 biresp.hr.hint = TALER_JSON_get_error_hint (j); 256 break; 257 // Donation limit is not sufficent 258 case MHD_HTTP_CONFLICT: 259 biresp.hr.ec = TALER_JSON_get_error_code (j); 260 biresp.hr.hint = TALER_JSON_get_error_hint (j); 261 break; 262 // donation unit key is no longer valid 263 case MHD_HTTP_GONE: 264 biresp.hr.ec = TALER_JSON_get_error_code (j); 265 biresp.hr.hint = TALER_JSON_get_error_hint (j); 266 break; 267 default: 268 /* unexpected response code */ 269 GNUNET_break_op (0); 270 biresp.hr.ec = TALER_JSON_get_error_code (j); 271 biresp.hr.hint = TALER_JSON_get_error_hint (j); 272 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 273 "Unexpected response code %u/%d for POST %s\n", 274 (unsigned int) response_code, 275 (int) biresp.hr.ec, 276 birh->url); 277 break; 278 } 279 if (NULL != birh->cb) 280 { 281 birh->cb (birh->cb_cls, 282 &biresp); 283 birh->cb = NULL; 284 } 285 DONAU_charity_issue_receipt_cancel (birh); 286 } 287 288 289 struct DONAU_BatchIssueReceiptHandle * 290 DONAU_charity_issue_receipt ( 291 struct GNUNET_CURL_Context *ctx, 292 const char *url, 293 const struct DONAU_CharityPrivateKeyP *charity_priv, 294 const uint64_t charity_id, 295 const uint64_t year, 296 const size_t num_bkp, 297 const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp, 298 DONAU_BatchIssueReceiptsCallback cb, 299 void *cb_cls) 300 { 301 CURL *eh; 302 json_t *body; 303 char arg_str[sizeof (charity_id) * 2 + 32]; 304 struct DONAU_BatchIssueReceiptHandle *birh; 305 306 birh = GNUNET_new (struct DONAU_BatchIssueReceiptHandle); 307 birh->num_blinded_sigs = num_bkp; 308 DONAU_charity_bkp_sign (num_bkp, bkp, 309 charity_priv, 310 &birh->charity_sig); 311 birh->cb = cb; 312 birh->cb_cls = cb_cls; 313 birh->ctx = ctx; 314 GNUNET_snprintf (arg_str, 315 sizeof (arg_str), 316 "batch-issue/%llu", 317 (unsigned long long) charity_id); 318 birh->url = TALER_url_join (url, 319 arg_str, 320 NULL); 321 if (NULL == birh->url) 322 { 323 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 324 "Could not construct request URL.\n"); 325 GNUNET_free (birh); 326 return NULL; 327 } 328 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 329 "issue_receipts_with_URL `%s'.\n", 330 birh->url); 331 body = issue_receipt_body_to_json (num_bkp, 332 bkp, 333 year, 334 &birh->charity_sig); 335 eh = DONAU_curl_easy_get_ (birh->url); 336 if ( (NULL == eh) || 337 (GNUNET_OK != 338 TALER_curl_easy_post (&birh->post_ctx, 339 eh, 340 body)) ) 341 { 342 GNUNET_break (0); 343 if (NULL != eh) 344 curl_easy_cleanup (eh); 345 json_decref (body); 346 GNUNET_free (birh->url); 347 return NULL; 348 } 349 json_decref (body); 350 birh->job = GNUNET_CURL_job_add2 (ctx, 351 eh, 352 birh->post_ctx.headers, 353 &handle_batch_issue_finished, 354 birh); 355 return birh; 356 } 357 358 359 void 360 DONAU_charity_issue_receipt_cancel ( 361 struct DONAU_BatchIssueReceiptHandle *birh) 362 { 363 if (NULL != birh->job) 364 { 365 GNUNET_CURL_job_cancel (birh->job); 366 birh->job = NULL; 367 } 368 TALER_curl_easy_post_finished (&birh->post_ctx); 369 GNUNET_free (birh->url); 370 GNUNET_free (birh); 371 }