fix #3284: support lib/MULTIARCH/ paths in installation, use GNUNET_PREFIX=@libdir...
[oweals/gnunet.git] / src / peerinfo / peerinfo_api.c
index 0f95a421a2e50986bcc9322a126048204d6bc4d0..716802389b6de7bea21dafece62b0b53726f24ac 100644 (file)
@@ -1,10 +1,10 @@
 /*
      This file is part of GNUnet.
 /*
      This file is part of GNUnet.
-     (C) 2001, 2002, 2004, 2005, 2007, 2009 Christian Grothoff (and other contributing authors)
+     (C) 2001, 2002, 2004, 2005, 2007, 2009, 2010, 2012 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
 
      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 2, or (at your
+     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
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
  * @author Christian Grothoff
  */
 #include "platform.h"
  * @author Christian Grothoff
  */
 #include "platform.h"
-#include "gnunet_client_lib.h"
-#include "gnunet_peerinfo_service.h"
+#include "gnunet_util_lib.h"
 #include "gnunet_protocols.h"
 #include "gnunet_protocols.h"
-#include "gnunet_time_lib.h"
 #include "peerinfo.h"
 
 #include "peerinfo.h"
 
-#define ADD_PEER_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
+#define LOG(kind,...) GNUNET_log_from (kind, "peerinfo-api",__VA_ARGS__)
 
 
 
 
-struct CAFContext
+/**
+ * Entry in the transmission queue to PEERINFO service.  We use
+ * the same structure for queueing 'iteration' requests and
+ * actual 'add' messages.
+ */
+struct GNUNET_PEERINFO_AddContext
 {
 {
-  struct GNUNET_CLIENT_Connection *client;
-  struct GNUNET_MessageHeader *msg;
-};
+  /**
+   * This is a linked list.
+   */
+  struct GNUNET_PEERINFO_AddContext *next;
 
 
+  /**
+   * This is a linked list.
+   */
+  struct GNUNET_PEERINFO_AddContext *prev;
 
 
-static size_t
-copy_and_free (void *cls, size_t size, void *buf)
-{
-  struct CAFContext *cc = cls;
-  struct GNUNET_MessageHeader *msg = cc->msg;
-  uint16_t msize;
+  /**
+   * Handle to the PEERINFO service.
+   */
+  struct GNUNET_PEERINFO_Handle *h;
 
 
-  if (buf == NULL)
-    {
-#if DEBUG_PEERINFO
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  _
-                  ("Failed to transmit message of type %u to `%s' service.\n"),
-                  ntohs (msg->type), "peerinfo");
-#endif
-      GNUNET_free (msg);
-      GNUNET_CLIENT_disconnect (cc->client);
-      GNUNET_free (cc);
-      return 0;
-    }
-  msize = ntohs (msg->size);
-  GNUNET_assert (size >= msize);
-  memcpy (buf, msg, msize);
-  GNUNET_free (msg);
-  GNUNET_CLIENT_disconnect (cc->client);
-  GNUNET_free (cc);
-  return msize;
-}
+  /**
+   * Function to call after request has been transmitted, or NULL.
+   */
+  GNUNET_PEERINFO_Continuation cont;
 
 
+  /**
+   * Closure for 'cont'.
+   */
+  void *cont_cls;
 
 
+  /**
+   * Number of bytes of the request message (follows after this struct).
+   */
+  size_t size;
 
 
-/**
- * Add a host to the persistent list.
- *
- * @param cfg configuration to use
- * @param sched scheduler to use
- * @param peer identity of the peer
- * @param hello the verified (!) HELLO message
- */
-void
-GNUNET_PEERINFO_add_peer (const struct GNUNET_CONFIGURATION_Handle *cfg,
-                          struct GNUNET_SCHEDULER_Handle *sched,
-                          const struct GNUNET_PeerIdentity *peer,
-                          const struct GNUNET_HELLO_Message *hello)
-{
-  struct GNUNET_CLIENT_Connection *client;
-  struct PeerAddMessage *pam;
-  uint16_t hs;
-  struct CAFContext *cc;
-
-#if DEBUG_PEERINFO
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Adding peer `%s' to peerinfo database\n",
-             GNUNET_i2s(peer));
-#endif
-  client = GNUNET_CLIENT_connect (sched, "peerinfo", cfg);
-  if (client == NULL)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  _("Could not connect to `%s' service.\n"), "peerinfo");
-      return;
-    }
-  hs = GNUNET_HELLO_size (hello);
-#if DEBUG_PEERINFO
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Size of `%s' is %u bytes\n",
-             "HELLO",
-             (unsigned int) GNUNET_HELLO_size (hello));
-#endif  
-  pam = GNUNET_malloc (sizeof (struct PeerAddMessage) + hs);
-  pam->header.size = htons (hs + sizeof (struct PeerAddMessage));
-  pam->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_ADD);
-  memcpy (&pam->peer, peer, sizeof (struct GNUNET_PeerIdentity));
-  memcpy (&pam[1], hello, hs);
-  cc = GNUNET_malloc (sizeof (struct CAFContext));
-  cc->client = client;
-  cc->msg = &pam->header;
-  GNUNET_CLIENT_notify_transmit_ready (client,
-                                       ntohs (pam->header.size),
-                                       ADD_PEER_TIMEOUT, 
-                                      GNUNET_NO,
-                                      &copy_and_free, cc);
-}
+};
 
 
 /**
 
 
 /**
- * Context for the info handler.
+ * Context for an iteration request.
  */
 struct GNUNET_PEERINFO_IteratorContext
 {
 
   /**
  */
 struct GNUNET_PEERINFO_IteratorContext
 {
 
   /**
-   * Our connection to the PEERINFO service.
+   * Kept in a DLL.
    */
    */
-  struct GNUNET_CLIENT_Connection *client;
+  struct GNUNET_PEERINFO_IteratorContext *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct GNUNET_PEERINFO_IteratorContext *prev;
+
+  /**
+   * Handle to the PEERINFO service.
+   */
+  struct GNUNET_PEERINFO_Handle *h;
 
   /**
 
   /**
-   * Function to call with information.
+   * Function to call with the results.
    */
   GNUNET_PEERINFO_Processor callback;
 
   /**
    */
   GNUNET_PEERINFO_Processor callback;
 
   /**
-   * Closure for callback.
+   * Closure for 'callback'.
    */
   void *callback_cls;
 
   /**
    */
   void *callback_cls;
 
   /**
-   * When should we time out?
+   * Our entry in the transmission queue.
+   */
+  struct GNUNET_PEERINFO_AddContext *ac;
+
+  /**
+   * Task responsible for timeout.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier timeout_task;
+
+  /**
+   * Timeout for the operation.
    */
   struct GNUNET_TIME_Absolute timeout;
 
    */
   struct GNUNET_TIME_Absolute timeout;
 
+  /**
+   * Peer we are interested in (only valid if iteration was restricted to one peer).
+   */
+  struct GNUNET_PeerIdentity peer;
+
+  /**
+   * Is 'peer' set?
+   */
+  int have_peer;
+
+  /**
+   * Set to GNUNET_YES if we are currently receiving replies from the
+   * service.
+   */
+  int request_transmitted;
+
 };
 
 
 /**
 };
 
 
 /**
- * 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
+ * Handle to the peerinfo service.
  */
  */
-static void
-info_handler (void *cls, const struct GNUNET_MessageHeader *msg)
+struct GNUNET_PEERINFO_Handle
 {
 {
-  struct GNUNET_PEERINFO_IteratorContext *ic = cls;
-  const struct InfoMessage *im;
-  const struct GNUNET_HELLO_Message *hello;
-  uint16_t ms;
+  /**
+   * Our configuration.
+   */
+  const struct GNUNET_CONFIGURATION_Handle *cfg;
 
 
-  if (msg == NULL)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  _("Failed to receive response from `%s' service.\n"),
-                  "peerinfo");
-      ic->callback (ic->callback_cls, NULL, NULL, 1);
-      GNUNET_CLIENT_disconnect (ic->client);
-      GNUNET_free (ic);
-      return;
-    }
-  if (ntohs (msg->type) == GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END)
-    {
-#if DEBUG_PEERINFO
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                 "Received end of list of peers from peerinfo database\n");
-#endif
-      ic->callback (ic->callback_cls, NULL, NULL, 0);
-      GNUNET_CLIENT_disconnect (ic->client);
-      GNUNET_free (ic);
-      return;
-    }
-  ms = ntohs (msg->size);
-  if ((ms < sizeof (struct InfoMessage)) ||
-      (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_PEERINFO_INFO))
-    {
-      GNUNET_break (0);
-      ic->callback (ic->callback_cls, NULL, NULL, 2);
-      GNUNET_CLIENT_disconnect (ic->client);
-      GNUNET_free (ic);
-      return;
-    }
-  im = (const struct InfoMessage *) msg;
-  hello = NULL;
-  if (ms > sizeof (struct InfoMessage) + sizeof (struct GNUNET_MessageHeader))
-    {
-      hello = (const struct GNUNET_HELLO_Message *) &im[1];
-      if (ms != sizeof (struct InfoMessage) + GNUNET_HELLO_size (hello))
-        {
-          GNUNET_break (0);
-          ic->callback (ic->callback_cls, NULL, NULL, 2);
-          GNUNET_CLIENT_disconnect (ic->client);
-          GNUNET_free (ic);
-          return;
-        }
-    }
-#if DEBUG_PEERINFO
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Received information about peer `%s' from peerinfo database\n",
-             GNUNET_i2s (&im->peer));
-#endif
-#if DEBUG_PEERINFO
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Size of `%s' is %u bytes\n",
-             "HELLO",
-             (unsigned int) GNUNET_HELLO_size (hello));
-#endif  
-  ic->callback (ic->callback_cls, &im->peer, hello, ntohl (im->trust));
-  GNUNET_CLIENT_receive (ic->client,
-                         &info_handler,
-                         ic,
-                         GNUNET_TIME_absolute_get_remaining (ic->timeout));
-}
+  /**
+   * Connection to the service.
+   */
+  struct GNUNET_CLIENT_Connection *client;
+
+  /**
+   * Head of transmission queue.
+   */
+  struct GNUNET_PEERINFO_AddContext *ac_head;
+
+  /**
+   * Tail of transmission queue.
+   */
+  struct GNUNET_PEERINFO_AddContext *ac_tail;
+
+  /**
+   * Handle for the current transmission request, or NULL if none is pending.
+   */
+  struct GNUNET_CLIENT_TransmitHandle *th;
+
+  /**
+   * Head of iterator DLL.
+   */
+  struct GNUNET_PEERINFO_IteratorContext *ic_head;
+
+  /**
+   * Tail of iterator DLL.
+   */
+  struct GNUNET_PEERINFO_IteratorContext *ic_tail;
+
+  /**
+   * ID for a reconnect task.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier r_task;
+
+  /**
+   * Are we now receiving?
+   */
+  int in_receive;
+
+};
 
 
 /**
 
 
 /**
- * Call a method for each known matching host and change
- * its trust value.  The method will be invoked once for
- * each host and then finally once with a NULL pointer.
- * Note that the last call can be triggered by timeout or
- * by simply being done; however, the trust argument will
- * be set to zero if we are done and to 1 if we timed out.
+ * Connect to the peerinfo service.
  *
  * @param cfg configuration to use
  *
  * @param cfg configuration to use
- * @param sched scheduler to use
- * @param peer restrict iteration to this peer only (can be NULL)
- * @param trust_delta how much to change the trust in all matching peers
- * @param timeout how long to wait until timing out
- * @param callback the method to call for each peer
- * @param callback_cls closure for callback
- * @return NULL on error, otherwise an iterator context
+ * @return NULL on error (configuration related, actual connection
+ *         establishment may happen asynchronously).
  */
  */
