/* This file is part of Anastasis Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis 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. Anastasis 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 Anastasis; see the file COPYING.GPL. If not, see */ /** * @file reducer/anastasis_api_providers.c * @brief anastasis provider synchronization logic * @author Christian Grothoff */ #include #include #include "anastasis_redux.h" #include "anastasis_error_codes.h" #include "anastasis_api_redux.h" /** * Main data structure for sync_providers(). */ struct MasterSync; /** * Data structure for one provider we are syncing /config with. */ struct SyncEntry { /** * Kept in a DLL. */ struct SyncEntry *next; /** * Kept in a DLL. */ struct SyncEntry *prev; /** * Sync operation we are part of. */ struct MasterSync *ms; /** * Redux action for this provider. */ struct ANASTASIS_ReduxAction *ra; }; /** * Main data structure for sync_providers(). */ struct MasterSync { /** * Our own sync action we expose externally. */ struct ANASTASIS_ReduxAction ra; /** * Head of DLL with entries per provider. */ struct SyncEntry *se_head; /** * Tail of DLL with entries per provider. */ struct SyncEntry *se_tail; /** * Function to call with the result. */ ANASTASIS_ActionCallback cb; /** * Closure for @e cb. */ void *cb_cls; }; /** * Free @a cls data structure. * * @param[in] cls data structure to free, must be a `struct MasterSync *` */ static void clean_sync (void *cls) { struct MasterSync *ms = cls; struct SyncEntry *se; while (NULL != (se = ms->se_head)) { GNUNET_CONTAINER_DLL_remove (ms->se_head, ms->se_tail, se); se->ra->cleanup (se->ra->cleanup_cls); GNUNET_free (se); } GNUNET_free (ms); } /** * Function called when we have made progress on any of the * providers we are trying to sync with. * * @param cls closure * @param error error code, #TALER_EC_NONE if @a new_bs is the new successful state * @param new_state the new state of the operation (client should json_incref() to keep an alias) */ static void sync_progress (void *cls, enum TALER_ErrorCode error, json_t *new_state) { struct SyncEntry *se = cls; struct MasterSync *ms = se->ms; GNUNET_CONTAINER_DLL_remove (ms->se_head, ms->se_tail, se); GNUNET_free (se); ms->cb (ms->cb_cls, error, new_state); clean_sync (ms); } struct ANASTASIS_ReduxAction * ANASTASIS_REDUX_sync_providers_ (json_t *state, const json_t *arguments, ANASTASIS_ActionCallback cb, void *cb_cls) { json_t *rd; json_t *cs_arr; struct MasterSync *ms; rd = json_object_get (state, "recovery_document"); if (NULL == rd) { GNUNET_break (0); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'recovery_document' missing"); return NULL; } cs_arr = json_object_get (rd, "challenges"); if (! json_is_array (cs_arr)) { GNUNET_break_op (0); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'recovery_document' must be an array"); return NULL; } ms = GNUNET_new (struct MasterSync); ms->cb = cb; ms->cb_cls = cb_cls; { json_t *cs; unsigned int n_index; json_array_foreach (cs_arr, n_index, cs) { const char *provider_url; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("url", &provider_url), GNUNET_JSON_spec_end () }; struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; struct SyncEntry *se; if (GNUNET_OK != GNUNET_JSON_parse (cs, spec, NULL, NULL)) { GNUNET_break_op (0); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'recovery_document' missing"); clean_sync (ms); return NULL; } if (GNUNET_OK == ANASTASIS_reducer_lookup_salt (state, provider_url, &provider_salt)) continue; /* provider already ready */ se = GNUNET_new (struct SyncEntry); se->ms = ms; GNUNET_CONTAINER_DLL_insert (ms->se_head, ms->se_tail, se); se->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url, state, &sync_progress, se); } } if (NULL == ms->se_head) { /* everything already synced */ clean_sync (ms); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED, "already in sync"); return NULL; } ms->ra.cleanup = &clean_sync; ms->ra.cleanup_cls = ms; return &ms->ra; } struct ANASTASIS_ReduxAction * ANASTASIS_REDUX_poll_providers_ (json_t *state, const json_t *arguments, ANASTASIS_ActionCallback cb, void *cb_cls) { json_t *ap; const char *url; json_t *obj; struct MasterSync *ms; ap = json_object_get (state, "authentication_providers"); if (NULL == ap) { GNUNET_break (0); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'authentication_providers' missing"); return NULL; } ms = GNUNET_new (struct MasterSync); ms->cb = cb; ms->cb_cls = cb_cls; json_object_foreach (ap, url, obj) { struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; struct SyncEntry *se; struct ANASTASIS_ReduxAction *ra; if (GNUNET_OK == ANASTASIS_reducer_lookup_salt (state, url, &provider_salt)) continue; se = GNUNET_new (struct SyncEntry); se->ms = ms; GNUNET_CONTAINER_DLL_insert (ms->se_head, ms->se_tail, se); ra = ANASTASIS_REDUX_add_provider_to_state_ (url, state, &sync_progress, se); if (NULL == ra) return NULL; /* sync_progress already called! */ se->ra = ra; } if (NULL == ms->se_head) { /* everything already synced */ clean_sync (ms); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, "already in sync"); return NULL; } ms->ra.cleanup = &clean_sync; ms->ra.cleanup_cls = ms; return &ms->ra; }