/* This file is part of GNUnet. (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009 Christian Grothoff (and other contributing authors) GNUnet 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. GNUnet 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 GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file fs/fs_search.c * @brief Helper functions for searching. * @author Christian Grothoff */ #include "platform.h" #include "gnunet_constants.h" #include "gnunet_fs_service.h" #include "gnunet_protocols.h" #include "fs.h" #define DEBUG_SEARCH GNUNET_EXTRA_LOGGING /** * Fill in all of the generic fields for a search event and * call the callback. * * @param pi structure to fill in * @param sc overall search context * @return value returned by the callback */ void * GNUNET_FS_search_make_status_ (struct GNUNET_FS_ProgressInfo *pi, struct GNUNET_FS_SearchContext *sc) { void *ret; pi->value.search.sc = sc; pi->value.search.cctx = sc->client_info; pi->value.search.pctx = (sc->psearch_result == NULL) ? NULL : sc->psearch_result->client_info; pi->value.search.query = sc->uri; pi->value.search.duration = GNUNET_TIME_absolute_get_duration (sc->start_time); pi->value.search.anonymity = sc->anonymity; ret = sc->h->upcb (sc->h->upcb_cls, pi); return ret; } /** * Check if the given result is identical * to the given URI. * * @param cls points to the URI we check against * @param key not used * @param value a "struct GNUNET_FS_SearchResult" who's URI we * should compare with * @return GNUNET_SYSERR if the result is present, * GNUNET_OK otherwise */ static int test_result_present (void *cls, const GNUNET_HashCode * key, void *value) { const struct GNUNET_FS_Uri *uri = cls; struct GNUNET_FS_SearchResult *sr = value; if (GNUNET_FS_uri_test_equal (uri, sr->uri)) return GNUNET_SYSERR; return GNUNET_OK; } /** * We've found a new CHK result. Let the client * know about it. * * @param sc the search context * @param sr the specific result */ static void notify_client_chk_result (struct GNUNET_FS_SearchContext *sc, struct GNUNET_FS_SearchResult *sr) { struct GNUNET_FS_ProgressInfo pi; pi.status = GNUNET_FS_STATUS_SEARCH_RESULT; pi.value.search.specifics.result.meta = sr->meta; pi.value.search.specifics.result.uri = sr->uri; pi.value.search.specifics.result.result = sr; pi.value.search.specifics.result.applicability_rank = sr->optional_support; sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc); } /** * We've found new information about an existing CHK result. Let the * client know about it. * * @param sc the search context * @param sr the specific result */ static void notify_client_chk_update (struct GNUNET_FS_SearchContext *sc, struct GNUNET_FS_SearchResult *sr) { struct GNUNET_FS_ProgressInfo pi; pi.status = GNUNET_FS_STATUS_SEARCH_UPDATE; pi.value.search.specifics.update.cctx = sr->client_info; pi.value.search.specifics.update.meta = sr->meta; pi.value.search.specifics.update.uri = sr->uri; pi.value.search.specifics.update.availability_rank = 2 * sr->availability_success - sr->availability_trials; pi.value.search.specifics.update.availability_certainty = sr->availability_trials; pi.value.search.specifics.update.applicability_rank = sr->optional_support; sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc); } /** * Context for "get_result_present". */ struct GetResultContext { /** * The URI we're looking for. */ const struct GNUNET_FS_Uri *uri; /** * Where to store a pointer to the search * result struct if we found a match. */ struct GNUNET_FS_SearchResult *sr; }; /** * Check if the given result is identical to the given URI and if so * return it. * * @param cls a "struct GetResultContext" * @param key not used * @param value a "struct GNUNET_FS_SearchResult" who's URI we * should compare with * @return GNUNET_OK */ static int get_result_present (void *cls, const GNUNET_HashCode * key, void *value) { struct GetResultContext *grc = cls; struct GNUNET_FS_SearchResult *sr = value; if (GNUNET_FS_uri_test_equal (grc->uri, sr->uri)) grc->sr = sr; return GNUNET_OK; } /** * Signal result of last probe to client and then schedule next * probe. */ static void signal_probe_result (struct GNUNET_FS_SearchResult *sr) { struct GNUNET_FS_ProgressInfo pi; pi.status = GNUNET_FS_STATUS_SEARCH_START; pi.value.search.specifics.update.cctx = sr->client_info; pi.value.search.specifics.update.meta = sr->meta; pi.value.search.specifics.update.uri = sr->uri; pi.value.search.specifics.update.availability_rank = sr->availability_success; pi.value.search.specifics.update.availability_certainty = sr->availability_trials; pi.value.search.specifics.update.applicability_rank = sr->optional_support; sr->sc->client_info = GNUNET_FS_search_make_status_ (&pi, sr->sc); GNUNET_FS_search_start_probe_ (sr); } /** * Handle the case where we have failed to receive a response for our probe. * * @param cls our 'struct GNUNET_FS_SearchResult*' * @param tc scheduler context */ static void probe_failure_handler (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct GNUNET_FS_SearchResult *sr = cls; sr->availability_trials++; GNUNET_FS_search_result_sync_ (sr); signal_probe_result (sr); } /** * Handle the case where we have gotten a response for our probe. * * @param cls our 'struct GNUNET_FS_SearchResult*' * @param tc scheduler context */ static void probe_success_handler (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct GNUNET_FS_SearchResult *sr = cls; sr->availability_trials++; sr->availability_success++; GNUNET_FS_search_result_sync_ (sr); signal_probe_result (sr); } /** * Notification of FS that a search probe has made progress. * This function is used INSTEAD of the client's event handler * for downloads where the GNUNET_FS_DOWNLOAD_IS_PROBE flag is set. * * @param cls closure, always NULL (!), actual closure * is in the client-context of the info struct * @param info details about the event, specifying the event type * and various bits about the event * @return client-context (for the next progress call * for this operation; should be set to NULL for * SUSPEND and STOPPED events). The value returned * will be passed to future callbacks in the respective * field in the GNUNET_FS_ProgressInfo struct. */ void * GNUNET_FS_search_probe_progress_ (void *cls, const struct GNUNET_FS_ProgressInfo *info) { struct GNUNET_FS_SearchResult *sr = info->value.download.cctx; struct GNUNET_TIME_Relative dur; switch (info->status) { case GNUNET_FS_STATUS_DOWNLOAD_START: /* ignore */ break; case GNUNET_FS_STATUS_DOWNLOAD_RESUME: /* probes should never be resumed */ GNUNET_assert (0); break; case GNUNET_FS_STATUS_DOWNLOAD_SUSPEND: /* probes should never be suspended */ GNUNET_break (0); break; case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS: /* ignore */ break; case GNUNET_FS_STATUS_DOWNLOAD_ERROR: if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) { GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK; } sr->probe_cancel_task = GNUNET_SCHEDULER_add_delayed (sr->remaining_probe_time, &probe_failure_handler, sr); break; case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED: if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) { GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK; } sr->probe_cancel_task = GNUNET_SCHEDULER_add_delayed (sr->remaining_probe_time, &probe_success_handler, sr); break; case GNUNET_FS_STATUS_DOWNLOAD_STOPPED: if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) { GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK; } sr = NULL; break; case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE: GNUNET_assert (sr->probe_cancel_task == GNUNET_SCHEDULER_NO_TASK); sr->probe_active_time = GNUNET_TIME_absolute_get (); sr->probe_cancel_task = GNUNET_SCHEDULER_add_delayed (sr->remaining_probe_time, &probe_failure_handler, sr); break; case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE: if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) { GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK; } dur = GNUNET_TIME_absolute_get_duration (sr->probe_active_time); sr->remaining_probe_time = GNUNET_TIME_relative_subtract (sr->remaining_probe_time, dur); GNUNET_FS_search_result_sync_ (sr); break; default: GNUNET_break (0); return NULL; } return sr; } /** * Start download probes for the given search result. * * @param sr the search result */ void GNUNET_FS_search_start_probe_ (struct GNUNET_FS_SearchResult *sr) { uint64_t off; uint64_t len; if (sr->probe_ctx != NULL) return; if (sr->download != NULL) return; if (0 == (sr->sc->h->flags & GNUNET_FS_FLAGS_DO_PROBES)) return; if (sr->availability_trials > AVAILABILITY_TRIALS_MAX) return; len = GNUNET_FS_uri_chk_get_file_size (sr->uri); if (len == 0) return; if ((len <= DBLOCK_SIZE) && (sr->availability_success > 0)) return; off = len / DBLOCK_SIZE; if (off > 0) off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, off); off *= DBLOCK_SIZE; if (len - off < DBLOCK_SIZE) len = len - off; else len = DBLOCK_SIZE; sr->remaining_probe_time = GNUNET_TIME_relative_multiply (sr->sc->h->avg_block_latency, 2 * (1 + sr->availability_trials)); sr->probe_ctx = GNUNET_FS_download_start (sr->sc->h, sr->uri, sr->meta, NULL, NULL, off, len, sr->sc->anonymity, GNUNET_FS_DOWNLOAD_NO_TEMPORARIES | GNUNET_FS_DOWNLOAD_IS_PROBE, sr, NULL); } /** * We have received a KSK result. Check how it fits in with the * overall query and notify the client accordingly. * * @param sc context for the overall query * @param ent entry for the specific keyword * @param uri the URI that was found * @param meta metadata associated with the URI * under the "ent" keyword */ static void process_ksk_result (struct GNUNET_FS_SearchContext *sc, struct SearchRequestEntry *ent, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta) { GNUNET_HashCode key; struct GNUNET_FS_SearchResult *sr; struct GetResultContext grc; int is_new; /* check if new */ GNUNET_FS_uri_to_key (uri, &key); if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_get_multiple (ent->results, &key, &test_result_present, (void *) uri)) return; /* duplicate result */ /* try to find search result in master map */ grc.sr = NULL; grc.uri = uri; GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map, &key, &get_result_present, &grc); sr = grc.sr; is_new = (NULL == sr) || (sr->mandatory_missing > 0); if (NULL == sr) { sr = GNUNET_malloc (sizeof (struct GNUNET_FS_SearchResult)); sr->sc = sc; sr->uri = GNUNET_FS_uri_dup (uri); sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta); sr->mandatory_missing = sc->mandatory_count; sr->key = key; GNUNET_CONTAINER_multihashmap_put (sc->master_result_map, &key, sr, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); } else { GNUNET_CONTAINER_meta_data_merge (sr->meta, meta); } /* check if mandatory satisfied */ if (ent->mandatory) sr->mandatory_missing--; else sr->optional_support++; if (0 != sr->mandatory_missing) return; if (is_new) notify_client_chk_result (sc, sr); else notify_client_chk_update (sc, sr); GNUNET_FS_search_result_sync_ (sr); GNUNET_FS_search_start_probe_ (sr); } /** * Start search for content, internal API. * * @param h handle to the file sharing subsystem * @param uri specifies the search parameters; can be * a KSK URI or an SKS URI. * @param anonymity desired level of anonymity * @param options options for the search * @param cctx client context * @param psearch parent search result (for namespace update searches) * @return context that can be used to control the search */ static struct GNUNET_FS_SearchContext * search_start (struct GNUNET_FS_Handle *h, const struct GNUNET_FS_Uri *uri, uint32_t anonymity, enum GNUNET_FS_SearchOptions options, void *cctx, struct GNUNET_FS_SearchResult *psearch); /** * We have received an SKS result. Start searching for updates and * notify the client if it is a new result. * * @param sc context for the overall query * @param id_update identifier for updates, NULL for none * @param uri the URI that was found * @param meta metadata associated with the URI */ static void process_sks_result (struct GNUNET_FS_SearchContext *sc, const char *id_update, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta) { struct GNUNET_FS_Uri uu; GNUNET_HashCode key; struct GNUNET_FS_SearchResult *sr; /* check if new */ GNUNET_FS_uri_to_key (uri, &key); GNUNET_CRYPTO_hash_xor (&uri->data.chk.chk.key, &uri->data.chk.chk.query, &key); if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map, &key, &test_result_present, (void *) uri)) return; /* duplicate result */ sr = GNUNET_malloc (sizeof (struct GNUNET_FS_SearchResult)); sr->sc = sc; sr->uri = GNUNET_FS_uri_dup (uri); sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta); sr->key = key; GNUNET_CONTAINER_multihashmap_put (sc->master_result_map, &key, sr, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); GNUNET_FS_search_result_sync_ (sr); GNUNET_FS_search_start_probe_ (sr); /* notify client */ notify_client_chk_result (sc, sr); /* search for updates */ if (strlen (id_update) == 0) return; /* no updates */ uu.type = sks; uu.data.sks.namespace = sc->uri->data.sks.namespace; uu.data.sks.identifier = GNUNET_strdup (id_update); (void) search_start (sc->h, &uu, sc->anonymity, sc->options, NULL, sr); GNUNET_free (uu.data.sks.identifier); } /** * Process a keyword-search result. * * @param sc our search context * @param kb the kblock * @param size size of kb */ static void process_kblock (struct GNUNET_FS_SearchContext *sc, const struct KBlock *kb, size_t size) { unsigned int i; size_t j; GNUNET_HashCode q; char pt[size - sizeof (struct KBlock)]; struct GNUNET_CRYPTO_AesSessionKey skey; struct GNUNET_CRYPTO_AesInitializationVector iv; const char *eos; struct GNUNET_CONTAINER_MetaData *meta; struct GNUNET_FS_Uri *uri; char *emsg; GNUNET_CRYPTO_hash (&kb->keyspace, sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), &q); /* find key */ for (i = 0; i < sc->uri->data.ksk.keywordCount; i++) if (0 == memcmp (&q, &sc->requests[i].query, sizeof (GNUNET_HashCode))) break; if (i == sc->uri->data.ksk.keywordCount) { /* oops, does not match any of our keywords!? */ GNUNET_break (0); return; } /* decrypt */ GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv); if (-1 == GNUNET_CRYPTO_aes_decrypt (&kb[1], size - sizeof (struct KBlock), &skey, &iv, pt)) { GNUNET_break (0); return; } /* parse */ eos = memchr (pt, 0, sizeof (pt)); if (NULL == eos) { GNUNET_break_op (0); return; } j = eos - pt + 1; if (sizeof (pt) == j) meta = GNUNET_CONTAINER_meta_data_create (); else meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j], sizeof (pt) - j); if (meta == NULL) { GNUNET_break_op (0); /* kblock malformed */ return; } uri = GNUNET_FS_uri_parse (pt, &emsg); if (uri == NULL) { GNUNET_break_op (0); /* kblock malformed */ GNUNET_free_non_null (emsg); GNUNET_CONTAINER_meta_data_destroy (meta); return; } /* process */ process_ksk_result (sc, &sc->requests[i], uri, meta); /* clean up */ GNUNET_CONTAINER_meta_data_destroy (meta); GNUNET_FS_uri_destroy (uri); } /** * Process a keyword-search result with a namespace advertisment. * * @param sc our search context * @param nb the nblock * @param size size of nb */ static void process_nblock (struct GNUNET_FS_SearchContext *sc, const struct NBlock *nb, size_t size) { unsigned int i; size_t j; GNUNET_HashCode q; char pt[size - sizeof (struct NBlock)]; struct GNUNET_CRYPTO_AesSessionKey skey; struct GNUNET_CRYPTO_AesInitializationVector iv; const char *eos; struct GNUNET_CONTAINER_MetaData *meta; struct GNUNET_FS_Uri *uri; char *uris; GNUNET_CRYPTO_hash (&nb->keyspace, sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), &q); /* find key */ for (i = 0; i < sc->uri->data.ksk.keywordCount; i++) if (0 == memcmp (&q, &sc->requests[i].query, sizeof (GNUNET_HashCode))) break; if (i == sc->uri->data.ksk.keywordCount) { /* oops, does not match any of our keywords!? */ GNUNET_break (0); return; } /* decrypt */ GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv); if (-1 == GNUNET_CRYPTO_aes_decrypt (&nb[1], size - sizeof (struct NBlock), &skey, &iv, pt)) { GNUNET_break (0); return; } /* parse */ eos = memchr (pt, 0, sizeof (pt)); if (NULL == eos) { GNUNET_break_op (0); return; } j = eos - pt + 1; if (sizeof (pt) == j) meta = GNUNET_CONTAINER_meta_data_create (); else meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j], sizeof (pt) - j); if (meta == NULL) { GNUNET_break_op (0); /* nblock malformed */ return; } uri = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri)); uri->type = sks; uri->data.sks.identifier = GNUNET_strdup (pt); GNUNET_CRYPTO_hash (&nb->subspace, sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), &uri->data.sks.namespace); uris = GNUNET_FS_uri_to_string (uri); GNUNET_CONTAINER_meta_data_insert (meta, "", EXTRACTOR_METATYPE_URI, EXTRACTOR_METAFORMAT_UTF8, "text/plain", uris, strlen (uris) + 1); GNUNET_free (uris); GNUNET_PSEUDONYM_add (sc->h->cfg, &uri->data.sks.namespace, meta); /* process */ process_ksk_result (sc, &sc->requests[i], uri, meta); /* clean up */ GNUNET_CONTAINER_meta_data_destroy (meta); GNUNET_FS_uri_destroy (uri); } /** * Process a namespace-search result. * * @param sc our search context * @param sb the sblock * @param size size of sb */ static void process_sblock (struct GNUNET_FS_SearchContext *sc, const struct SBlock *sb, size_t size) { size_t len = size - sizeof (struct SBlock); char pt[len]; struct GNUNET_CRYPTO_AesSessionKey skey; struct GNUNET_CRYPTO_AesInitializationVector iv; struct GNUNET_FS_Uri *uri; struct GNUNET_CONTAINER_MetaData *meta; const char *id; const char *uris; size_t off; char *emsg; GNUNET_HashCode key; char *identifier; /* decrypt */ identifier = sc->uri->data.sks.identifier; GNUNET_CRYPTO_hash (identifier, strlen (identifier), &key); GNUNET_CRYPTO_hash_to_aes_key (&key, &skey, &iv); if (-1 == GNUNET_CRYPTO_aes_decrypt (&sb[1], len, &skey, &iv, pt)) { GNUNET_break (0); return; } /* parse */ off = GNUNET_STRINGS_buffer_tokenize (pt, len, 2, &id, &uris); if (off == 0) { GNUNET_break_op (0); /* sblock malformed */ return; } meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[off], len - off); if (meta == NULL) { GNUNET_break_op (0); /* sblock malformed */ return; } uri = GNUNET_FS_uri_parse (uris, &emsg); if (uri == NULL) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse URI `%s': %s\n", uris, emsg); GNUNET_break_op (0); /* sblock malformed */ GNUNET_free_non_null (emsg); GNUNET_CONTAINER_meta_data_destroy (meta); return; } /* process */ process_sks_result (sc, id, uri, meta); /* clean up */ GNUNET_FS_uri_destroy (uri); GNUNET_CONTAINER_meta_data_destroy (meta); } /** * Process a search result. * * @param sc our search context * @param type type of the result * @param expiration when it will expire * @param data the (encrypted) response * @param size size of data */ static void process_result (struct GNUNET_FS_SearchContext *sc, enum GNUNET_BLOCK_Type type, struct GNUNET_TIME_Absolute expiration, const void *data, size_t size) { if (GNUNET_TIME_absolute_get_duration (expiration).rel_value > 0) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result received has already expired.\n"); return; /* result expired */ } switch (type) { case GNUNET_BLOCK_TYPE_FS_KBLOCK: if (!GNUNET_FS_uri_test_ksk (sc->uri)) { GNUNET_break (0); return; } if (sizeof (struct KBlock) > size) { GNUNET_break_op (0); return; } process_kblock (sc, data, size); break; case GNUNET_BLOCK_TYPE_FS_SBLOCK: if (!GNUNET_FS_uri_test_sks (sc->uri)) { GNUNET_break (0); return; } if (sizeof (struct SBlock) > size) { GNUNET_break_op (0); return; } process_sblock (sc, data, size); break; case GNUNET_BLOCK_TYPE_FS_NBLOCK: if (!GNUNET_FS_uri_test_ksk (sc->uri)) { GNUNET_break (0); return; } if (sizeof (struct NBlock) > size) { GNUNET_break_op (0); return; } process_nblock (sc, data, size); break; case GNUNET_BLOCK_TYPE_ANY: GNUNET_break (0); break; case GNUNET_BLOCK_TYPE_FS_DBLOCK: GNUNET_break (0); break; case GNUNET_BLOCK_TYPE_FS_ONDEMAND: GNUNET_break (0); break; case GNUNET_BLOCK_TYPE_FS_IBLOCK: GNUNET_break (0); break; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Got result with unknown block type `%d', ignoring"), type); break; } } /** * Shutdown any existing connection to the FS * service and try to establish a fresh one * (and then re-transmit our search request). * * @param sc the search to reconnec */ static void try_reconnect (struct GNUNET_FS_SearchContext *sc); /** * Type of a function to call when we receive a message * from the service. * * @param cls closure * @param msg message received, NULL on timeout or fatal error */ static void receive_results (void *cls, const struct GNUNET_MessageHeader *msg) { struct GNUNET_FS_SearchContext *sc = cls; const struct PutMessage *cm; uint16_t msize; if ((NULL == msg) || (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) || (ntohs (msg->size) <= sizeof (struct PutMessage))) { try_reconnect (sc); return; } msize = ntohs (msg->size); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Receiving %u bytes of result from fs service\n", msize); cm = (const struct PutMessage *) msg; process_result (sc, ntohl (cm->type), GNUNET_TIME_absolute_ntoh (cm->expiration), &cm[1], msize - sizeof (struct PutMessage)); /* continue receiving */ GNUNET_CLIENT_receive (sc->client, &receive_results, sc, GNUNET_TIME_UNIT_FOREVER_REL); } /** * Schedule the transmission of the (next) search request * to the service. * * @param sc context for the search */ static void schedule_transmit_search_request (struct GNUNET_FS_SearchContext *sc); /** * Closure for 'build_result_set'. */ struct MessageBuilderContext { /** * How many entries can we store to xoff. */ unsigned int put_cnt; /** * How many entries should we skip. */ unsigned int skip_cnt; /** * Where to store the keys. */ GNUNET_HashCode *xoff; /** * Search context we are iterating for. */ struct GNUNET_FS_SearchContext *sc; /** * URI the search result must match, NULL for any */ struct GNUNET_FS_Uri *uri; }; /** * Iterating over the known results, pick those * matching the given result range and store * their keys at 'xoff'. * * @param cls the 'struct MessageBuilderContext' * @param key key for a result * @param value the search result * @return GNUNET_OK to continue iterating */ static int build_result_set (void *cls, const GNUNET_HashCode * key, void *value) { struct MessageBuilderContext *mbc = cls; struct GNUNET_FS_SearchResult *sr = value; if ((mbc->uri != NULL) && (GNUNET_YES != GNUNET_FS_uri_test_equal (mbc->uri, sr->uri))) return GNUNET_OK; if (mbc->skip_cnt > 0) { mbc->skip_cnt--; return GNUNET_OK; } if (mbc->put_cnt == 0) return GNUNET_SYSERR; mbc->sc->search_request_map_offset++; mbc->xoff[--mbc->put_cnt] = *key; return GNUNET_OK; } /** * Iterating over the known results, count those * matching the given result range and increment * put count for each. * * @param cls the 'struct MessageBuilderContext' * @param key key for a result * @param value the search result * @return GNUNET_OK to continue iterating */ static int find_result_set (void *cls, const GNUNET_HashCode * key, void *value) { struct MessageBuilderContext *mbc = cls; struct GNUNET_FS_SearchResult *sr = value; if ((mbc->uri != NULL) && (GNUNET_YES != GNUNET_FS_uri_test_equal (mbc->uri, sr->uri))) return GNUNET_OK; mbc->put_cnt++; return GNUNET_OK; } /** * We're ready to transmit the search request to the * file-sharing service. Do it. * * @param cls closure * @param size number of bytes available in buf * @param buf where the callee should write the message * @return number of bytes written to buf */ static size_t transmit_search_request (void *cls, size_t size, void *buf) { struct GNUNET_FS_SearchContext *sc = cls; struct MessageBuilderContext mbc; size_t msize; struct SearchMessage *sm; const char *identifier; GNUNET_HashCode key; GNUNET_HashCode idh; unsigned int sqms; if (NULL == buf) { try_reconnect (sc); return 0; } mbc.sc = sc; mbc.skip_cnt = sc->search_request_map_offset; sm = buf; sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH); mbc.xoff = (GNUNET_HashCode *) & sm[1]; if (GNUNET_FS_uri_test_ksk (sc->uri)) { msize = sizeof (struct SearchMessage); GNUNET_assert (size >= msize); mbc.uri = NULL; mbc.put_cnt = 0; GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &find_result_set, &mbc); sqms = mbc.put_cnt; mbc.put_cnt = (size - msize) / sizeof (GNUNET_HashCode); mbc.put_cnt = GNUNET_MIN (mbc.put_cnt, sqms - mbc.skip_cnt); if (sc->search_request_map_offset < sqms) GNUNET_assert (mbc.put_cnt > 0); sm->header.size = htons (msize); if (0 != (sc->options & GNUNET_FS_SEARCH_OPTION_LOOPBACK_ONLY)) sm->options = htonl (1); else sm->options = htonl (0); sm->type = htonl (GNUNET_BLOCK_TYPE_ANY); sm->anonymity_level = htonl (sc->anonymity); memset (&sm->target, 0, sizeof (GNUNET_HashCode)); sm->query = sc->requests[sc->keyword_offset].query; msize += sizeof (GNUNET_HashCode) * mbc.put_cnt; GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &build_result_set, &mbc); sm->header.size = htons (msize); if (sqms != sc->search_request_map_offset) { /* more requesting to be done... */ schedule_transmit_search_request (sc); return msize; } sc->keyword_offset++; if (sc->uri->data.ksk.keywordCount != sc->keyword_offset) { /* more requesting to be done... */ schedule_transmit_search_request (sc); return msize; } } else { GNUNET_assert (GNUNET_FS_uri_test_sks (sc->uri)); msize = sizeof (struct SearchMessage); GNUNET_assert (size >= msize); if (0 != (sc->options & GNUNET_FS_SEARCH_OPTION_LOOPBACK_ONLY)) sm->options = htonl (1); else sm->options = htonl (0); sm->type = htonl (GNUNET_BLOCK_TYPE_FS_SBLOCK); sm->anonymity_level = htonl (sc->anonymity); sm->target = sc->uri->data.sks.namespace; identifier = sc->uri->data.sks.identifier; GNUNET_CRYPTO_hash (identifier, strlen (identifier), &key); GNUNET_CRYPTO_hash (&key, sizeof (GNUNET_HashCode), &idh); GNUNET_CRYPTO_hash_xor (&idh, &sm->target, &sm->query); mbc.put_cnt = (size - msize) / sizeof (GNUNET_HashCode); sqms = GNUNET_CONTAINER_multihashmap_size (sc->master_result_map); mbc.put_cnt = GNUNET_MIN (mbc.put_cnt, sqms - mbc.skip_cnt); mbc.uri = NULL; if (sc->search_request_map_offset < sqms) GNUNET_assert (mbc.put_cnt > 0); msize += sizeof (GNUNET_HashCode) * mbc.put_cnt; GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &build_result_set, &mbc); sm->header.size = htons (msize); if (sqms != sc->search_request_map_offset) { /* more requesting to be done... */ schedule_transmit_search_request (sc); return msize; } } GNUNET_CLIENT_receive (sc->client, &receive_results, sc, GNUNET_TIME_UNIT_FOREVER_REL); return msize; } /** * Schedule the transmission of the (next) search request * to the service. * * @param sc context for the search */ static void schedule_transmit_search_request (struct GNUNET_FS_SearchContext *sc) { size_t size; unsigned int sqms; unsigned int fit; size = sizeof (struct SearchMessage); sqms = GNUNET_CONTAINER_multihashmap_size (sc->master_result_map) - sc->search_request_map_offset; fit = (GNUNET_SERVER_MAX_MESSAGE_SIZE - 1 - size) / sizeof (GNUNET_HashCode); fit = GNUNET_MIN (fit, sqms); size += sizeof (GNUNET_HashCode) * fit; GNUNET_CLIENT_notify_transmit_ready (sc->client, size, GNUNET_CONSTANTS_SERVICE_TIMEOUT, GNUNET_NO, &transmit_search_request, sc); } /** * Reconnect to the FS service and transmit * our queries NOW. * * @param cls our search context * @param tc unused */ static void do_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct GNUNET_FS_SearchContext *sc = cls; struct GNUNET_CLIENT_Connection *client; sc->task = GNUNET_SCHEDULER_NO_TASK; client = GNUNET_CLIENT_connect ("fs", sc->h->cfg); if (NULL == client) { try_reconnect (sc); return; } sc->client = client; sc->search_request_map_offset = 0; sc->keyword_offset = 0; schedule_transmit_search_request (sc); } /** * Shutdown any existing connection to the FS * service and try to establish a fresh one * (and then re-transmit our search request). * * @param sc the search to reconnec */ static void try_reconnect (struct GNUNET_FS_SearchContext *sc) { if (NULL != sc->client) { GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO); sc->client = NULL; } sc->task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &do_reconnect, sc); } /** * Start search for content, internal API. * * @param h handle to the file sharing subsystem * @param uri specifies the search parameters; can be * a KSK URI or an SKS URI. * @param anonymity desired level of anonymity * @param options options for the search * @param cctx initial value for the client context * @param psearch parent search result (for namespace update searches) * @return context that can be used to control the search */ static struct GNUNET_FS_SearchContext * search_start (struct GNUNET_FS_Handle *h, const struct GNUNET_FS_Uri *uri, uint32_t anonymity, enum GNUNET_FS_SearchOptions options, void *cctx, struct GNUNET_FS_SearchResult *psearch) { struct GNUNET_FS_SearchContext *sc; struct GNUNET_FS_ProgressInfo pi; sc = GNUNET_malloc (sizeof (struct GNUNET_FS_SearchContext)); sc->h = h; sc->options = options; sc->uri = GNUNET_FS_uri_dup (uri); sc->anonymity = anonymity; sc->start_time = GNUNET_TIME_absolute_get (); if (psearch != NULL) { sc->psearch_result = psearch; psearch->update_search = sc; } sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16); sc->client_info = cctx; if (GNUNET_OK != GNUNET_FS_search_start_searching_ (sc)) { GNUNET_FS_uri_destroy (sc->uri); GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map); GNUNET_free (sc); return NULL; } GNUNET_FS_search_sync_ (sc); pi.status = GNUNET_FS_STATUS_SEARCH_START; sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc); return sc; } /** * Build the request and actually initiate the search using the * GNUnet FS service. * * @param sc search context * @return GNUNET_OK on success, GNUNET_SYSERR on error */ int GNUNET_FS_search_start_searching_ (struct GNUNET_FS_SearchContext *sc) { unsigned int i; const char *keyword; GNUNET_HashCode hc; struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub; struct GNUNET_CRYPTO_RsaPrivateKey *pk; GNUNET_assert (NULL == sc->client); if (GNUNET_FS_uri_test_ksk (sc->uri)) { GNUNET_assert (0 != sc->uri->data.ksk.keywordCount); sc->requests = GNUNET_malloc (sizeof (struct SearchRequestEntry) * sc->uri->data.ksk.keywordCount); for (i = 0; i < sc->uri->data.ksk.keywordCount; i++) { keyword = &sc->uri->data.ksk.keywords[i][1]; GNUNET_CRYPTO_hash (keyword, strlen (keyword), &hc); pk = GNUNET_CRYPTO_rsa_key_create_from_hash (&hc); GNUNET_assert (pk != NULL); GNUNET_CRYPTO_rsa_key_get_public (pk, &pub); GNUNET_CRYPTO_rsa_key_free (pk); GNUNET_CRYPTO_hash (&pub, sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), &sc->requests[i].query); sc->requests[i].mandatory = (sc->uri->data.ksk.keywords[i][0] == '+'); if (sc->requests[i].mandatory) sc->mandatory_count++; sc->requests[i].results = GNUNET_CONTAINER_multihashmap_create (4); GNUNET_CRYPTO_hash (keyword, strlen (keyword), &sc->requests[i].key); } } sc->client = GNUNET_CLIENT_connect ("fs", sc->h->cfg); if (NULL == sc->client) return GNUNET_SYSERR; schedule_transmit_search_request (sc); return GNUNET_OK; } /** * Freeze probes for the given search result. * * @param cls the global FS handle * @param key the key for the search result (unused) * @param value the search result to free * @return GNUNET_OK */ static int search_result_freeze_probes (void *cls, const GNUNET_HashCode * key, void *value) { struct GNUNET_FS_SearchResult *sr = value; if (sr->probe_ctx != NULL) { GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES); sr->probe_ctx = NULL; } if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) { GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK; } if (sr->update_search != NULL) GNUNET_FS_search_pause (sr->update_search); return GNUNET_OK; } /** * Resume probes for the given search result. * * @param cls the global FS handle * @param key the key for the search result (unused) * @param value the search result to free * @return GNUNET_OK */ static int search_result_resume_probes (void *cls, const GNUNET_HashCode * key, void *value) { struct GNUNET_FS_SearchResult *sr = value; GNUNET_FS_search_start_probe_ (sr); if (sr->update_search != NULL) GNUNET_FS_search_continue (sr->update_search); return GNUNET_OK; } /** * Signal suspend and free the given search result. * * @param cls the global FS handle * @param key the key for the search result (unused) * @param value the search result to free * @return GNUNET_OK */ static int search_result_suspend (void *cls, const GNUNET_HashCode * key, void *value) { struct GNUNET_FS_SearchContext *sc = cls; struct GNUNET_FS_SearchResult *sr = value; struct GNUNET_FS_ProgressInfo pi; if (sr->download != NULL) GNUNET_FS_download_signal_suspend_ (sr->download); if (sr->update_search != NULL) GNUNET_FS_search_signal_suspend_ (sr->update_search); pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_SUSPEND; pi.value.search.specifics.result_suspend.cctx = sr->client_info; pi.value.search.specifics.result_suspend.meta = sr->meta; pi.value.search.specifics.result_suspend.uri = sr->uri; sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc); GNUNET_break (NULL == sr->client_info); GNUNET_free_non_null (sr->serialization); GNUNET_FS_uri_destroy (sr->uri); GNUNET_CONTAINER_meta_data_destroy (sr->meta); if (sr->probe_ctx != NULL) GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES); if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); GNUNET_free (sr); return GNUNET_OK; } /** * Create SUSPEND event for the given search operation * and then clean up our state (without stop signal). * * @param cls the 'struct GNUNET_FS_SearchContext' to signal for */ void GNUNET_FS_search_signal_suspend_ (void *cls) { struct GNUNET_FS_SearchContext *sc = cls; struct GNUNET_FS_ProgressInfo pi; unsigned int i; GNUNET_FS_end_top (sc->h, sc->top); GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &search_result_suspend, sc); pi.status = GNUNET_FS_STATUS_SEARCH_SUSPEND; sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc); GNUNET_break (NULL == sc->client_info); if (sc->task != GNUNET_SCHEDULER_NO_TASK) GNUNET_SCHEDULER_cancel (sc->task); if (NULL != sc->client) GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO); GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map); if (sc->requests != NULL) { GNUNET_assert (GNUNET_FS_uri_test_ksk (sc->uri)); for (i = 0; i < sc->uri->data.ksk.keywordCount; i++) GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results); } GNUNET_free_non_null (sc->requests); GNUNET_free_non_null (sc->emsg); GNUNET_FS_uri_destroy (sc->uri); GNUNET_free_non_null (sc->serialization); GNUNET_free (sc); } /** * Start search for content. * * @param h handle to the file sharing subsystem * @param uri specifies the search parameters; can be * a KSK URI or an SKS URI. * @param anonymity desired level of anonymity * @param options options for the search * @param cctx initial value for the client context * @return context that can be used to control the search */ struct GNUNET_FS_SearchContext * GNUNET_FS_search_start (struct GNUNET_FS_Handle *h, const struct GNUNET_FS_Uri *uri, uint32_t anonymity, enum GNUNET_FS_SearchOptions options, void *cctx) { struct GNUNET_FS_SearchContext *ret; ret = search_start (h, uri, anonymity, options, cctx, NULL); if (ret == NULL) return NULL; ret->top = GNUNET_FS_make_top (h, &GNUNET_FS_search_signal_suspend_, ret); return ret; } /** * Pause search. * * @param sc context for the search that should be paused */ void GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc) { struct GNUNET_FS_ProgressInfo pi; if (sc->task != GNUNET_SCHEDULER_NO_TASK) GNUNET_SCHEDULER_cancel (sc->task); sc->task = GNUNET_SCHEDULER_NO_TASK; if (NULL != sc->client) GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO); sc->client = NULL; GNUNET_FS_search_sync_ (sc); GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &search_result_freeze_probes, sc); pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED; sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc); } /** * Continue paused search. * * @param sc context for the search that should be resumed */ void GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc) { struct GNUNET_FS_ProgressInfo pi; GNUNET_assert (sc->client == NULL); GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK); do_reconnect (sc, NULL); GNUNET_FS_search_sync_ (sc); pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED; sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc); GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &search_result_resume_probes, sc); } /** * Free the given search result. * * @param cls the global FS handle * @param key the key for the search result (unused) * @param value the search result to free * @return GNUNET_OK */ static int search_result_free (void *cls, const GNUNET_HashCode * key, void *value) { struct GNUNET_FS_SearchContext *sc = cls; struct GNUNET_FS_SearchResult *sr = value; struct GNUNET_FS_ProgressInfo pi; if (NULL != sr->download) { sr->download->search = NULL; sr->download->top = GNUNET_FS_make_top (sr->download->h, &GNUNET_FS_download_signal_suspend_, sr->download); if (NULL != sr->download->serialization) { GNUNET_FS_remove_sync_file_ (sc->h, GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD, sr->download->serialization); GNUNET_free (sr->download->serialization); sr->download->serialization = NULL; } pi.status = GNUNET_FS_STATUS_DOWNLOAD_LOST_PARENT; GNUNET_FS_download_make_status_ (&pi, sr->download); GNUNET_FS_download_sync_ (sr->download); sr->download = NULL; } if (NULL != sr->update_search) { GNUNET_FS_search_stop (sr->update_search); GNUNET_assert (sr->update_search == NULL); } pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED; pi.value.search.specifics.result_stopped.cctx = sr->client_info; pi.value.search.specifics.result_stopped.meta = sr->meta; pi.value.search.specifics.result_stopped.uri = sr->uri; sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc); GNUNET_break (NULL == sr->client_info); GNUNET_free_non_null (sr->serialization); GNUNET_FS_uri_destroy (sr->uri); GNUNET_CONTAINER_meta_data_destroy (sr->meta); if (sr->probe_ctx != NULL) GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES); if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK) GNUNET_SCHEDULER_cancel (sr->probe_cancel_task); GNUNET_free (sr); return GNUNET_OK; } /** * Stop search for content. * * @param sc context for the search that should be stopped */ void GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc) { struct GNUNET_FS_ProgressInfo pi; unsigned int i; if (sc->top != NULL) GNUNET_FS_end_top (sc->h, sc->top); if (sc->psearch_result != NULL) sc->psearch_result->update_search = NULL; GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map, &search_result_free, sc); if (sc->serialization != NULL) { GNUNET_FS_remove_sync_file_ (sc->h, (sc->psearch_result != NULL) ? GNUNET_FS_SYNC_PATH_CHILD_SEARCH : GNUNET_FS_SYNC_PATH_MASTER_SEARCH, sc->serialization); GNUNET_FS_remove_sync_dir_ (sc->h, (sc->psearch_result != NULL) ? GNUNET_FS_SYNC_PATH_CHILD_SEARCH : GNUNET_FS_SYNC_PATH_MASTER_SEARCH, sc->serialization); GNUNET_free (sc->serialization); } pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED; sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc); GNUNET_break (NULL == sc->client_info); if (sc->task != GNUNET_SCHEDULER_NO_TASK) GNUNET_SCHEDULER_cancel (sc->task); if (NULL != sc->client) GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO); GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map); if (sc->requests != NULL) { GNUNET_assert (GNUNET_FS_uri_test_ksk (sc->uri)); for (i = 0; i < sc->uri->data.ksk.keywordCount; i++) GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results); } GNUNET_free_non_null (sc->requests); GNUNET_free_non_null (sc->emsg); GNUNET_FS_uri_destroy (sc->uri); GNUNET_free (sc); } /* end of fs_search.c */