-struct GNUNET_PEERINFO_IteratorContext *
-GNUNET_PEERINFO_iterate (const struct GNUNET_CONFIGURATION_Handle *cfg,
-                         struct GNUNET_SCHEDULER_Handle *sched,
-                         const struct GNUNET_PeerIdentity *peer,
-                         int trust_delta,
-                         struct GNUNET_TIME_Relative timeout,
-                         GNUNET_PEERINFO_Processor callback,
-                         void *callback_cls)
+struct GNUNET_PEERINFO_Handle *
+GNUNET_PEERINFO_connect (const struct GNUNET_CONFIGURATION_Handle *cfg)
 {
 {
-  struct GNUNET_CLIENT_Connection *client;
-  struct ListAllPeersMessage *lapm;
-  struct ListPeerMessage *lpm;
-  struct GNUNET_PEERINFO_IteratorContext *ihc;
+  struct GNUNET_PEERINFO_Handle *h;
 
 
-  client = GNUNET_CLIENT_connect (sched, "peerinfo", cfg);
-  if (client == NULL)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  _("Could not connect to `%s' service.\n"), "peerinfo");
-      return NULL;
-    }
-#if DEBUG_PEERINFO
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Requesting list of peers from peerinfo database\n");
-#endif
-  if (peer == NULL)
-    {
-      ihc = GNUNET_malloc (sizeof (struct GNUNET_PEERINFO_IteratorContext) +
-                          sizeof (struct ListAllPeersMessage));
-      lapm = (struct ListAllPeersMessage *) &ihc[1];
-      lapm->header.size = htons (sizeof (struct ListAllPeersMessage));
-      lapm->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL);
-      lapm->trust_change = htonl (trust_delta);
-    }
-  else
-    {
-      ihc = GNUNET_malloc (sizeof (struct GNUNET_PEERINFO_IteratorContext) +
-                          sizeof (struct ListPeerMessage));
-      lpm = (struct ListPeerMessage *) &ihc[1];
-      lpm->header.size = htons (sizeof (struct ListPeerMessage));
-      lpm->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_GET);
-      lpm->trust_change = htonl (trust_delta);
-      memcpy (&lpm->peer, peer, sizeof (struct GNUNET_PeerIdentity));
-    }
-  ihc->client = client;
-  ihc->callback = callback;
-  ihc->callback_cls = callback_cls;
-  ihc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
-  if (GNUNET_OK != 
-      GNUNET_CLIENT_transmit_and_get_response (client,
-                                              (const struct GNUNET_MessageHeader*) &ihc[1],
-                                              timeout,
-                                              GNUNET_YES,
-                                              &info_handler,
-                                              ihc))
-    {
-      GNUNET_break (0);
-      GNUNET_CLIENT_disconnect (ihc->client);
-      GNUNET_free (ihc);
-      return NULL;
-    }
-  return ihc;
+  h = GNUNET_new (struct GNUNET_PEERINFO_Handle);
+  h->client = GNUNET_CLIENT_connect ("peerinfo", cfg);
+  h->cfg = cfg;
+  return h;
 }
 
 
 /**
 }
 
 
 /**
- * Cancel an iteration over peer information.
+ * Disconnect from the peerinfo service.  Note that all iterators must
+ * have completed or have been cancelled by the time this function is
+ * called (otherwise, calling this function is a serious error).
+ * Furthermore, if 'GNUNET_PEERINFO_add_peer' operations are still
+ * pending, they will be cancelled silently on disconnect.
  *
  *
- * @param ic context of the iterator to cancel
+ * @param h handle to disconnect
  */
 void
  */
 void
