/*
This file is part of SYNC
Copyright (C) 2014-2019 Taler Systems SA
SYNC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3, or
(at your option) any later version.
SYNC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
License along with SYNC; see the file COPYING. If not, see
*/
/**
* @file lib/testing_api_cmd_backup_upload.c
* @brief command to upload data to the sync backend service.
* @author Christian Grothoff
*/
#include "platform.h"
#include "sync_service.h"
#include "sync_testing_lib.h"
#include
#include
#include
#include "sync_testing_lib.h"
/**
* State for a "backup upload" CMD.
*/
struct BackupUploadState
{
/**
* Eddsa private key.
*/
struct SYNC_AccountPrivateKeyP sync_priv;
/**
* Eddsa public key.
*/
struct SYNC_AccountPublicKeyP sync_pub;
/**
* Hash of the previous upload (maybe bogus if
* #SYNC_TESTING_UO_PREV_HASH_WRONG is set in @e uo).
* Maybe all zeros if there was no previous upload.
*/
struct GNUNET_HashCode prev_hash;
/**
* Hash of the current upload.
*/
struct GNUNET_HashCode curr_hash;
/**
* The /backups POST operation handle.
*/
struct SYNC_UploadOperation *uo;
/**
* URL of the sync backend.
*/
const char *sync_url;
/**
* Previous upload, or NULL for none. Used to calculate what THIS
* upload is based on.
*/
const char *prev_upload;
/**
* Last upload, or NULL for none, usually same as @e prev_upload.
* Used to check the response on #MHD_HTTP_CONFLICT.
*/
const char *last_upload;
/**
* Payment order ID we got back, if any. Otherwise NULL.
*/
char *payment_order_id;
/**
* Claim token we got back, if any. Otherwise all zeros.
*/
struct TALER_ClaimTokenP token;
/**
* Payment order ID we are to provide in the request, may be NULL.
*/
const char *payment_order_req;
/**
* The interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
/**
* The backup data we are uploading.
*/
const void *backup;
/**
* Number of bytes in @e backup.
*/
size_t backup_size;
/**
* Expected status code.
*/
unsigned int http_status;
/**
* Options for how we are supposed to do the upload.
*/
enum SYNC_TESTING_UploadOption uopt;
};
/**
* Function called with the results of a #SYNC_upload().
*
* @param cls closure
* @param ec Taler error code
* @param http_status HTTP status of the request
* @param ud details about the upload operation
*/
static void
backup_upload_cb (void *cls,
enum TALER_ErrorCode ec,
unsigned int http_status,
const struct SYNC_UploadDetails *ud)
{
struct BackupUploadState *bus = cls;
bus->uo = NULL;
if (http_status != bus->http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u to command %s in %s:%u\n",
http_status,
bus->is->commands[bus->is->ip].label,
__FILE__,
__LINE__);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
if (NULL != ud)
{
switch (ud->us)
{
case SYNC_US_SUCCESS:
if (0 != GNUNET_memcmp (&bus->curr_hash,
ud->details.curr_backup_hash))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
break;
case SYNC_US_PAYMENT_REQUIRED:
{
struct TALER_MERCHANT_PayUriData pd;
if (GNUNET_OK !=
TALER_MERCHANT_parse_pay_uri (ud->details.payment_request,
&pd))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
bus->payment_order_id = GNUNET_strdup (pd.order_id);
if (NULL != pd.claim_token)
bus->token = *pd.claim_token;
TALER_MERCHANT_parse_pay_uri_free (&pd);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order ID from Sync service is `%s'\n",
bus->payment_order_id);
memset (&bus->curr_hash,
0,
sizeof (struct GNUNET_HashCode));
}
break;
case SYNC_US_CONFLICTING_BACKUP:
{
const struct TALER_TESTING_Command *ref;
const struct GNUNET_HashCode *h;
ref = TALER_TESTING_interpreter_lookup_command
(bus->is,
bus->last_upload);
GNUNET_assert (NULL != ref);
GNUNET_assert (GNUNET_OK ==
SYNC_TESTING_get_trait_hash (ref,
SYNC_TESTING_TRAIT_HASH_CURRENT,
&h));
if (0 != GNUNET_memcmp (h,
&ud->details.recovered_backup.
existing_backup_hash))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
}
case SYNC_US_HTTP_ERROR:
break;
case SYNC_US_CLIENT_ERROR:
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
case SYNC_US_SERVER_ERROR:
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
}
TALER_TESTING_interpreter_next (bus->is);
}
/**
* Run a "backup upload" CMD.
*
* @param cls closure.
* @param cmd command currently being run.
* @param is interpreter state.
*/
static void
backup_upload_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct BackupUploadState *bus = cls;
bus->is = is;
if (NULL != bus->prev_upload)
{
const struct TALER_TESTING_Command *ref;
ref = TALER_TESTING_interpreter_lookup_command (
is,
bus->prev_upload);
if (NULL == ref)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
{
const struct GNUNET_HashCode *h;
if (GNUNET_OK ==
SYNC_TESTING_get_trait_hash (ref,
SYNC_TESTING_TRAIT_HASH_CURRENT,
&h))
{
bus->prev_hash = *h;
}
}
{
const struct SYNC_AccountPrivateKeyP *priv;
if (GNUNET_OK !=
SYNC_TESTING_get_trait_account_priv (ref,
0,
&priv))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
bus->sync_priv = *priv;
}
{
const struct SYNC_AccountPublicKeyP *pub;
if (GNUNET_OK !=
SYNC_TESTING_get_trait_account_pub (ref,
0,
&pub))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
bus->sync_pub = *pub;
}
if (0 != (SYNC_TESTING_UO_REFERENCE_ORDER_ID & bus->uopt))
{
const char *order_id;
if (GNUNET_OK !=
TALER_TESTING_get_trait_order_id (ref,
0,
&order_id))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
bus->payment_order_req = order_id;
if (NULL == bus->payment_order_req)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
}
}
else
{
GNUNET_CRYPTO_eddsa_key_create (&bus->sync_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&bus->sync_priv.eddsa_priv,
&bus->sync_pub.eddsa_pub);
}
if (0 != (SYNC_TESTING_UO_PREV_HASH_WRONG & bus->uopt))
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&bus->prev_hash,
sizeof (struct GNUNET_HashCode));
GNUNET_CRYPTO_hash (bus->backup,
bus->backup_size,
&bus->curr_hash);
bus->uo = SYNC_upload (is->ctx,
bus->sync_url,
&bus->sync_priv,
( ( (NULL != bus->prev_upload) &&
(GNUNET_NO == GNUNET_is_zero (
&bus->prev_hash)) ) ||
(0 != (SYNC_TESTING_UO_PREV_HASH_WRONG
& bus->uopt)) )
? &bus->prev_hash
: NULL,
bus->backup_size,
bus->backup,
(0 != (SYNC_TESTING_UO_REQUEST_PAYMENT & bus->uopt))
? SYNC_PO_FORCE_PAYMENT
: SYNC_PO_NONE,
bus->payment_order_req,
&backup_upload_cb,
bus);
if (NULL == bus->uo)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (bus->is);
return;
}
}
/**
* Free the state of a "backup upload" CMD, and possibly
* cancel it if it did not complete.
*
* @param cls closure.
* @param cmd command being freed.
*/
static void
backup_upload_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct BackupUploadState *bus = cls;
if (NULL != bus->uo)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command '%s' did not complete (backup upload)\n",
cmd->label);
SYNC_upload_cancel (bus->uo);
bus->uo = NULL;
}
GNUNET_free (bus->payment_order_id);
GNUNET_free (bus);
}
/**
* Offer internal data to other commands.
*
* @param cls closure
* @param ret[out] result (could be anything)
* @param trait name of the trait
* @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
static int
backup_upload_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct BackupUploadState *bus = cls;
struct TALER_TESTING_Trait straits[] = {
SYNC_TESTING_make_trait_hash (SYNC_TESTING_TRAIT_HASH_CURRENT,
&bus->curr_hash),
SYNC_TESTING_make_trait_hash (SYNC_TESTING_TRAIT_HASH_PREVIOUS,
&bus->prev_hash),
TALER_TESTING_make_trait_claim_token (0,
&bus->token),
SYNC_TESTING_make_trait_account_pub (0,
&bus->sync_pub),
SYNC_TESTING_make_trait_account_priv (0,
&bus->sync_priv),
TALER_TESTING_make_trait_order_id (0,
bus->payment_order_id),
TALER_TESTING_trait_end ()
};
struct TALER_TESTING_Trait ftraits[] = {
TALER_TESTING_make_trait_claim_token (0,
&bus->token),
SYNC_TESTING_make_trait_account_pub (0,
&bus->sync_pub),
SYNC_TESTING_make_trait_account_priv (0,
&bus->sync_priv),
TALER_TESTING_make_trait_order_id (0,
bus->payment_order_id),
TALER_TESTING_trait_end ()
};
return TALER_TESTING_get_trait ((NULL != bus->payment_order_req)
? ftraits
: straits,
ret,
trait,
index);
}
/**
* Make the "backup upload" command.
*
* @param label command label
* @param sync_url base URL of the sync serving
* the policy store request.
* @param prev_upload reference to a previous upload we are
* supposed to update, NULL for none
* @param last_upload reference to the last upload for the
* same account, used to check result on MHD_HTTP_CONFLICT
* @param uo upload options
* @param http_status expected HTTP status.
* @param backup_data data to upload
* @param backup_data_size number of bytes in @a backup_data
* @return the command
*/
struct TALER_TESTING_Command
SYNC_TESTING_cmd_backup_upload (const char *label,
const char *sync_url,
const char *prev_upload,
const char *last_upload,
enum SYNC_TESTING_UploadOption uo,
unsigned int http_status,
const void *backup_data,
size_t backup_data_size)
{
struct BackupUploadState *bus;
bus = GNUNET_new (struct BackupUploadState);
bus->http_status = http_status;
bus->prev_upload = prev_upload;
bus->last_upload = last_upload;
bus->uopt = uo;
bus->sync_url = sync_url;
bus->backup = backup_data;
bus->backup_size = backup_data_size;
{
struct TALER_TESTING_Command cmd = {
.cls = bus,
.label = label,
.run = &backup_upload_run,
.cleanup = &backup_upload_cleanup,
.traits = &backup_upload_traits
};
return cmd;
}
}