WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
-
+
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @author Christian Grothoff
*
* TODO:
+ * - "get_nick_record" is a bottleneck, introduce a cache to
+ * avoid looking it up again and again (for the same few
+ * zones that the user will typically manage!)
* - run testcases, make sure everything works!
*/
#include "platform.h"
*/
#define MONITOR_STALL_WARN_DELAY GNUNET_TIME_UNIT_MINUTES
+/**
+ * Size of the cache used by #get_nick_record()
+ */
+#define NC_SIZE 16
/**
* A namestore client
*/
uint32_t offset;
+ /**
+ * Number of pending cache operations triggered by this zone iteration which we
+ * need to wait for before allowing the client to continue.
+ */
+ unsigned int cache_ops;
+
+ /**
+ * Set to #GNUNET_YES if the last iteration exhausted the limit set by the
+ * client and we should send the #GNUNET_MESSAGE_TYPE_NAMESTORE_RECORD_RESULT_END
+ * message and free the data structure once @e cache_ops is zero.
+ */
+ int send_end;
+
};
struct GNUNET_NAMECACHE_QueueEntry *qe;
/**
- * Client to notify about the result.
+ * Client to notify about the result, can be NULL.
*/
struct NamestoreClient *nc;
+ /**
+ * Zone iteration to call #zone_iteration_done_client_continue()
+ * for if applicable, can be NULL.
+ */
+ struct ZoneIteration *zi;
+
/**
* Client's request ID.
*/
};
+/**
+ * Entry in list of cached nick resolutions.
+ */
+struct NickCache
+{
+ /**
+ * Zone the cache entry is for.
+ */
+ struct GNUNET_CRYPTO_EcdsaPrivateKey zone;
+
+ /**
+ * Cached record data.
+ */
+ struct GNUNET_GNSRECORD_Data *rd;
+
+ /**
+ * Timestamp when this cache entry was used last.
+ */
+ struct GNUNET_TIME_Absolute last_used;
+};
+
+
+/**
+ * We cache nick records to reduce DB load.
+ */
+static struct NickCache nick_cache[NC_SIZE];
+
/**
* Public key of all zeros.
*/
* record, which (if found) is then copied to @a cls for future use.
*
* @param cls a `struct GNUNET_GNSRECORD_Data **` for storing the nick (if found)
- * @param seq sequence number of the record
+ * @param seq sequence number of the record, MUST NOT BE ZERO
* @param private_key the private key of the zone (unused)
* @param label should be #GNUNET_GNS_EMPTY_LABEL_AT
* @param rd_count number of records in @a rd
struct GNUNET_GNSRECORD_Data **res = cls;
(void) private_key;
- (void) seq;
+ GNUNET_assert (0 != seq);
if (0 != strcmp (label, GNUNET_GNS_EMPTY_LABEL_AT))
{
GNUNET_break (0);
}
+/**
+ * Add entry to the cache for @a zone and @a nick
+ *
+ * @param zone zone key to cache under
+ * @param nick nick entry to cache
+ */
+static void
+cache_nick (const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
+ const struct GNUNET_GNSRECORD_Data *nick)
+{
+ struct NickCache *oldest;
+
+ oldest = NULL;
+ for (unsigned int i=0;i<NC_SIZE;i++)
+ {
+ struct NickCache *pos = &nick_cache[i];
+
+ if ( (NULL == oldest) ||
+ (oldest->last_used.abs_value_us >
+ pos->last_used.abs_value_us) )
+ oldest = pos;
+ if (0 == memcmp (zone,
+ &pos->zone,
+ sizeof (*zone)))
+ {
+ oldest = pos;
+ break;
+ }
+ }
+ GNUNET_free_non_null (oldest->rd);
+ oldest->zone = *zone;
+ oldest->rd = GNUNET_malloc (sizeof (*nick) +
+ nick->data_size);
+ *oldest->rd = *nick;
+ oldest->rd->data = &oldest->rd[1];
+ memcpy (&oldest->rd[1],
+ nick->data,
+ nick->data_size);
+ oldest->last_used = GNUNET_TIME_absolute_get ();
+}
+
+
/**
* Return the NICK record for the zone (if it exists).
*
struct GNUNET_GNSRECORD_Data *nick;
int res;
+ /* check cache first */
+ for (unsigned int i=0;i<NC_SIZE;i++)
+ {
+ struct NickCache *pos = &nick_cache[i];
+ if ( (NULL != pos->rd) &&
+ (0 == memcmp (zone,
+ &pos->zone,
+ sizeof (*zone))) )
+ {
+ nick = GNUNET_malloc (sizeof (*nick) +
+ pos->rd->data_size);
+ *nick = *pos->rd;
+ nick->data = &nick[1];
+ memcpy (&nick[1],
+ pos->rd->data,
+ pos->rd->data_size);
+ pos->last_used = GNUNET_TIME_absolute_get ();
+ return nick;
+ }
+ }
+
nick = NULL;
res = GSN_database->lookup_records (GSN_database->cls,
zone,
GNUNET_GNSRECORD_z2s (&pub));
return NULL;
}
+
+ /* update cache */
+ cache_nick (zone,
+ nick);
return nick;
}
char *rd_ser;
nick = get_nick_record (zone_key);
-
GNUNET_assert (-1 !=
GNUNET_GNSRECORD_records_get_size (rd_count,
rd));
GNUNET_SERVICE_client_drop (nc->client);
return;
}
- if (rd_ser_len >= UINT16_MAX - name_len - sizeof (*zir_msg))
+ if (((size_t) rd_ser_len) >= UINT16_MAX - name_len - sizeof (*zir_msg))
{
GNUNET_break (0);
GNUNET_SERVICE_client_drop (nc->client);
}
+/**
+ * Function called once we are done with the zone iteration and
+ * allow the zone iteration client to send us more messages.
+ *
+ * @param zi zone iteration we are processing
+ */
+static void
+zone_iteration_done_client_continue (struct ZoneIteration *zi)
+{
+ struct GNUNET_MQ_Envelope *env;
+ struct GNUNET_NAMESTORE_Header *em;
+
+ GNUNET_SERVICE_client_continue (zi->nc->client);
+ if (! zi->send_end)
+ return;
+ /* send empty response to indicate end of list */
+ env = GNUNET_MQ_msg (em,
+ GNUNET_MESSAGE_TYPE_NAMESTORE_RECORD_RESULT_END);
+ em->r_id = htonl (zi->request_id);
+ GNUNET_MQ_send (zi->nc->mq,
+ env);
+
+ GNUNET_CONTAINER_DLL_remove (zi->nc->op_head,
+ zi->nc->op_tail,
+ zi);
+ GNUNET_free (zi);
+}
+
+
/**
* Cache operation complete, clean up.
*
const char *emsg)
{
struct CacheOperation *cop = cls;
+ struct ZoneIteration *zi;
if (NULL != emsg)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
send_store_response (cop->nc,
success,
cop->rid);
+ if (NULL != (zi = cop->zi))
+ {
+ zi->cache_ops--;
+ if (0 == zi->cache_ops)
+ {
+ /* unchoke zone iteration, cache has caught up */
+ zone_iteration_done_client_continue (zi);
+ }
+ }
GNUNET_free (cop);
}
* refresh the corresponding (encrypted) block in the namecache.
*
* @param nc client responsible for the request, can be NULL
+ * @param zi zone iteration response for the request, can be NULL
* @param rid request ID of the client
* @param zone_key private key of the zone
* @param name label for the records
*/
static void
refresh_block (struct NamestoreClient *nc,
+ struct ZoneIteration *zi,
uint32_t rid,
const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
const char *name,
GNUNET_NO);
cop = GNUNET_new (struct CacheOperation);
cop->nc = nc;
+ cop->zi = zi;
+ if (NULL != zi)
+ zi->cache_ops++;
cop->rid = rid;
GNUNET_CONTAINER_DLL_insert (cop_head,
cop_tail,
sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))) )
{
sa->zm_pos = zm->next; /* not interesting to this monitor */
- continue; // -- fails tests, but why not here?
+ continue;
}
if (zm->limit == zm->iteration_cnt)
{
}
/* great, done with the monitors, unpack (again) for refresh_block operation */
refresh_block (sa->nc,
+ NULL,
rid,
&rp_msg->private_key,
sa->conv_name,
/**
- * FIXME.
+ * Function called by the namestore plugin when we are trying to lookup
+ * a record as part of #handle_record_lookup(). Merges all results into
+ * the context.
*
- * @param seq sequence number of the record
+ * @param cls closure with a `struct RecordLookupContext`
+ * @param seq unique serial number of the record, MUST NOT BE ZERO
+ * @param zone_key private key of the zone
+ * @param label name that is being mapped (at most 255 characters long)
+ * @param rd_count number of entries in @a rd array
+ * @param rd array of records with data to store
*/
static void
lookup_it (void *cls,
struct RecordLookupContext *rlc = cls;
(void) private_key;
- (void) seq;
+ GNUNET_assert (0 != seq);
if (0 != strcmp (label,
rlc->label))
return;
{
uint32_t name_len;
size_t src_size;
- const char *name_tmp;
(void) cls;
name_len = ntohl (ll_msg->label_len);
GNUNET_break (0);
return GNUNET_SYSERR;
}
-
- name_tmp = (const char *) &ll_msg[1];
- if ('\0' != name_tmp[name_len -1])
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
+ GNUNET_MQ_check_zero_termination (ll_msg);
return GNUNET_OK;
}
conv_name)) ||
(GNUNET_GNSRECORD_TYPE_NICK != rd[i].record_type) )
rd_clean_off++;
+
+ if ( (0 == strcmp (GNUNET_GNS_EMPTY_LABEL_AT,
+ conv_name)) &&
+ (GNUNET_GNSRECORD_TYPE_NICK == rd[i].record_type) )
+ cache_nick (&rp_msg->private_key,
+ &rd[i]);
}
res = GSN_database->store_records (GSN_database->cls,
&rp_msg->private_key,
* Zone to name iterator
*
* @param cls struct ZoneToNameCtx *
- * @param seq sequence number of the record
+ * @param seq sequence number of the record, MUST NOT BE ZERO
* @param zone_key the zone key
* @param name name
* @param rd_count number of records in @a rd
char *name_tmp;
char *rd_tmp;
- (void) seq;
+ GNUNET_assert (0 != seq);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found result for zone-to-name lookup: `%s'\n",
name);
* Process results for zone iteration from database
*
* @param cls struct ZoneIterationProcResult
- * @param seq sequence number of the record
+ * @param seq sequence number of the record, MUST NOT BE ZERO
* @param zone_key the zone key
* @param name name
* @param rd_count number of records for this name
struct ZoneIterationProcResult *proc = cls;
int do_refresh_block;
+ GNUNET_assert (0 != seq);
if ( (NULL == zone_key) &&
(NULL == name) )
{
}
if (GNUNET_YES == do_refresh_block)
refresh_block (NULL,
+ proc->zi,
0,
zone_key,
name,
uint64_t limit)
{
struct ZoneIterationProcResult proc;
- struct GNUNET_MQ_Envelope *env;
- struct RecordResultMessage *rrm;
struct GNUNET_TIME_Absolute start;
struct GNUNET_TIME_Relative duration;
duration.rel_value_us,
GNUNET_NO);
if (0 == proc.limit)
- {
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Returned %llu results, more results available\n",
(unsigned long long) limit);
- return; /* more results later after we get the
-#GNUNET_MESSAGE_TYPE_NAMESTORE_ZONE_ITERATION_NEXT message */
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Completed iteration after %llu/%llu results\n",
- (unsigned long long) (limit - proc.limit),
- (unsigned long long) limit);
- /* send empty response to indicate end of list */
- env = GNUNET_MQ_msg (rrm,
- GNUNET_MESSAGE_TYPE_NAMESTORE_RECORD_RESULT);
- rrm->gns_header.r_id = htonl (zi->request_id);
- GNUNET_MQ_send (zi->nc->mq,
- env);
- GNUNET_CONTAINER_DLL_remove (zi->nc->op_head,
- zi->nc->op_tail,
- zi);
- GNUNET_free (zi);
+ zi->send_end = (0 != proc.limit);
+ if (0 == zi->cache_ops)
+ zone_iteration_done_client_continue (zi);
}
zi);
run_zone_iteration_round (zi,
1);
- GNUNET_SERVICE_client_continue (nc->client);
}
}
run_zone_iteration_round (zi,
limit);
- GNUNET_SERVICE_client_continue (nc->client);
}
* A #GNUNET_NAMESTORE_RecordIterator for monitors.
*
* @param cls a 'struct ZoneMonitor *' with information about the monitor
- * @param seq sequence number of the record
+ * @param seq sequence number of the record, MUST NOT BE ZERO
* @param zone_key zone key of the zone
* @param name name
* @param rd_count number of records in @a rd
{
struct ZoneMonitor *zm = cls;
+ GNUNET_assert (0 != seq);
zm->seq = seq;
GNUNET_assert (NULL != name);
GNUNET_STATISTICS_update (statistics,