-GNUNET_PEERINFO_iterate_cancel (struct GNUNET_PEERINFO_IteratorContext *ic)
+GNUNET_PEERINFO_disconnect (struct GNUNET_PEERINFO_Handle *h)
 {
 {
-  GNUNET_CLIENT_disconnect (ic->client);
-  GNUNET_free (ic);
+  struct GNUNET_PEERINFO_AddContext *ac;
+  struct GNUNET_PEERINFO_IteratorContext *ic;
+
+  while (NULL != (ic = h->ic_head))
+  {
+    GNUNET_break (GNUNET_YES == ic->request_transmitted);
+    ic->request_transmitted = GNUNET_NO;
+    GNUNET_PEERINFO_iterate_cancel (ic);
+  }
+  while (NULL != (ac = h->ac_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (h->ac_head, h->ac_tail, ac);
+    if (NULL != ac->cont)
+      ac->cont (ac->cont_cls, _("aborted due to explicit disconnect request"));
+    GNUNET_free (ac);
+  }
+  if (NULL != h->th)
+  {
+    GNUNET_CLIENT_notify_transmit_ready_cancel (h->th);
+    h->th = NULL;
+  }
+  if (NULL != h->client)
+  {
+    GNUNET_CLIENT_disconnect (h->client);
+    h->client = NULL;
+  }
+  if (GNUNET_SCHEDULER_NO_TASK != h->r_task)
+  {
+    GNUNET_SCHEDULER_cancel (h->r_task);
+    h->r_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  GNUNET_free (h);
 }
 
 
 /**
 }
 
 
 /**
- * Context for the info handler.
+ * Check if we have a request pending in the transmission queue and are
+ * able to transmit it right now.  If so, schedule transmission.
+ *
+ * @param h handle to the service
  */
  */
-struct GNUNET_PEERINFO_NotifyContext
-{
-
-  /**
-   * Our connection to the PEERINFO service.
-   */
-  struct GNUNET_CLIENT_Connection *client;
+static void
+trigger_transmit (struct GNUNET_PEERINFO_Handle *h);
 
 
-  /**
-   * Function to call with information.
-   */
-  GNUNET_PEERINFO_Processor callback;
 
 
-  /**
-   * Closure for callback.
-   */
-  void *callback_cls;
+/**
+ * Close the existing connection to PEERINFO and reconnect.
+ *
+ * @param h handle to the service
+ */
+static void
+reconnect (struct GNUNET_PEERINFO_Handle *h);
 
 
-  /**
-   * Handle to our initial request for message transmission to
-   * the peerinfo service.
-   */
-  struct GNUNET_CLIENT_TransmitHandle *init;
 
 
-  /**
-   * Configuration.
-   */
-  const struct GNUNET_CONFIGURATION_Handle *cfg;
+/**
+ * Task scheduled to re-try connecting to the peerinfo service.
+ *
+ * @param cls the 'struct GNUNET_PEERINFO_Handle'
+ * @param tc scheduler context
+ */
+static void
+reconnect_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct GNUNET_PEERINFO_Handle *h = cls;
 
 
-  /**
-   * Scheduler.
-   */
-  struct GNUNET_SCHEDULER_Handle *sched;
-};
+  h->r_task = GNUNET_SCHEDULER_NO_TASK;
+  reconnect (h);
+}
 
 
 /**
 
 
 /**
- * Send a request to the peerinfo service to start being
- * notified about all changes to peer information.
+ * Close the existing connection to PEERINFO and reconnect.
  *
  *
- * @param nc our context
+ * @param h handle to the service
  */
 static void
  */
 static void
-request_notifications (struct GNUNET_PEERINFO_NotifyContext *nc);
+reconnect (struct GNUNET_PEERINFO_Handle *h)
+{
+  if (GNUNET_SCHEDULER_NO_TASK != h->r_task)
+  {
+    GNUNET_SCHEDULER_cancel (h->r_task);
+    h->r_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  if (NULL != h->th)
+  {
+    GNUNET_CLIENT_notify_transmit_ready_cancel (h->th);
+    h->th = NULL;
+  }
+  if (NULL != h->client)
+  {
+    GNUNET_CLIENT_disconnect (h->client);
+    h->client = NULL;
+  }
+  h->in_receive = GNUNET_NO;
+  h->client = GNUNET_CLIENT_connect ("peerinfo", h->cfg);
+  if (NULL == h->client)
+  {
+    h->r_task =
+        GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &reconnect_task,
+                                      h);
+    return;
+  }
+  trigger_transmit (h);
+}
 
 
 /**
 
 
 /**
- * Read notifications from the client handle and pass them
- * to the callback.
+ * Transmit the request at the head of the transmission queue
+ * and trigger continuation (if any).
  *
  *
- * @param nc our context
+ * @param cls the 'struct GNUNET_PEERINFO_Handle' (with the queue)
+ * @param size size of the buffer (0 on error)
+ * @param buf where to copy the message
+ * @return number of bytes copied to buf
+ */
+static size_t
+do_transmit (void *cls, size_t size, void *buf)
+{
+  struct GNUNET_PEERINFO_Handle *h = cls;
+  struct GNUNET_PEERINFO_AddContext *ac = h->ac_head;
+  size_t ret;
+
+  h->th = NULL;
+  if (NULL == ac)
+    return 0; /* request was cancelled in the meantime */
+  if (NULL == buf)
+  {
+    /* peerinfo service died */
+    LOG (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK,
+         "Failed to transmit message to `%s' service.\n", "PEERINFO");
+    GNUNET_CONTAINER_DLL_remove (h->ac_head, h->ac_tail, ac);
+    reconnect (h);
+    if (NULL != ac->cont)
+      ac->cont (ac->cont_cls, _("failed to transmit request (service down?)"));
+    GNUNET_free (ac);
+    return 0;
+  }
+  ret = ac->size;
+  if (size < ret)
+  {
+    /* change in head of queue (i.e. cancel + add), try again */
+    trigger_transmit (h);
+    return 0;
+  }
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Transmitting request of size %u to `%s' service.\n", ret, "PEERINFO");
+  memcpy (buf, &ac[1], ret);
+  GNUNET_CONTAINER_DLL_remove (h->ac_head, h->ac_tail, ac);
+  trigger_transmit (h);
+  if (NULL != ac->cont)
+    ac->cont (ac->cont_cls, NULL);
+  GNUNET_free (ac);
+  return ret;
+}
+
+
+/**
+ * Check if we have a request pending in the transmission queue and are
+ * able to transmit it right now.  If so, schedule transmission.
+ *
+ * @param h handle to the service
  */
 static void
  */
 static void
-receive_notifications (struct GNUNET_PEERINFO_NotifyContext *nc);
+trigger_transmit (struct GNUNET_PEERINFO_Handle *h)
+{
+  struct GNUNET_PEERINFO_AddContext *ac;
+
+  if (NULL == (ac = h->ac_head))
+    return; /* no requests queued */
+  if (NULL != h->th)
+    return; /* request already pending */
+  if (NULL == h->client)
+  {
+    /* disconnected, try to reconnect */
+    reconnect (h);
+    return;
+  }
+  h->th =
+    GNUNET_CLIENT_notify_transmit_ready (h->client, ac->size,
+                                        GNUNET_TIME_UNIT_FOREVER_REL,
+                                        GNUNET_YES,
+                                        &do_transmit, h);
+}
 
 
 /**
 
 
 /**
- * Receive a peerinfo information message, process it and
- * go for more.
+ * Add a host to the persistent list.  This method operates in
+ * semi-reliable mode: if the transmission is not completed by
+ * the time 'GNUNET_PEERINFO_disconnect' is called, it will be
+ * aborted.  Furthermore, if a second HELLO is added for the
+ * same peer before the first one was transmitted, PEERINFO may
+ * merge the two HELLOs prior to transmission to the service.
+ *
+ * @param h handle to the peerinfo service
+ * @param hello the verified (!) HELLO message
+ * @param cont continuation to call when done, NULL is allowed
+ * @param cont_cls closure for 'cont'
+ * @return handle to cancel add operation; all pending
+ *         'add' operations will be cancelled automatically
+ *        on disconnect, so it is not necessary to keep this
+ *        handle (unless 'cont' is NULL and at some point
+ *        calling 'cont' must be prevented)
+ */
+struct GNUNET_PEERINFO_AddContext *
+GNUNET_PEERINFO_add_peer (struct GNUNET_PEERINFO_Handle *h,
+                          const struct GNUNET_HELLO_Message *hello,
+                         GNUNET_PEERINFO_Continuation cont,
+                         void *cont_cls)
+{
+  uint16_t hs = GNUNET_HELLO_size (hello);
+  struct GNUNET_PEERINFO_AddContext *ac;
+  struct GNUNET_PeerIdentity peer;
+
+  GNUNET_assert (GNUNET_OK == GNUNET_HELLO_get_id (hello, &peer));
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Adding peer `%s' to PEERINFO database (%u bytes of `%s')\n",
+       GNUNET_i2s (&peer), hs, "HELLO");
+  ac = GNUNET_malloc (sizeof (struct GNUNET_PEERINFO_AddContext) + hs);
+  ac->h = h;
+  ac->size = hs;
+  ac->cont = cont;
+  ac->cont_cls = cont_cls;
+  memcpy (&ac[1], hello, hs);
+  GNUNET_CONTAINER_DLL_insert_tail (h->ac_head, h->ac_tail, ac);
+  trigger_transmit (h);
+  return ac;
+}
+
+
+/**
+ * Cancel pending 'add' operation.  Must only be called before
+ * either 'cont' or 'GNUNET_PEERINFO_disconnect' are invoked.
+ *
+ * @param ac handle for the add operation to cancel
+ */
+void
+GNUNET_PEERINFO_add_peer_cancel (struct GNUNET_PEERINFO_AddContext *ac)
+{
+  struct GNUNET_PEERINFO_Handle *h = ac->h;
+
+  GNUNET_CONTAINER_DLL_remove (h->ac_head, h->ac_tail, ac);
+  GNUNET_free (ac);
+}
+
+
+/**
+ * Type of a function to call when we receive a message from the
+ * service.  Call the iterator with the result and (if applicable)
+ * continue to receive more messages or trigger processing the next
+ * event (if applicable).
  *
  * @param cls closure
  * @param msg message received, NULL on timeout or fatal error
  */
 static void
  *
  * @param cls closure
  * @param msg message received, NULL on timeout or fatal error
  */
 static void
-process_notification (void *cls,
-                     const struct
-                     GNUNET_MessageHeader * msg)
+peerinfo_handler (void *cls, const struct GNUNET_MessageHeader *msg)
 {
 {
-  struct GNUNET_PEERINFO_NotifyContext *nc = cls;
+  struct GNUNET_PEERINFO_Handle *h = cls;
+  struct GNUNET_PEERINFO_IteratorContext *ic = h->ic_head;
   const struct InfoMessage *im;
   const struct GNUNET_HELLO_Message *hello;
   const struct InfoMessage *im;
   const struct GNUNET_HELLO_Message *hello;
+  GNUNET_PEERINFO_Processor cb;
+  struct GNUNET_PeerIdentity id;
+  void *cb_cls;
   uint16_t ms;
 
   uint16_t ms;
 
-  if (msg == NULL)
+  h->in_receive = GNUNET_NO;
+  if (NULL == msg)
+  {
+    /* peerinfo service died, signal error */
+    if (NULL != ic)
     {
     {
-      GNUNET_CLIENT_disconnect (nc->client);
-      nc->client = GNUNET_CLIENT_connect (nc->sched, "peerinfo", nc->cfg);
-      request_notifications (nc);
-      return;
+      cb = ic->callback;
+      cb_cls = ic->callback_cls;
+      GNUNET_PEERINFO_iterate_cancel (ic);
+    }
+    else
+    {
+      cb = NULL;
+    }
+    reconnect (h);
+    if (NULL != cb)
+      cb (cb_cls, NULL, NULL,
+         _("Failed to receive response from `PEERINFO' service."));
+    return;
+  }
+  if (NULL == ic)
+  {
+    /* didn't expect a response, reconnect */
+    reconnect (h);
+    return;
+  }
+  ic->request_transmitted = GNUNET_NO;
+  cb = ic->callback;
+  cb_cls = ic->callback_cls;
+  if (GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END == ntohs (msg->type))
+  {
+    /* normal end of list of peers, signal end, process next pending request */
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "Received end of list of peers from `%s' service\n", "PEERINFO");
+    GNUNET_PEERINFO_iterate_cancel (ic);
+    trigger_transmit (h);
+    if ( (GNUNET_NO == h->in_receive) &&
+        (NULL != h->ic_head) )
+    {
+      h->in_receive = GNUNET_YES;
+      GNUNET_CLIENT_receive (h->client, &peerinfo_handler, h,
+                            GNUNET_TIME_absolute_get_remaining (h->ic_head->timeout));
     }
     }
+    if (NULL != cb)
+      cb (cb_cls, NULL, NULL, NULL);
+    return;
+  }
+
   ms = ntohs (msg->size);
   if ((ms < sizeof (struct InfoMessage)) ||
       (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_PEERINFO_INFO))
   ms = ntohs (msg->size);
   if ((ms < sizeof (struct InfoMessage)) ||
       (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_PEERINFO_INFO))
+  {
+    /* malformed message */
+    GNUNET_break (0);
+    GNUNET_PEERINFO_iterate_cancel (ic);
+    reconnect (h);
+    if (NULL != cb)
+      cb (cb_cls, NULL, NULL,
+         _("Received invalid message from `PEERINFO' service."));
+    return;
+  }
+  im = (const struct InfoMessage *) msg;
+  GNUNET_break (0 == ntohl (im->reserved));
+  if ( (GNUNET_YES == ic->have_peer) &&
+       (0 != memcmp (&ic->peer, &im->peer, sizeof (struct GNUNET_PeerIdentity))) )
+  {
+    /* bogus message (from a different iteration call?); out of sequence! */
+    LOG (GNUNET_ERROR_TYPE_ERROR,
+         "Received HELLO for peer `%s', expected peer `%s'\n",
+        GNUNET_i2s (&im->peer),
+        GNUNET_i2s (&ic->peer));
+
+    GNUNET_break (0);
+    GNUNET_PEERINFO_iterate_cancel (ic);
+    reconnect (h);
+    if (NULL != cb)
+      cb (cb_cls, NULL, NULL,
+         _("Received invalid message from `PEERINFO' service."));
+    return;
+  }
+  hello = NULL;
+  if (ms > sizeof (struct InfoMessage) + sizeof (struct GNUNET_MessageHeader))
+  {
+    hello = (const struct GNUNET_HELLO_Message *) &im[1];
+    if (ms != sizeof (struct InfoMessage) + GNUNET_HELLO_size (hello))
     {
     {
+      /* malformed message */
       GNUNET_break (0);
       GNUNET_break (0);
-      GNUNET_CLIENT_disconnect (nc->client);
-      nc->client = GNUNET_CLIENT_connect (nc->sched, "peerinfo", nc->cfg);
-      request_notifications (nc);
+      GNUNET_PEERINFO_iterate_cancel (ic);
+      reconnect (h);
+      if (NULL != cb)
+        cb (cb_cls, NULL, NULL,
+           _("Received invalid message from `PEERINFO' service."));
       return;
     }
       return;
     }
-  im = (const struct InfoMessage *) msg;
-  hello = NULL;
-  if (ms > sizeof (struct InfoMessage) + sizeof (struct GNUNET_MessageHeader))
+    if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &id))
     {
     {
-      hello = (const struct GNUNET_HELLO_Message *) &im[1];
-      if (ms != sizeof (struct InfoMessage) + GNUNET_HELLO_size (hello))
-        {
-          GNUNET_break (0);
-         GNUNET_CLIENT_disconnect (nc->client);
-         nc->client = GNUNET_CLIENT_connect (nc->sched, "peerinfo", nc->cfg);
-         request_notifications (nc);
-          return;
-        }
+      /* malformed message */
+      GNUNET_break (0);
+      GNUNET_PEERINFO_iterate_cancel (ic);
+      reconnect (h);
+      if (NULL != cb)
+        cb (cb_cls, NULL, NULL,
+           _("Received invalid message from `PEERINFO' service."));
+      return;
     }
     }
-#if DEBUG_PEERINFO
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Received information about peer `%s' from peerinfo database\n",
-             GNUNET_i2s (&im->peer));
-#endif
-  nc->callback (nc->callback_cls, &im->peer, hello, ntohl (im->trust));
-  receive_notifications (nc);
+    if (0 != memcmp (&im->peer, &id, sizeof (struct GNUNET_PeerIdentity)))
+    {
+      /* malformed message */
+      GNUNET_break (0);
+      GNUNET_PEERINFO_iterate_cancel (ic);
+      reconnect (h);
+      if (NULL != cb)
+        cb (cb_cls, NULL, NULL,
+           _("Received invalid message from `PEERINFO' service."));
+      return;
+    }
+  }
+
+  /* normal data message */
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Received %u bytes of `%s' information about peer `%s' from `%s' service\n",
+       (hello == NULL) ? 0 : (unsigned int) GNUNET_HELLO_size (hello), "HELLO",
+       GNUNET_i2s (&im->peer), "PEERINFO");
+  h->in_receive = GNUNET_YES;
+  GNUNET_CLIENT_receive (h->client, &peerinfo_handler, h,
+                         GNUNET_TIME_absolute_get_remaining (ic->timeout));
+  if (NULL != cb)
+    cb (cb_cls, &im->peer, hello, NULL);
 }
 
 
 /**
 }
 
 
 /**
- * Read notifications from the client handle and pass them
- * to the callback.
+ * We've transmitted the iteration request.  Now get ready to process
+ * the results (or handle transmission error).
  *
  *
- * @param nc our context
+ * @param cls the 'struct GNUNET_PEERINFO_IteratorContext'
+ * @param emsg error message, NULL if transmission worked
  */
 static void
  */
 static void
-receive_notifications (struct GNUNET_PEERINFO_NotifyContext *nc)
+iterator_start_receive (void *cls, const char *emsg)
 {
 {
-  GNUNET_CLIENT_receive (nc->client,
-                        &process_notification,
-                        nc,
-                        GNUNET_TIME_UNIT_FOREVER_REL);
-}
-
-
-/**
- * Transmit our init-notify request, start receiving.
- *
- * @param cls closure (our 'struct GNUNET_PEERINFO_NotifyContext')
- * @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_notify_request (void *cls,
-                        size_t size, 
-                        void *buf)
-{
-  struct GNUNET_PEERINFO_NotifyContext *nc = cls;
-  struct GNUNET_MessageHeader hdr;
-
-  nc->init = NULL;
-  if (buf == NULL)
-    {
-      GNUNET_CLIENT_disconnect (nc->client);
-      nc->client = GNUNET_CLIENT_connect (nc->sched, "peerinfo", nc->cfg);
-      request_notifications (nc);
-      return 0;
-    }
-  GNUNET_assert (size >= sizeof (struct GNUNET_MessageHeader));
-  hdr.size = htons (sizeof (struct GNUNET_MessageHeader));
-  hdr.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY);
-  memcpy (buf, &hdr, sizeof (struct GNUNET_MessageHeader));
-  receive_notifications (nc);
-  return sizeof (struct GNUNET_MessageHeader);
+  struct GNUNET_PEERINFO_IteratorContext *ic = cls;
+  struct GNUNET_PEERINFO_Handle *h = ic->h;
+  GNUNET_PEERINFO_Processor cb;
+  void *cb_cls;
+
+  ic->ac = NULL;
+  if (NULL != emsg)
+  {
+    cb = ic->callback;
+    cb_cls = ic->callback_cls;
+    GNUNET_PEERINFO_iterate_cancel (ic);
+    reconnect (h);
+    if (NULL != cb)
+      cb (cb_cls, NULL, NULL, emsg);
+    return;
+  }
+  LOG (GNUNET_ERROR_TYPE_DEBUG, "Waiting for response from `%s' service.\n",
+       "PEERINFO");
+  ic->request_transmitted = GNUNET_YES;
+  if (GNUNET_NO == h->in_receive)
+  {
+    h->in_receive = GNUNET_YES;
+    GNUNET_CLIENT_receive (h->client, &peerinfo_handler, h,
+                          GNUNET_TIME_absolute_get_remaining (ic->timeout));
+  }
 }
 
 
 /**
 }
 
 
 /**
- * Send a request to the peerinfo service to start being
- * notified about all changes to peer information.
+ * Peerinfo iteration request has timed out.
  *
  *
- * @param nc our context
+ * @param cls the 'struct GNUNET_PEERINFO_IteratorContext*'
+ * @param tc scheduler context
  */
 static void
  */
 static void
-request_notifications (struct GNUNET_PEERINFO_NotifyContext *nc)
+signal_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
 {
 {
-  GNUNET_assert (NULL == nc->init);
-  nc->init =GNUNET_CLIENT_notify_transmit_ready (nc->client,
-                                                sizeof (struct GNUNET_MessageHeader),
-                                                GNUNET_TIME_UNIT_FOREVER_REL,
-                                                GNUNET_YES,
-                                                &transmit_notify_request,
-                                                nc);
+  struct GNUNET_PEERINFO_IteratorContext *ic = cls;
+  GNUNET_PEERINFO_Processor cb;
+  void *cb_cls;
+
+  ic->timeout_task = GNUNET_SCHEDULER_NO_TASK;
+  cb = ic->callback;
+  cb_cls = ic->callback_cls;
+  GNUNET_PEERINFO_iterate_cancel (ic);
+  if (NULL != cb)
+    cb (cb_cls, NULL, NULL,
+       _("Timeout transmitting iteration request to `PEERINFO' service."));
 }
 
 
 /**
 }
 
 
 /**
- * Call a method whenever our known information about peers
- * changes.  Initially calls the given function for all known
- * peers and then only signals changes.
+ * Call a method for each known matching host.  The callback method
+ * will be invoked once for each matching host and then finally once
+ * with a NULL pointer.  After that final invocation, the iterator
+ * context must no longer be used.
  *
  *
- * @param cfg configuration to use
- * @param sched scheduler to use
+ * Instead of calling this function with 'peer == NULL' it is often
+ * better to use 'GNUNET_PEERINFO_notify'.
+ *
+ * @param h handle to the peerinfo service
+ * @param include_friend_only include HELLO messages for friends only
+ * @param peer restrict iteration to this peer only (can be NULL)
+ * @param timeout how long to wait until timing out
  * @param callback the method to call for each peer
  * @param callback_cls closure for callback
  * @param callback the method to call for each peer
  * @param callback_cls closure for callback
- * @return NULL on error
+ * @return iterator context
  */
  */
-struct GNUNET_PEERINFO_NotifyContext *
-GNUNET_PEERINFO_notify (const struct GNUNET_CONFIGURATION_Handle *cfg,
-                       struct GNUNET_SCHEDULER_Handle *sched,
-                       GNUNET_PEERINFO_Processor callback,
-                       void *callback_cls)
+struct GNUNET_PEERINFO_IteratorContext *
+GNUNET_PEERINFO_iterate (struct GNUNET_PEERINFO_Handle *h,
+                                                                                                int include_friend_only,
+                         const struct GNUNET_PeerIdentity *peer,
+                         struct GNUNET_TIME_Relative timeout,
+                         GNUNET_PEERINFO_Processor callback, void *callback_cls)
 {
 {
-  struct GNUNET_PEERINFO_NotifyContext *nc;
-  struct GNUNET_CLIENT_Connection *client;
-
-  client = GNUNET_CLIENT_connect (sched, "peerinfo", cfg);
-  if (client == NULL)
-    {      
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  _("Could not connect to `%s' service.\n"), "peerinfo");
-      return NULL;
-    }
-  nc = GNUNET_malloc (sizeof (struct GNUNET_PEERINFO_NotifyContext));
-  nc->sched = sched;
-  nc->cfg = cfg;
-  nc->client = client;
-  nc->callback = callback;
-  nc->callback_cls = callback_cls; 
-  request_notifications (nc);
-  return nc;
+  struct ListAllPeersMessage *lapm;
+  struct ListPeerMessage *lpm;
+  struct GNUNET_PEERINFO_IteratorContext *ic;
+  struct GNUNET_PEERINFO_AddContext *ac;
+
+  ic = GNUNET_new (struct GNUNET_PEERINFO_IteratorContext);
+  if (NULL == peer)
+  {
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "Requesting list of peers from PEERINFO service\n");
+    ac =
+        GNUNET_malloc (sizeof (struct GNUNET_PEERINFO_AddContext) +
+                       sizeof (struct ListAllPeersMessage));
+    ac->size = sizeof (struct ListAllPeersMessage);
+    lapm = (struct ListAllPeersMessage *) &ac[1];
+    lapm->header.size = htons (sizeof (struct ListAllPeersMessage));
+    lapm->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL);
+    lapm->include_friend_only = htonl (include_friend_only);
+  }
+  else
+  {
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "Requesting information on peer `%4s' from PEERINFO service\n",
+         GNUNET_i2s (peer));
+    ac =
+        GNUNET_malloc (sizeof (struct GNUNET_PEERINFO_AddContext) +
+                       sizeof (struct ListPeerMessage));
+    ac->size = sizeof (struct ListPeerMessage);
+    lpm = (struct ListPeerMessage *) &ac[1];
+    lpm->header.size = htons (sizeof (struct ListPeerMessage));
+    lpm->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_GET);
+    lpm->include_friend_only = htonl (include_friend_only);
+    memcpy (&lpm->peer, peer, sizeof (struct GNUNET_PeerIdentity));
+    ic->have_peer = GNUNET_YES;
+    ic->peer = *peer;
+  }
+  ic->h = h;
+  ic->ac = ac;
+  ic->callback = callback;
+  ic->callback_cls = callback_cls;
+  ic->timeout = GNUNET_TIME_relative_to_absolute (timeout);
+  ic->timeout_task =
+      GNUNET_SCHEDULER_add_delayed (timeout, &signal_timeout, ic);
+  ac->cont = &iterator_start_receive;
+  ac->cont_cls = ic;
+  GNUNET_CONTAINER_DLL_insert_tail (h->ac_head, h->ac_tail, ac);
+  GNUNET_CONTAINER_DLL_insert_tail (h->ic_head,
+                                   h->ic_tail,
+                                   ic);
+  trigger_transmit (h);
+  return ic;
 }
 
 
 /**
 }
 
 
 /**
- * Stop notifying about changes.
+ * Cancel an iteration over peer information.
  *
  *
- * @param nc context to stop notifying
+ * @param ic context of the iterator to cancel
  */
 void
  */
 void
-GNUNET_PEERINFO_notify_cancel (struct GNUNET_PEERINFO_NotifyContext *nc)
+GNUNET_PEERINFO_iterate_cancel (struct GNUNET_PEERINFO_IteratorContext *ic)
 {
 {
-  if (NULL != nc->init)
-    {
-      GNUNET_CLIENT_notify_transmit_ready_cancel (nc->init);
-      nc->init = NULL;
-    }
-  GNUNET_CLIENT_disconnect (nc->client);
-  GNUNET_free (nc);
+  struct GNUNET_PEERINFO_Handle *h;
+
+  h = ic->h;
+  if (GNUNET_SCHEDULER_NO_TASK != ic->timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (ic->timeout_task);
+    ic->timeout_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  ic->callback = NULL;
+  if (GNUNET_YES == ic->request_transmitted)
+    return;                     /* need to finish processing */
+  GNUNET_CONTAINER_DLL_remove (h->ic_head,
+                              h->ic_tail,
+                              ic);
+  if (NULL != ic->ac)
+  {
+    GNUNET_CONTAINER_DLL_remove (h->ac_head, h->ac_tail, ic->ac);
+    GNUNET_free (ic->ac);
+  }
+  GNUNET_free (ic);
 }
 
 
 }