Index: AUTHORS
===================================================================
--- AUTHORS	(revision 14325)
+++ AUTHORS	(working copy)
@@ -53,6 +53,7 @@
 Tzvetan Horozov <chorozov@gmail.com>
 Uli Luckas <luckas@musoft.de>
 Vasil Dimov <vd@datamax.bg>
+Vitaly Minko <vitaly.minko@gmail.com>
 Werner Koch <libgcrypt@g10code.com> [original code of libgcrypt]
 
 Translations (webpage, documentation, as far as known):
Index: src/chat/test_chat.c
===================================================================
--- src/chat/test_chat.c	(revision 0)
+++ src/chat/test_chat.c	(revision 0)
@@ -0,0 +1,630 @@
+/*
+     This file is part of GNUnet.
+     (C) 2005, 2006, 2007, 2008, 2011 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 chat/test_chat.c
+ * @brief base test case for the chat library
+ * @author Christian Grothoff
+ * @author Nathan Evans
+ * @author Vitaly Minko
+ *
+ * This test case serves as a base for simple chatting, anonymous chatting,
+ * authenticated chatting and acknowledgements test cases.  Based on the
+ * executable being run the correct test case will be performed.  Private
+ * chatting is covered by a separate test case since it requires 3 users.
+ */
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_arm_service.h"
+#include "gnunet_chat_service.h"
+
+#define VERBOSE GNUNET_NO
+
+#define START_ARM GNUNET_YES
+
+/**
+ * How long until we give up on passing the test?
+ */
+#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
+
+struct PeerContext
+{
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+#if START_ARM
+  struct GNUNET_OS_Process *arm_proc;
+#endif
+};
+
+struct Wanted
+{
+  struct GNUNET_CONTAINER_MetaData *meta;
+
+  GNUNET_HashCode *sender;
+
+  char *msg;
+
+  const char *me;
+
+  enum GNUNET_CHAT_MsgOptions opt;
+
+  uint32_t sequence_number;
+
+  struct GNUNET_TIME_Absolute timestamp;
+
+  GNUNET_SCHEDULER_Task next_task;
+
+  void *next_task_cls;
+
+};
+
+static struct PeerContext p1;
+
+static struct PeerContext p2;
+
+static GNUNET_HashCode alice;
+
+static GNUNET_HashCode bob;
+
+static struct GNUNET_CHAT_Room *alice_room;
+
+static struct GNUNET_CHAT_Room *bob_room;
+
+static struct GNUNET_CONTAINER_MetaData *alice_meta;
+
+static struct GNUNET_CONTAINER_MetaData *bob_meta;
+
+static struct Wanted alice_wanted;
+
+static struct Wanted bob_wanted;
+
+static GNUNET_SCHEDULER_TaskIdentifier kill_task;
+
+static GNUNET_SCHEDULER_TaskIdentifier wait_task;
+
+static int err;
+
+static int is_ready;
+
+static int is_p2p;
+
+static int is_ackn;
+
+static int is_anon;
+
+static int is_auth;
+
+
+static void
+setup_peer (struct PeerContext *p, const char *cfgname)
+{
+  p->cfg = GNUNET_CONFIGURATION_create ();
+#if START_ARM
+  p->arm_proc = GNUNET_OS_start_process (NULL, NULL, "gnunet-service-arm",
+                                        "gnunet-service-arm",
+#if VERBOSE
+                                        "-L", "DEBUG",
+#endif
+                                        "-c", cfgname, NULL);
+#endif
+  GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname));
+}
+
+
+static void
+stop_arm (struct PeerContext *p)
+{
+#if START_ARM
+  if (0 != GNUNET_OS_process_kill (p->arm_proc, SIGTERM))
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
+  if (GNUNET_OS_process_wait(p->arm_proc) != GNUNET_OK)
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "ARM process %u stopped\n", GNUNET_OS_process_get_pid (p->arm_proc));
+  GNUNET_OS_process_close (p->arm_proc);
+  p->arm_proc = NULL;
+#endif
+  GNUNET_CONFIGURATION_destroy (p->cfg);
+}
+
+
+static void
+abort_test (void *cls,
+	    const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  if (alice_room != NULL)
+    {
+      GNUNET_CHAT_leave_room (alice_room);
+      alice_room = NULL;
+    }
+  if (bob_room != NULL)
+    {
+      GNUNET_CHAT_leave_room (bob_room);
+      bob_room = NULL;
+    }
+  err = 1;
+}
+
+
+static void
+timeout_kill (void *cls,
+	      const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Timed out, stopping the test.\n");
+#endif
+  kill_task = GNUNET_SCHEDULER_NO_TASK;
+  if (wait_task != GNUNET_SCHEDULER_NO_TASK)
+    {
+      GNUNET_SCHEDULER_cancel (wait_task);
+      wait_task = GNUNET_SCHEDULER_NO_TASK;
+    }
+  GNUNET_SCHEDULER_add_continuation (&abort_test, NULL,
+				     GNUNET_SCHEDULER_REASON_PREREQ_DONE);
+}
+
+
+static int
+join_cb (void *cls)
+{
+  struct Wanted *want = cls;
+
+#if VERBOSE
+  printf ("%s has joined\n", want->me);
+#endif
+  if (NULL != want->next_task)
+    GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+  return GNUNET_OK;
+}
+
+
+static int
+member_list_cb (void *cls,
+		const struct GNUNET_CONTAINER_MetaData *member_info,
+		const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
+		enum GNUNET_CHAT_MsgOptions options)
+{
+  struct Wanted *want = cls;
+  GNUNET_HashCode sender;
+
+#if VERBOSE
+  printf ("%s - told that %s has %s\n",
+           want->me,
+           member_info == NULL ? NULL
+           : GNUNET_CONTAINER_meta_data_get_by_type (member_info,
+						     EXTRACTOR_METATYPE_TITLE),
+	   member_info == NULL ? "left" : "joined");
+#endif
+  GNUNET_CRYPTO_hash (member_id,
+		      sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
+		      &sender);
+  if ((0 == memcmp (&sender, want->sender,
+		    sizeof (GNUNET_HashCode))) &&
+      (((member_info == NULL) &&
+	(want->meta == NULL)) ||
+       ((member_info != NULL) &&
+	(want->meta != NULL) &&
+	(GNUNET_CONTAINER_meta_data_test_equal (member_info,
+						want->meta)))) &&
+      (options == want->opt))
+    {
+      if (NULL != want->next_task)
+	GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+    }
+  else
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (&abort_test, NULL);
+    }
+  return GNUNET_OK;
+}
+
+
+static int
+receive_cb (void *cls,
+	    struct GNUNET_CHAT_Room *room,
+	    const GNUNET_HashCode *sender,
+	    const struct GNUNET_CONTAINER_MetaData *meta,
+	    const char *message,
+	    struct GNUNET_TIME_Absolute timestamp,
+	    enum GNUNET_CHAT_MsgOptions options)
+{
+  struct Wanted *want = cls;
+
+#if VERBOSE
+  printf ("%s - told that %s said %s\n",
+	  want->me,
+	  meta == NULL ? NULL
+	  : GNUNET_CONTAINER_meta_data_get_by_type (meta,
+						    EXTRACTOR_METATYPE_TITLE),
+	  message);
+#endif
+  if ((0 == strcmp (message, want->msg)) &&
+      (((sender == NULL) && (want->sender == NULL)) ||
+       ((sender != NULL) && (want->sender != NULL) &&
+	(0 == memcmp (sender, want->sender,
+		      sizeof (GNUNET_HashCode))))) &&
+      (GNUNET_CONTAINER_meta_data_test_equal (meta, want->meta)) &&
+      (options == want->opt) &&
+      /* Not == since the library sets the actual timestamp, so it may be
+       * slightly greater
+       */
+      (timestamp.abs_value >= want->timestamp.abs_value))
+    {
+      if (NULL != want->next_task)
+	GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+    }
+  else
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (&abort_test, NULL);
+    }
+  return GNUNET_OK;
+}
+
+
+static int
+confirmation_cb (void *cls,
+		 struct GNUNET_CHAT_Room *room,
+		 uint32_t orig_seq_number,
+		 struct GNUNET_TIME_Absolute timestamp,
+		 const GNUNET_HashCode *receiver)
+{
+  struct Wanted *want = cls;
+
+#if VERBOSE
+  printf ("%s - told that %s acknowledged message #%d\n",
+	  want->me,
+	  GNUNET_CONTAINER_meta_data_get_by_type (want->meta,
+						  EXTRACTOR_METATYPE_TITLE),
+	  orig_seq_number);
+#endif
+  if ((0 == memcmp (receiver, want->sender,
+		    sizeof (GNUNET_HashCode))) &&
+      (orig_seq_number == want->sequence_number) &&
+      (timestamp.abs_value >= want->timestamp.abs_value))
+    {
+      if (NULL != want->next_task)
+	GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+    }
+  else
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (&abort_test, NULL);
+    }
+  return GNUNET_OK;
+}
+
+
+static void
+wait_until_ready (void *cls,
+		  const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  GNUNET_SCHEDULER_Task task = cls;
+
+#if VERBOSE
+  printf ("Waiting...\n");
+#endif
+  if (is_ready)
+    {
+      wait_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (task, NULL);
+    }
+  else
+    wait_task =
+      GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+								   50),
+				    &wait_until_ready,
+				    task);
+}
+
+
+static void
+disconnect_alice (void *cls,
+		  const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Alice is leaving.\n");
+#endif
+  if (is_p2p)
+    stop_arm (&p2);
+  GNUNET_CHAT_leave_room (alice_room);
+  alice_room = NULL;
+  GNUNET_SCHEDULER_cancel (kill_task);
+  kill_task = GNUNET_SCHEDULER_NO_TASK;
+}
+
+
+static void
+disconnect_bob (void *cls,
+		const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Bod is leaving.\n");
+#endif
+  alice_wanted.meta = NULL;
+  alice_wanted.sender = &bob;
+  alice_wanted.msg = NULL;
+  alice_wanted.opt = 0;
+  alice_wanted.next_task = &disconnect_alice;
+  alice_wanted.next_task_cls = NULL;
+  GNUNET_CHAT_leave_room (bob_room);
+  bob_room = NULL;
+}
+
+
+static void
+set_ready (void *cls,
+	   const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  is_ready = GNUNET_YES;
+}
+
+
+static void
+send_to_alice (void *cls,
+	       const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Bob says 'Hi!'\n");
+#endif
+
+  alice_wanted.meta = bob_meta;
+  alice_wanted.sender = &bob;
+  alice_wanted.msg = "Hi Alice!";
+  alice_wanted.opt = GNUNET_CHAT_MSG_OPTION_NONE;
+  alice_wanted.timestamp = GNUNET_TIME_absolute_get ();
+  alice_wanted.next_task = &disconnect_bob;
+  alice_wanted.next_task_cls = NULL;
+  GNUNET_CHAT_send_message (bob_room,
+			    "Hi Alice!",
+			    GNUNET_CHAT_MSG_OPTION_NONE,
+			    NULL,
+			    NULL);
+}
+
+
+static void
+send_to_bob (void *cls,
+	     const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  enum GNUNET_CHAT_MsgOptions options;
+  uint32_t *seq = NULL;
+
+#if VERBOSE
+  printf ("Alice says 'Hi!'\n");
+#endif
+  if (is_ackn)
+    {
+      options = GNUNET_CHAT_MSG_ACKNOWLEDGED;
+      alice_wanted.meta = bob_meta;
+      alice_wanted.sender = &bob;
+      alice_wanted.timestamp = GNUNET_TIME_absolute_get ();
+      alice_wanted.next_task = &disconnect_bob;
+      alice_wanted.next_task_cls = NULL;
+      bob_wanted.meta = alice_meta;
+      bob_wanted.sender = &alice;
+      bob_wanted.next_task = NULL;
+      seq = &(alice_wanted.sequence_number);
+    }
+  else if (is_anon)
+    {
+      options = GNUNET_CHAT_MSG_ANONYMOUS;
+      bob_wanted.meta = NULL;
+      bob_wanted.sender = NULL;
+      bob_wanted.next_task = &disconnect_bob;
+    }
+  else if (is_auth)
+    {
+      options = GNUNET_CHAT_MSG_AUTHENTICATED;
+      bob_wanted.meta = alice_meta;
+      bob_wanted.sender = &alice;
+      bob_wanted.next_task = &disconnect_bob;
+    }
+  else
+    {
+      options = GNUNET_CHAT_MSG_OPTION_NONE;
+      bob_wanted.meta = alice_meta;
+      bob_wanted.sender = &alice;
+      bob_wanted.next_task = &send_to_alice;
+    }
+  bob_wanted.msg = "Hi Bob!";
+  bob_wanted.opt = options;
+  bob_wanted.timestamp = GNUNET_TIME_absolute_get ();
+  bob_wanted.next_task_cls = NULL;
+  GNUNET_CHAT_send_message (alice_room, "Hi Bob!", options, NULL, seq);
+}
+
+
+static void
+prepare_for_alice_task (void *cls,
+			const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  bob_wanted.meta = alice_meta;
+  bob_wanted.sender = &alice;
+  bob_wanted.msg = NULL;
+  bob_wanted.opt = -1;
+  bob_wanted.next_task = &set_ready;
+  bob_wanted.next_task_cls = NULL;
+}
+
+
+static void
+join_bob_task (void *cls,
+	       const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Bob joining\n");
+#endif
+  alice_wanted.meta = bob_meta;
+  alice_wanted.sender = &bob;
+  alice_wanted.msg = NULL;
+  alice_wanted.opt = -1;
+  alice_wanted.next_task = &wait_until_ready;
+  alice_wanted.next_task_cls = &send_to_bob;
+  bob_wanted.next_task = &prepare_for_alice_task;
+  bob_wanted.next_task_cls = NULL;
+  is_ready = GNUNET_NO;
+  bob_room =
+    GNUNET_CHAT_join_room (is_p2p ? p2.cfg : p1.cfg, "bob", bob_meta,
+			   "test", -1,
+			   &join_cb, &bob_wanted,
+			   &receive_cb, &bob_wanted,
+			   &member_list_cb, &bob_wanted,
+			   &confirmation_cb, &bob_wanted,
+			   &bob);
+  if (NULL == bob_room)
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_CHAT_leave_room (alice_room);
+      alice_room = NULL;
+      err = 1;
+    }
+}
+
+
+static void
+join_alice_task (void *cls,
+		 const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Alice joining\n");
+#endif
+  alice_wanted.next_task = &join_bob_task;
+  alice_wanted.next_task_cls = NULL;
+  alice_room =
+    GNUNET_CHAT_join_room (p1.cfg, "alice", alice_meta,
+			   "test", -1,
+			   &join_cb, &alice_wanted,
+			   &receive_cb, &alice_wanted,
+			   &member_list_cb, &alice_wanted,
+			   &confirmation_cb, &alice_wanted,
+			   &alice);
+  if (NULL == alice_room)
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      err = 1;
+    }
+}
+
+
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  if (is_p2p)
+    {
+      setup_peer (&p1, "test_chat_peer1.conf");
+      setup_peer (&p2, "test_chat_peer2.conf");
+    }
+  else
+    setup_peer (&p1, "test_chat_data.conf");
+
+  memset (&alice_wanted, 0, sizeof (struct Wanted));
+  memset (&bob_wanted, 0, sizeof (struct Wanted));
+  alice_wanted.me = "Alice";
+  bob_wanted.me = "Bob";
+  alice_meta = GNUNET_CONTAINER_meta_data_create ();
+  GNUNET_CONTAINER_meta_data_insert (alice_meta,
+				     "<gnunet>",
+				     EXTRACTOR_METATYPE_TITLE,
+				     EXTRACTOR_METAFORMAT_UTF8,
+				     "text/plain",
+				     "Alice",
+				     strlen("Alice")+1);
+  bob_meta = GNUNET_CONTAINER_meta_data_create ();
+  GNUNET_CONTAINER_meta_data_insert (bob_meta,
+				     "<gnunet>",
+				     EXTRACTOR_METATYPE_TITLE,
+				     EXTRACTOR_METAFORMAT_UTF8,
+				     "text/plain",
+				     "Bob",
+				     strlen("Bob")+1);
+  kill_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
+					    &timeout_kill,
+					    NULL);
+  GNUNET_SCHEDULER_add_now (&join_alice_task, NULL);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  char *const argvx[] = { 
+    "test-chat",
+    "-c",
+    "test_chat_data.conf",
+#if VERBOSE
+    "-L", "DEBUG",
+#endif
+    NULL
+  };
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_OPTION_END
+  };
+
+  GNUNET_log_setup ("test_chat", 
+#if VERBOSE
+		    "DEBUG",
+#else
+		    "WARNING",
+#endif
+		    NULL);
+  if (strstr(argv[0], "p2p") != NULL)
+    {
+      is_p2p = GNUNET_YES;
+    }
+  if (strstr(argv[0], "acknowledgment") != NULL)
+    {
+      is_ackn = GNUNET_YES;
+    }
+  else if (strstr(argv[0], "anonymous") != NULL)
+    {
+      is_anon = GNUNET_YES;
+    }
+  else if (strstr(argv[0], "authentication") != NULL)
+    {
+      is_auth = GNUNET_YES;
+    }
+  GNUNET_PROGRAM_run ((sizeof (argvx) / sizeof (char *)) - 1,
+                      argvx, "test-chat",
+		      "nohelp", options, &run, NULL);
+  stop_arm (&p1);
+  GNUNET_CONTAINER_meta_data_destroy (alice_meta);
+  GNUNET_CONTAINER_meta_data_destroy (bob_meta);
+  if (is_p2p)
+    {
+      GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat-peer-1/");
+      GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat-peer-2/");
+    }
+  else
+    GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat/");
+  return err;
+}
+
+/* end of test_chat.c */
Index: src/chat/test_chat_peer2.conf
===================================================================
--- src/chat/test_chat_peer2.conf	(revision 0)
+++ src/chat/test_chat_peer2.conf	(revision 0)
@@ -0,0 +1,72 @@
+[PATHS]
+SERVICEHOME = /tmp/gnunet-test-chat-peer-2/
+DEFAULTCONFIG = test_chat_peer2.conf
+
+[gnunetd]
+HOSTKEY = $SERVICEHOME/.hostkey
+
+[hostlist]
+SERVERS = http://localhost:31000/
+OPTIONS = -b
+
+[resolver]
+PORT = 32001
+HOSTNAME = localhost
+UNIXPATH = /tmp/gnunet-chat-p2-service-resolver.sock
+
+[transport]
+PORT = 32002
+UNIXPATH = /tmp/gnunet-chat-p2-service-transport.sock
+PLUGINS = tcp
+
+[transport-tcp]
+PORT = 32003
+
+[arm]
+PORT = 32004
+UNIXPATH = /tmp/gnunet-chat-p2-service-arm.sock
+HOSTNAME = localhost
+DEFAULTSERVICES = resolver transport core topology hostlist statistics chat
+
+[core]
+PORT = 32005
+UNIXPATH = /tmp/gnunet-chat-p2-service-core.sock
+HOSTNAME = localhost
+
+[topology]
+MINIMUM-FRIENDS = 0
+FRIENDS-ONLY = NO
+AUTOCONNECT = YES
+TARGET-CONNECTION-COUNT = 16
+FRIENDS = $SERVICEHOME/friends
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-daemon-topology
+
+[peerinfo]
+PORT = 32006
+UNIXPATH = /tmp/gnunet-chat-p2-service-peerinfo.sock
+HOSTNAME = localhost
+
+[statistics]
+PORT = 32007
+HOSTNAME = localhost
+UNIXPATH = /tmp/gnunet-chat-p2-service-statistics.sock
+
+[chat]
+PORT = 32008
+HOSTNAME = localhost
+HOME = $SERVICEHOME
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-service-chat
+
+[testing]
+WEAKRANDOM = YES
+
+[fs]
+AUTOSTART = NO
+
+[datastore]
+AUTOSTART = NO
+
+[dht]
+AUTOSTART = NO
Index: src/chat/chat.c
===================================================================
--- src/chat/chat.c	(revision 14325)
+++ src/chat/chat.c	(working copy)
@@ -32,12 +32,12 @@
 #include "gnunet_signatures.h"
 #include "chat.h"
 
-#define DEBUG_CHAT GNUNET_YES
+#define DEBUG_CHAT GNUNET_NO
 #define NICK_IDENTITY_PREFIX ".chat_identity_"
 
 
 /**
- * Handle for a (joined) chat room.
+ * Handle for a chat room.
  */
 struct GNUNET_CHAT_Room
 {
@@ -53,6 +53,12 @@
 
   struct MemberList *members;
 
+  int is_joined;
+
+  GNUNET_CHAT_JoinCallback join_callback;
+
+  void *join_callback_cls;
+
   GNUNET_CHAT_MessageCallback message_callback;
 
   void *message_callback_cls;
@@ -221,7 +227,9 @@
   struct JoinNotificationMessage *join_msg;
   struct ReceiveNotificationMessage *received_msg;
   struct ConfirmationReceiptMessage *receipt;
+  struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
   GNUNET_HashCode id;
+  const GNUNET_HashCode *sender;
   struct GNUNET_CONTAINER_MetaData *meta;
   struct GNUNET_CHAT_SendReceiptContext *src;
   struct MemberList *pos;
@@ -261,11 +269,30 @@
 			  sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
 			  &pos->id);
       GNUNET_PSEUDONYM_add (room->cfg, &pos->id, meta);
+      pos->next = room->members;
+      room->members = pos;
+      if (GNUNET_NO == room->is_joined)
+	{
+	  GNUNET_CRYPTO_rsa_key_get_public (room->my_private_key, &pkey);
+	  if (0 == memcmp (&join_msg->public_key,
+			   &pkey,
+			   sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded)))
+	    {
+	      room->join_callback (room->join_callback_cls);
+	      room->is_joined = GNUNET_YES;
+	    }
+	  else
+	    {
+	      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+			  _("The current user must be the the first one joined\n"));
+	      GNUNET_break (0);
+	      return;
+	    }
+	}
+      else 
       room->member_list_callback (room->member_list_callback_cls,
 				  meta, &join_msg->public_key,
 				  ntohl (join_msg->msg_options));
-      pos->next = room->members;
-      room->members = pos;
       break;
     case GNUNET_MESSAGE_TYPE_CHAT_LEAVE_NOTIFICATION:
 #if DEBUG_CHAT
@@ -347,6 +374,13 @@
 	  memcpy (message_content, &received_msg[1], msg_len);
 	}
       message_content[msg_len] = '\0';
+      if (0 != (ntohl (received_msg->msg_options) & GNUNET_CHAT_MSG_ANONYMOUS))
+	{
+	  sender = NULL;
+	  meta = NULL;
+	}
+      else
+	{
       pos = room->members;
       while ((NULL != pos) &&
 	     (0 != memcmp (&pos->id,
@@ -354,11 +388,15 @@
 			   sizeof (GNUNET_HashCode))))
 	pos = pos->next;
       GNUNET_assert (NULL != pos);
+	  sender = &received_msg->sender;
+	  meta = pos->meta;
+	}
       room->message_callback (room->message_callback_cls,
 			      room,
-			      &received_msg->sender,
-			      pos->meta,
+			      sender,
+			      meta,
 			      message_content,
+			      GNUNET_TIME_absolute_ntoh (received_msg->timestamp),
 			      ntohl (received_msg->msg_options));
       if (message_content != decrypted_msg)
 	GNUNET_free (message_content);
@@ -378,9 +416,7 @@
 				     room,
 				     ntohl (receipt->sequence_number),
 				     GNUNET_TIME_absolute_ntoh (receipt->timestamp),
-				     &receipt->target,
-				     &receipt->content,
-				     &receipt->signature);
+				     &receipt->target);
       break;
     default:
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -510,8 +546,9 @@
     {
 #if DEBUG_CHAT
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-		  "Could not transmit join request\n");
+		  "Could not transmit join request, retrying...\n");
 #endif
+      GNUNET_CHAT_rejoin_room (chat_room);
       return 0;
     }
 #if DEBUG_CHAT
@@ -542,6 +579,10 @@
 		  _("Could not serialize metadata\n"));
       return 0;
     }
+  GNUNET_CLIENT_receive (chat_room->client,
+			 &receive_results,
+			 chat_room,
+			 GNUNET_TIME_UNIT_FOREVER_REL);
   return size_of_join;
 }
 
@@ -623,6 +664,8 @@
 		       struct GNUNET_CONTAINER_MetaData *member_info,
 		       const char *room_name,
 		       enum GNUNET_CHAT_MsgOptions msg_options,
+		       GNUNET_CHAT_JoinCallback joinCallback,
+		       void *join_cls,
 		       GNUNET_CHAT_MessageCallback messageCallback,
 		       void *message_cls,
 		       GNUNET_CHAT_MemberListCallback memberCallback,
@@ -654,11 +697,32 @@
 		  _("Failed to connect to the chat service\n"));
       return NULL;
     }
+  if (NULL == joinCallback)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+		  _("Undefined mandatory parameter: joinCallback\n"));
+      return NULL;
+    }
+  if (NULL == messageCallback)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+		  _("Undefined mandatory parameter: messageCallback\n"));
+      return NULL;
+    }
+  if (NULL == memberCallback)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+		  _("Undefined mandatory parameter: memberCallback\n"));
+      return NULL;
+    }
   chat_room = GNUNET_malloc (sizeof (struct GNUNET_CHAT_Room));
   chat_room->msg_options = msg_options;
   chat_room->room_name = GNUNET_strdup (room_name);
   chat_room->member_info = GNUNET_CONTAINER_meta_data_duplicate (member_info);
   chat_room->my_private_key = priv_key;
+  chat_room->is_joined = GNUNET_NO;
+  chat_room->join_callback = joinCallback;
+  chat_room->join_callback_cls = join_cls;
   chat_room->message_callback = messageCallback;
   chat_room->message_callback_cls = message_cls;
   chat_room->member_list_callback = memberCallback;
@@ -668,10 +732,6 @@
   chat_room->cfg = cfg;
   chat_room->client = client;
   chat_room->members = NULL;
-  GNUNET_CLIENT_receive (client,
-			 &receive_results,
-			 chat_room,
-			 GNUNET_TIME_UNIT_FOREVER_REL);
   if (GNUNET_SYSERR == GNUNET_CHAT_rejoin_room (chat_room))
     {
       GNUNET_CHAT_leave_room (chat_room);
@@ -717,6 +777,8 @@
   msg_to_send->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_TRANSMIT_REQUEST);
   msg_to_send->msg_options = htonl (smc->options);
   msg_to_send->sequence_number = htonl (smc->sequence_number);
+  msg_to_send->timestamp =
+    GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get ());
   msg_to_send->reserved = htonl (0);
   if (NULL == smc->receiver)
     memset (&msg_to_send->target, 0, sizeof (GNUNET_HashCode));
@@ -770,13 +832,15 @@
 #if DEBUG_CHAT
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending a message\n");
 #endif
-  *sequence_number = ++room->sequence_number;
+  room->sequence_number++;
+  if (NULL != sequence_number)
+    *sequence_number = room->sequence_number;
   smc = GNUNET_malloc (sizeof (struct GNUNET_CHAT_SendMessageContext));
   smc->chat_room = room;
   smc->message = GNUNET_strdup (message);
   smc->options = options;
   smc->receiver = receiver;
-  smc->sequence_number = *sequence_number;
+  smc->sequence_number = room->sequence_number;
   msg_size = strlen (message) + sizeof (struct TransmitRequestMessage);
   GNUNET_CLIENT_notify_transmit_ready (room->client,
 				       msg_size,
Index: src/chat/test_chat_peer3.conf
===================================================================
--- src/chat/test_chat_peer3.conf	(revision 0)
+++ src/chat/test_chat_peer3.conf	(revision 0)
@@ -0,0 +1,72 @@
+[PATHS]
+SERVICEHOME = /tmp/gnunet-test-chat-peer-3/
+DEFAULTCONFIG = test_chat_peer3.conf
+
+[gnunetd]
+HOSTKEY = $SERVICEHOME/.hostkey
+
+[hostlist]
+SERVERS = http://localhost:31000/
+OPTIONS = -b
+
+[resolver]
+PORT = 33001
+HOSTNAME = localhost
+UNIXPATH = /tmp/gnunet-chat-p3-service-resolver.sock
+
+[transport]
+PORT = 33002
+UNIXPATH = /tmp/gnunet-chat-p3-service-transport.sock
+PLUGINS = tcp
+
+[transport-tcp]
+PORT = 33003
+
+[arm]
+PORT = 33004
+UNIXPATH = /tmp/gnunet-chat-p3-service-arm.sock
+HOSTNAME = localhost
+DEFAULTSERVICES = resolver transport core topology hostlist statistics chat
+
+[core]
+PORT = 33005
+UNIXPATH = /tmp/gnunet-chat-p3-service-core.sock
+HOSTNAME = localhost
+
+[topology]
+MINIMUM-FRIENDS = 0
+FRIENDS-ONLY = NO
+AUTOCONNECT = YES
+TARGET-CONNECTION-COUNT = 16
+FRIENDS = $SERVICEHOME/friends
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-daemon-topology
+
+[peerinfo]
+PORT = 33006
+UNIXPATH = /tmp/gnunet-chat-p3-service-peerinfo.sock
+HOSTNAME = localhost
+
+[statistics]
+PORT = 33007
+HOSTNAME = localhost
+UNIXPATH = /tmp/gnunet-chat-p3-service-statistics.sock
+
+[chat]
+PORT = 33008
+HOSTNAME = localhost
+HOME = $SERVICEHOME
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-service-chat
+
+[testing]
+WEAKRANDOM = YES
+
+[fs]
+AUTOSTART = NO
+
+[datastore]
+AUTOSTART = NO
+
+[dht]
+AUTOSTART = NO
Index: src/chat/chat.h
===================================================================
--- src/chat/chat.h	(revision 14325)
+++ src/chat/chat.h	(working copy)
@@ -70,8 +70,13 @@
   uint32_t reserved GNUNET_PACKED;
 
   /**
+   * Timestamp of the message.
+   */
+  struct GNUNET_TIME_AbsoluteNBO timestamp;
+
+  /**
    * Hash of the public key of the pseudonym of the sender of the message.
-   * TBD: Should be all zeros for anonymous.
+   * Should be all zeros for anonymous.
    */
   GNUNET_HashCode sender;
 
@@ -122,6 +127,11 @@
   uint32_t sequence_number GNUNET_PACKED;
 
   /**
+   * Timestamp of the message.
+   */
+  struct GNUNET_TIME_AbsoluteNBO timestamp;
+
+  /**
    * Who should receive this message?  Set to all zeros for "everyone".
    */
   GNUNET_HashCode target;
@@ -131,12 +141,15 @@
 
 /**
  * Receipt sent from a message receiver to the service to confirm delivery of
- * a chat message.
+ * a chat message and from the service to sender of the original message to
+ * acknowledge delivery.
  */
 struct ConfirmationReceiptMessage
 {
   /**
-   * Message type will be GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_RECEIPT 
+   * Message type will be
+   * GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_RECEIPT when sending from client,
+   * GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_NOTIFICATION when sending to client.
    */
   struct GNUNET_MessageHeader header;
 
@@ -351,8 +364,9 @@
 
 /**
  * Message send by one peer to another to indicate receiving of a chat message.
- * After this struct, the remaining bytes are the actual text message.  If the
- * mesasge is private, then the text is encrypted, otherwise it's plaintext.
+ * This struct is followed by the room name (only if the message is anonymous)
+ * and then the remaining bytes are the actual text message.  If the mesasge is
+ * private, then the text is encrypted, otherwise it's plaintext.
  */
 struct P2PReceiveNotificationMessage
 {
@@ -372,13 +386,23 @@
   uint32_t sequence_number GNUNET_PACKED;
 
   /**
+   * Length of the room name. This is only used for anonymous messages.
+   */
+  uint16_t room_name_len GNUNET_PACKED;
+
+  /**
    * Reserved (for alignment).
    */
-  uint32_t reserved GNUNET_PACKED;
+  uint16_t reserved GNUNET_PACKED;
+
+  /**
+   * Timestamp of the message.
+   */
+  struct GNUNET_TIME_AbsoluteNBO timestamp;
 
   /**
    * Hash of the public key of the pseudonym of the sender of the message
-   * TBD: Should be all zeros for anonymous.
+   * Should be all zeros for anonymous.
    */
   GNUNET_HashCode sender;
 
Index: src/chat/Makefile.am
===================================================================
--- src/chat/Makefile.am	(revision 14325)
+++ src/chat/Makefile.am	(working copy)
@@ -37,3 +37,85 @@
   $(top_builddir)/src/chat/libgnunetchat.la \
   $(top_builddir)/src/util/libgnunetutil.la \
   $(GN_LIBINTL)
+
+check_PROGRAMS = \
+ test_chat \
+ test_chat_acknowledgement \
+ test_chat_anonymous \
+ test_chat_authentication \
+ test_chat_p2p \
+ test_chat_acknowledgement_p2p \
+ test_chat_anonymous_p2p \
+ test_chat_authentication_p2p \
+ test_chat_private \
+ test_chat_private_p2p
+
+if !DISABLE_TEST_RUN
+TESTS = $(check_PROGRAMS)
+endif
+
+test_chat_SOURCES = \
+ test_chat.c
+test_chat_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_acknowledgement_SOURCES = \
+ test_chat.c
+test_chat_acknowledgement_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_anonymous_SOURCES = \
+ test_chat.c
+test_chat_anonymous_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_authentication_SOURCES = \
+ test_chat.c
+test_chat_authentication_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_p2p_SOURCES = \
+ test_chat.c
+test_chat_p2p_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_acknowledgement_p2p_SOURCES = \
+ test_chat.c
+test_chat_acknowledgement_p2p_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_anonymous_p2p_SOURCES = \
+ test_chat.c
+test_chat_anonymous_p2p_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_authentication_p2p_SOURCES = \
+ test_chat.c
+test_chat_authentication_p2p_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_private_SOURCES = \
+ test_chat_private.c
+test_chat_private_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+test_chat_private_p2p_SOURCES = \
+ test_chat_private.c
+test_chat_private_p2p_LDADD = \
+  $(top_builddir)/src/chat/libgnunetchat.la \
+  $(top_builddir)/src/util/libgnunetutil.la
+
+EXTRA_DIST = \
+  test_chat_data.conf \
+  test_chat_peer1.conf \
+  test_chat_peer2.conf \
+  test_chat_peer3.conf
Index: src/chat/test_chat_private.c
===================================================================
--- src/chat/test_chat_private.c	(revision 0)
+++ src/chat/test_chat_private.c	(revision 0)
@@ -0,0 +1,693 @@
+/*
+     This file is part of GNUnet.
+     (C) 2011 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 chat/test_chat_private.c
+ * @brief testcase for private chatting
+ * @author Vitaly Minko
+ */
+
+#include "platform.h"
+#include "gnunet_crypto_lib.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_arm_service.h"
+#include "gnunet_chat_service.h"
+
+#define VERBOSE GNUNET_NO
+
+#define START_ARM GNUNET_YES
+
+/**
+ * How long until we give up on passing the test?
+ */
+#define KILL_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
+
+/**
+ * How long until we give up on receiving somebody else's private message?
+ */
+#define PM_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
+
+struct PeerContext
+{
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+#if START_ARM
+  struct GNUNET_OS_Process *arm_proc;
+#endif
+};
+
+struct Wanted
+{
+  struct GNUNET_CONTAINER_MetaData *meta;
+
+  GNUNET_HashCode *sender;
+
+  /**
+   * Alternative meta/sender is used when we expect join/leave notification
+   * from two peers and don't know which one will come first.
+   */
+  struct GNUNET_CONTAINER_MetaData *meta2;
+
+  GNUNET_HashCode *sender2;
+
+  char *msg;
+
+  const char *me;
+
+  enum GNUNET_CHAT_MsgOptions opt;
+
+  struct GNUNET_TIME_Absolute timestamp;
+
+  GNUNET_SCHEDULER_Task next_task;
+
+  void *next_task_cls;
+
+};
+
+static struct PeerContext p1;
+
+static struct PeerContext p2;
+
+static struct PeerContext p3;
+
+static GNUNET_HashCode alice;
+
+static GNUNET_HashCode bob;
+
+static GNUNET_HashCode carol;
+
+static struct GNUNET_CHAT_Room *alice_room;
+
+static struct GNUNET_CHAT_Room *bob_room;
+
+static struct GNUNET_CHAT_Room *carol_room;
+
+static struct GNUNET_CONTAINER_MetaData *alice_meta;
+
+static struct GNUNET_CONTAINER_MetaData *bob_meta;
+
+static struct GNUNET_CONTAINER_MetaData *carol_meta;
+
+static struct Wanted alice_wanted;
+
+static struct Wanted bob_wanted;
+
+static struct Wanted carol_wanted;
+
+static GNUNET_SCHEDULER_TaskIdentifier kill_task;
+
+static GNUNET_SCHEDULER_TaskIdentifier finish_task;
+
+static GNUNET_SCHEDULER_TaskIdentifier wait_task;
+
+static int err;
+
+static int alice_ready;
+
+static int bob_ready;
+
+static int is_p2p;
+
+struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *bob_public_key = NULL;
+
+
+static void
+setup_peer (struct PeerContext *p, const char *cfgname)
+{
+  p->cfg = GNUNET_CONFIGURATION_create ();
+#if START_ARM
+  p->arm_proc = GNUNET_OS_start_process (NULL, NULL, "gnunet-service-arm",
+                                        "gnunet-service-arm",
+#if VERBOSE
+                                        "-L", "DEBUG",
+#endif
+                                        "-c", cfgname, NULL);
+#endif
+  GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname));
+}
+
+
+static void
+stop_arm (struct PeerContext *p)
+{
+#if START_ARM
+  if (0 != GNUNET_OS_process_kill (p->arm_proc, SIGTERM))
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
+  if (GNUNET_OS_process_wait(p->arm_proc) != GNUNET_OK)
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "ARM process %u stopped\n", GNUNET_OS_process_get_pid (p->arm_proc));
+  GNUNET_OS_process_close (p->arm_proc);
+  p->arm_proc = NULL;
+#endif
+  GNUNET_CONFIGURATION_destroy (p->cfg);
+}
+
+
+static void
+abort_test (void *cls,
+	    const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  if (alice_room != NULL)
+    {
+      GNUNET_CHAT_leave_room (alice_room);
+      alice_room = NULL;
+    }
+  if (bob_room != NULL)
+    {
+      GNUNET_CHAT_leave_room (bob_room);
+      bob_room = NULL;
+    }
+  if (carol_room != NULL)
+    {
+      GNUNET_CHAT_leave_room (carol_room);
+      carol_room = NULL;
+    }
+  err = 1;
+}
+
+
+static void
+timeout_kill (void *cls,
+	      const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Timed out, stopping the test.\n");
+#endif
+  kill_task = GNUNET_SCHEDULER_NO_TASK;
+  if (wait_task != GNUNET_SCHEDULER_NO_TASK)
+    {
+      GNUNET_SCHEDULER_cancel (wait_task);
+      wait_task = GNUNET_SCHEDULER_NO_TASK;
+    }
+  GNUNET_SCHEDULER_add_continuation (&abort_test, NULL,
+				     GNUNET_SCHEDULER_REASON_PREREQ_DONE);
+}
+
+
+static int
+join_cb (void *cls)
+{
+  struct Wanted *want = cls;
+
+#if VERBOSE
+  printf ("%s has joined\n", want->me);
+#endif
+  if (NULL != want->next_task)
+    GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+  return GNUNET_OK;
+}
+
+
+static int
+member_list_cb (void *cls,
+		const struct GNUNET_CONTAINER_MetaData *member_info,
+		const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
+		enum GNUNET_CHAT_MsgOptions options)
+{
+  struct Wanted *want = cls;
+  GNUNET_HashCode sender;
+
+#if VERBOSE
+  printf ("%s - told that %s has %s\n",
+           want->me,
+           member_info == NULL ? NULL
+           : GNUNET_CONTAINER_meta_data_get_by_type (member_info,
+						     EXTRACTOR_METATYPE_TITLE),
+	   member_info == NULL ? "left" : "joined");
+#endif
+  GNUNET_CRYPTO_hash (member_id,
+		      sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
+		      &sender);
+  /* entertain both primary and an alternative sender/meta */
+  if (((0 == memcmp (&sender, want->sender, sizeof (GNUNET_HashCode))) ||
+       ((want->sender2 != NULL) &&
+	(0 == memcmp (&sender, want->sender2, sizeof (GNUNET_HashCode))))) &&
+      (((member_info == NULL) && (want->meta == NULL)) ||
+       ((member_info != NULL) &&
+	(((want->meta != NULL) &&
+	  GNUNET_CONTAINER_meta_data_test_equal (member_info,
+						 want->meta)) ||
+	 ((want->meta2 != NULL) &&
+	  GNUNET_CONTAINER_meta_data_test_equal (member_info,
+						 want->meta2))))) &&
+      (options == want->opt))
+    {
+      /* remember Bob's public key, we need it to send private message */
+      if (NULL == bob_public_key &&
+	  (0 == memcmp (&bob, want->sender, sizeof (GNUNET_HashCode))))
+	bob_public_key =
+	  GNUNET_memdup (member_id,
+			 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded));
+      if (want->sender2 != NULL)
+	{
+	  /* flush alternative sender */
+	  if (0 == memcmp (&sender, want->sender, sizeof (GNUNET_HashCode)))
+	    {
+	      want->sender = want->sender2;
+	      want->meta = want->meta2;
+	    }
+	  want->sender2 = NULL;
+	  want->meta2 = NULL;
+	}
+      else
+	if (NULL != want->next_task)
+	  GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+    }
+  else
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (&abort_test, NULL);
+    }
+  return GNUNET_OK;
+}
+
+
+static int
+receive_cb (void *cls,
+	    struct GNUNET_CHAT_Room *room,
+	    const GNUNET_HashCode *sender,
+	    const struct GNUNET_CONTAINER_MetaData *meta,
+	    const char *message,
+	    struct GNUNET_TIME_Absolute timestamp,
+	    enum GNUNET_CHAT_MsgOptions options)
+{
+  struct Wanted *want = cls;
+
+#if VERBOSE
+  printf ("%s - told that %s said '%s'\n",
+	  want->me,
+	  meta == NULL ? NULL
+	  : GNUNET_CONTAINER_meta_data_get_by_type (meta,
+						    EXTRACTOR_METATYPE_TITLE),
+	  message);
+#endif
+
+  if ((want->msg != NULL) && (0 == strcmp (message, want->msg)) &&
+      (((sender == NULL) && (want->sender == NULL)) ||
+       ((sender != NULL) && (want->sender != NULL) &&
+	(0 == memcmp (sender, want->sender,
+		      sizeof (GNUNET_HashCode))))) &&
+      (GNUNET_CONTAINER_meta_data_test_equal (meta, want->meta)) &&
+      (options == want->opt) &&
+      /* Not == since the library sets the actual timestamp, so it may be
+       * slightly greater
+       */
+      (timestamp.abs_value >= want->timestamp.abs_value))
+    {
+      if (NULL != want->next_task)
+	GNUNET_SCHEDULER_add_now (want->next_task, want->next_task_cls);
+    }
+  else
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_cancel (finish_task);
+      finish_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (&abort_test, NULL);
+    }
+  return GNUNET_OK;
+}
+
+
+static void
+wait_until_all_ready (void *cls,
+		      const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  GNUNET_SCHEDULER_Task task = cls;
+
+#if VERBOSE
+  printf ("Waiting...\n");
+#endif
+  if (alice_ready && bob_ready)
+    {
+      wait_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_SCHEDULER_add_now (task, NULL);
+    }
+  else
+    wait_task =
+      GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+								   5000),
+				    &wait_until_all_ready,
+				    task);
+}
+
+
+static void
+set_alice_ready (void *cls,
+	   const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  alice_ready = GNUNET_YES;
+}
+
+
+static void
+set_bob_ready (void *cls,
+	   const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  bob_ready = GNUNET_YES;
+}
+
+
+static void
+disconnect_alice (void *cls,
+		  const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Alice is leaving.\n");
+#endif
+  if (is_p2p)
+    stop_arm (&p2);
+  GNUNET_CHAT_leave_room (alice_room);
+  alice_room = NULL;
+  GNUNET_SCHEDULER_cancel (kill_task);
+  kill_task = GNUNET_SCHEDULER_NO_TASK;
+}
+
+
+static void
+disconnect_bob (void *cls,
+		const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Bod is leaving.\n");
+#endif
+  if (is_p2p)
+    stop_arm (&p3);
+  alice_wanted.meta = NULL;
+  alice_wanted.sender = &bob;
+  alice_wanted.msg = NULL;
+  alice_wanted.opt = 0;
+  alice_wanted.next_task = &disconnect_alice;
+  alice_wanted.next_task_cls = NULL;
+  GNUNET_CHAT_leave_room (bob_room);
+  bob_room = NULL;
+}
+
+
+static void
+disconnect_carol (void *cls,
+		  const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Carol is leaving.\n");
+#endif
+  alice_wanted.meta = NULL;
+  alice_wanted.sender = &carol;
+  alice_wanted.msg = NULL;
+  alice_wanted.opt = 0;
+  alice_wanted.next_task = &set_alice_ready;
+  alice_wanted.next_task_cls = NULL;
+  alice_ready = GNUNET_NO;
+  bob_wanted.meta = NULL;
+  bob_wanted.sender = &carol;
+  bob_wanted.msg = NULL;
+  bob_wanted.opt = 0;
+  bob_wanted.next_task = &wait_until_all_ready;
+  bob_wanted.next_task_cls = &disconnect_bob;
+  bob_ready = GNUNET_YES;
+  GNUNET_CHAT_leave_room (carol_room);
+  carol_room = NULL;
+}
+
+
+static void
+send_from_alice_to_bob (void *cls,
+			const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  uint32_t seq;
+
+#if VERBOSE
+  printf ("Alice says 'Hi!' to Bob\n");
+#endif
+  alice_ready = GNUNET_YES;
+  bob_ready = GNUNET_NO;
+  bob_wanted.meta = alice_meta;
+  bob_wanted.sender = &alice;
+  bob_wanted.msg = "Hi Bob!";
+  bob_wanted.opt = GNUNET_CHAT_MSG_PRIVATE;
+  bob_wanted.next_task = &set_bob_ready;
+  bob_wanted.next_task_cls = NULL;
+  /* Carol should not receive this message */
+  carol_wanted.meta = NULL;
+  carol_wanted.sender = NULL;
+  carol_wanted.msg = NULL;
+  carol_wanted.opt = 0;
+  carol_wanted.next_task = NULL;
+  carol_wanted.next_task_cls = NULL;
+  GNUNET_CHAT_send_message (alice_room,
+			    "Hi Bob!",
+			    GNUNET_CHAT_MSG_PRIVATE,
+			    bob_public_key, &seq);
+  finish_task = GNUNET_SCHEDULER_add_delayed (PM_TIMEOUT,
+					      &wait_until_all_ready,
+					      &disconnect_carol);
+}
+
+
+static void
+prepare_bob_for_alice_task (void *cls,
+			    const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  bob_wanted.meta = alice_meta;
+  bob_wanted.sender = &alice;
+  bob_wanted.msg = NULL;
+  bob_wanted.opt = -1;
+  bob_wanted.next_task = &set_bob_ready;
+  bob_wanted.next_task_cls = NULL;
+}
+
+
+static void
+prepare_carol_for_alice_and_bob_task (void *cls,
+				      const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  carol_wanted.meta = alice_meta;
+  carol_wanted.sender = &alice;
+  /* set alternative meta/sender since we don't know from which peer
+     notification will come first */
+  carol_wanted.meta2 = bob_meta;
+  carol_wanted.sender2 = &bob;
+  carol_wanted.msg = NULL;
+  carol_wanted.opt = -1;
+  carol_wanted.next_task = &wait_until_all_ready;
+  carol_wanted.next_task_cls = &send_from_alice_to_bob;
+}
+
+
+static void
+join_carol_task (void *cls,
+		 const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Carol joining\n");
+#endif
+  alice_wanted.meta = carol_meta;
+  alice_wanted.sender = &carol;
+  alice_wanted.msg = NULL;
+  alice_wanted.opt = -1;
+  alice_wanted.next_task = &set_alice_ready;
+  alice_wanted.next_task_cls = NULL;
+  alice_ready = GNUNET_NO;
+  bob_wanted.meta = carol_meta;
+  bob_wanted.sender = &carol;
+  bob_wanted.msg = NULL;
+  bob_wanted.opt = -1;
+  bob_wanted.next_task = &set_bob_ready;
+  bob_wanted.next_task_cls = NULL;
+  bob_ready = GNUNET_NO;
+  carol_wanted.next_task = &prepare_carol_for_alice_and_bob_task;
+  carol_wanted.next_task_cls = NULL;
+  carol_room =
+    GNUNET_CHAT_join_room (is_p2p ? p3.cfg : p1.cfg, "carol", carol_meta,
+			   "test", -1,
+			   &join_cb, &carol_wanted,
+			   &receive_cb, &carol_wanted,
+			   &member_list_cb, &carol_wanted,
+			   NULL, NULL, &carol);
+  if (NULL == carol_room)
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_CHAT_leave_room (alice_room);
+      alice_room = NULL;
+      GNUNET_CHAT_leave_room (bob_room);
+      bob_room = NULL;
+      err = 1;
+    }
+}
+
+
+static void
+join_bob_task (void *cls,
+	       const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Bob joining\n");
+#endif
+  alice_wanted.meta = bob_meta;
+  alice_wanted.sender = &bob;
+  alice_wanted.msg = NULL;
+  alice_wanted.opt = -1;
+  alice_wanted.next_task = &wait_until_all_ready;
+  alice_wanted.next_task_cls = &join_carol_task;
+  alice_ready = GNUNET_YES;
+  bob_wanted.next_task = &prepare_bob_for_alice_task;
+  bob_wanted.next_task_cls = NULL;
+  bob_ready = GNUNET_NO;
+  bob_room =
+    GNUNET_CHAT_join_room (is_p2p ? p2.cfg : p1.cfg, "bob", bob_meta,
+			   "test", -1,
+			   &join_cb, &bob_wanted,
+			   &receive_cb, &bob_wanted,
+			   &member_list_cb, &bob_wanted,
+			   NULL, NULL, &bob);
+  if (NULL == bob_room)
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      GNUNET_CHAT_leave_room (alice_room);
+      alice_room = NULL;
+      err = 1;
+    }
+}
+
+
+static void
+join_alice_task (void *cls,
+		 const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+#if VERBOSE
+  printf ("Alice joining\n");
+#endif
+  alice_wanted.next_task = &join_bob_task;
+  alice_wanted.next_task_cls = NULL;
+  alice_room =
+    GNUNET_CHAT_join_room (p1.cfg, "alice", alice_meta,
+			   "test", -1,
+			   &join_cb, &alice_wanted,
+			   &receive_cb, &alice_wanted,
+			   &member_list_cb, &alice_wanted,
+			   NULL, NULL, &alice);
+  if (NULL == alice_room)
+    {
+      GNUNET_SCHEDULER_cancel (kill_task);
+      kill_task = GNUNET_SCHEDULER_NO_TASK;
+      err = 1;
+    }
+}
+
+
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  if (is_p2p)
+    {
+      setup_peer (&p1, "test_chat_peer1.conf");
+      setup_peer (&p2, "test_chat_peer2.conf");
+      setup_peer (&p3, "test_chat_peer3.conf");
+    }
+  else
+    setup_peer (&p1, "test_chat_data.conf");
+
+  memset (&alice_wanted, 0, sizeof (struct Wanted));
+  memset (&bob_wanted, 0, sizeof (struct Wanted));
+  memset (&carol_wanted, 0, sizeof (struct Wanted));
+  alice_wanted.me = "Alice";
+  bob_wanted.me = "Bob";
+  carol_wanted.me = "Carol";
+  alice_meta = GNUNET_CONTAINER_meta_data_create ();
+  GNUNET_CONTAINER_meta_data_insert (alice_meta,
+				     "<gnunet>",
+				     EXTRACTOR_METATYPE_TITLE,
+				     EXTRACTOR_METAFORMAT_UTF8,
+				     "text/plain",
+				     "Alice",
+				     strlen("Alice")+1);
+  bob_meta = GNUNET_CONTAINER_meta_data_create ();
+  GNUNET_CONTAINER_meta_data_insert (bob_meta,
+				     "<gnunet>",
+				     EXTRACTOR_METATYPE_TITLE,
+				     EXTRACTOR_METAFORMAT_UTF8,
+				     "text/plain",
+				     "Bob",
+				     strlen("Bob")+1);
+  carol_meta = GNUNET_CONTAINER_meta_data_create ();
+  GNUNET_CONTAINER_meta_data_insert (carol_meta,
+				     "<gnunet>",
+				     EXTRACTOR_METATYPE_TITLE,
+				     EXTRACTOR_METAFORMAT_UTF8,
+				     "text/plain",
+				     "Carol",
+				     strlen("Carol")+1);
+  kill_task = GNUNET_SCHEDULER_add_delayed (KILL_TIMEOUT, &timeout_kill, NULL);
+  GNUNET_SCHEDULER_add_now (&join_alice_task, NULL);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  char *const argvx[] = { 
+    "test-chat",
+    "-c",
+    "test_chat_data.conf",
+#if VERBOSE
+    "-L", "DEBUG",
+#endif
+    NULL
+  };
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_OPTION_END
+  };
+
+  GNUNET_log_setup ("test_chat", 
+#if VERBOSE
+		    "DEBUG",
+#else
+		    "WARNING",
+#endif
+		    NULL);
+  if (strstr(argv[0], "p2p") != NULL)
+    {
+      is_p2p = GNUNET_YES;
+    }
+  GNUNET_PROGRAM_run ((sizeof (argvx) / sizeof (char *)) - 1,
+                      argvx, "test-chat",
+		      "nohelp", options, &run, NULL);
+  stop_arm (&p1);
+  GNUNET_CONTAINER_meta_data_destroy (alice_meta);
+  GNUNET_CONTAINER_meta_data_destroy (bob_meta);
+  GNUNET_CONTAINER_meta_data_destroy (carol_meta);
+  if (is_p2p)
+    {
+      GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat-peer-1/");
+      GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat-peer-2/");
+      GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat-peer-3/");
+    }
+  else
+    GNUNET_DISK_directory_remove ("/tmp/gnunet-test-chat/");
+  return err;
+}
+
+/* end of test_chat_private.c */
Index: src/chat/test_chat_data.conf
===================================================================
--- src/chat/test_chat_data.conf	(revision 0)
+++ src/chat/test_chat_data.conf	(revision 0)
@@ -0,0 +1,37 @@
+[PATHS]
+SERVICEHOME = /tmp/gnunet-test-chat/
+DEFAULTCONFIG = test_chat_data.conf
+
+[gnunetd]
+HOSTKEY = $SERVICEHOME/.hostkey
+
+[resolver]
+PORT = 42464
+HOSTNAME = localhost
+
+[transport]
+PORT = 42465
+PLUGINS = 
+
+[arm]
+PORT = 42466
+HOSTNAME = localhost
+DEFAULTSERVICES = core chat
+
+[peerinfo]
+PORT = 42469
+HOSTNAME = localhost
+
+[core]
+PORT = 42470
+HOSTNAME = localhost
+
+[chat]
+PORT = 42471
+HOSTNAME = localhost
+HOME = $SERVICEHOME
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-service-chat
+
+[testing]
+WEAKRANDOM = YES
Index: src/chat/gnunet-service-chat.c
===================================================================
--- src/chat/gnunet-service-chat.c	(revision 14325)
+++ src/chat/gnunet-service-chat.c	(working copy)
@@ -33,9 +33,10 @@
 #include "gnunet_signatures.h"
 #include "chat.h"
 
-#define DEBUG_CHAT_SERVICE GNUNET_YES
+#define DEBUG_CHAT_SERVICE GNUNET_NO
 #define MAX_TRANSMIT_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
 #define QUEUE_SIZE 16
+#define MAX_ANONYMOUS_MSG_LIST_LENGTH 16
 
 
 /**
@@ -93,6 +94,20 @@
 
 };
 
+/**
+ * Linked list of recent anonymous messages.
+ */
+struct AnonymousMessage
+{
+  struct AnonymousMessage *next;
+
+  /**
+   * Hash of the message.
+   */
+  GNUNET_HashCode hash;
+
+};
+
 
 /**
  * Handle to the core service (NULL until we've connected to it).
@@ -119,6 +134,56 @@
  */
 struct GNUNET_SERVER_NotificationContext *nc = NULL;
 
+/**
+ * Head of the list of recent anonymous messages.
+ */
+static struct AnonymousMessage *anonymous_list_head = NULL;
+ 
+
+static void
+remember_anonymous_message (const struct P2PReceiveNotificationMessage *p2p_rnmsg)
+{
+  static GNUNET_HashCode hash;
+  struct AnonymousMessage *anon_msg;
+  struct AnonymousMessage *prev;
+  int anon_list_len;
+
+  GNUNET_CRYPTO_hash (p2p_rnmsg, ntohs (p2p_rnmsg->header.size), &hash);
+  anon_msg = GNUNET_malloc (sizeof (struct AnonymousMessage));
+  anon_msg->hash = hash;
+  anon_msg->next = anonymous_list_head;
+  anonymous_list_head = anon_msg;
+  anon_list_len = 1;
+  prev = NULL;
+  while ((NULL != anon_msg->next))
+    {
+      prev = anon_msg;
+      anon_msg = anon_msg->next;
+      anon_list_len++;
+    }
+  if (anon_list_len == MAX_ANONYMOUS_MSG_LIST_LENGTH)
+    {
+      GNUNET_free (anon_msg);
+      if (NULL != prev)
+	prev->next = NULL;
+    }
+}
+
+
+static int
+lookup_anonymous_message (const struct P2PReceiveNotificationMessage *p2p_rnmsg)
+{
+  static GNUNET_HashCode hash;
+  struct AnonymousMessage *anon_msg;
+
+  GNUNET_CRYPTO_hash (p2p_rnmsg, ntohs (p2p_rnmsg->header.size), &hash);
+  anon_msg = anonymous_list_head;
+  while ((NULL != anon_msg) &&
+	 (0 != memcmp (&anon_msg->hash, &hash, sizeof (GNUNET_HashCode))))
+    anon_msg = anon_msg->next;
+  return (NULL != anon_msg);
+}
+
 
 /**
  * Transmit a message notification to the peer.
@@ -141,9 +206,17 @@
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
 	      "Transmitting P2P message notification\n");
 #endif
+  if (buf == NULL)
+    {
+      /* client disconnected */
+#if DEBUG_CHAT_SERVICE
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+		  "Buffer is NULL, dropping the message\n");
+#endif
+      return 0;
+    }
   msg_size = ntohs (my_msg->header.size);
   GNUNET_assert (size >= msg_size);
-  GNUNET_assert (NULL != buf);
   memcpy (m, my_msg, msg_size);
   GNUNET_free (my_msg);
   return msg_size;
@@ -205,8 +278,10 @@
   struct GNUNET_CRYPTO_AesSessionKey key;
   char encrypted_msg[MAX_MESSAGE_LENGTH];
   const char *room;
+  size_t room_len;
   int msg_len;
-  int priv_msg;
+  int is_priv;
+  int is_anon;
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client sent a chat message\n");
   if (ntohs (message->size) <= sizeof (struct TransmitRequestMessage))
@@ -218,8 +293,8 @@
     }
   trmsg = (const struct TransmitRequestMessage *) message;
   msg_len = ntohs (trmsg->header.size) - sizeof (struct TransmitRequestMessage);
-  priv_msg = (ntohl (trmsg->msg_options) & GNUNET_CHAT_MSG_PRIVATE) != 0;
-  if (priv_msg)
+  is_priv = (0 != (ntohl (trmsg->msg_options) & GNUNET_CHAT_MSG_PRIVATE));
+  if (is_priv)
     {
 #if DEBUG_CHAT_SERVICE
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Encrypting the message text\n");
@@ -244,7 +319,7 @@
 			      msg_len);
   rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_MESSAGE_NOTIFICATION);
   rnmsg->msg_options = trmsg->msg_options;
-  rnmsg->sequence_number = trmsg->sequence_number;
+  rnmsg->timestamp = trmsg->timestamp;
   pos = client_list_head;
   while ((NULL != pos) && (pos->client != client))
     pos = pos->next;
@@ -260,11 +335,18 @@
     }
   room = pos->room;
   pos->msg_sequence_number = ntohl (trmsg->sequence_number);
-  if (0 == (ntohl (trmsg->msg_options) & GNUNET_CHAT_MSG_ANONYMOUS))
-    rnmsg->sender = pos->id;
-  else
+  is_anon = (0 != (ntohl (trmsg->msg_options) & GNUNET_CHAT_MSG_ANONYMOUS));
+  if (is_anon)
+    {
     memset (&rnmsg->sender, 0, sizeof (GNUNET_HashCode));
-  if (priv_msg)
+      rnmsg->sequence_number = 0;
+    }
+  else
+    {
+      rnmsg->sender = pos->id;
+      rnmsg->sequence_number = trmsg->sequence_number;
+    }
+  if (is_priv)
     {
 #if DEBUG_CHAT_SERVICE
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -323,7 +405,7 @@
 	  (NULL != pos->client) &&
 	  (pos->client != client))
 	{
-	  if (((!priv_msg) ||
+	  if (((!is_priv) ||
 	       (0 == memcmp (&trmsg->target,
 			     &pos->id,
 			     sizeof (GNUNET_HashCode)))) &&
@@ -341,16 +423,25 @@
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
 	      "Broadcasting message to neighbour peers\n");
 #endif
+  if (is_anon)
+    {
+      room_len = strlen (room);
+      p2p_rnmsg = GNUNET_malloc (sizeof (struct P2PReceiveNotificationMessage) +
+				 msg_len + room_len);
+      p2p_rnmsg->header.size =
+	htons (sizeof (struct P2PReceiveNotificationMessage) + msg_len +
+	       room_len);
+      p2p_rnmsg->room_name_len = htons (room_len);
+      memcpy ((char *) &p2p_rnmsg[1], room, room_len);
+      memcpy ((char *) &p2p_rnmsg[1] + room_len, &trmsg[1], msg_len);
+    }
+  else
+    {
   p2p_rnmsg = GNUNET_malloc (sizeof (struct P2PReceiveNotificationMessage) +
 			     msg_len);
-  p2p_rnmsg->header.size = htons (sizeof (struct P2PReceiveNotificationMessage) +
-				  msg_len);
-  p2p_rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_MESSAGE_NOTIFICATION);
-  p2p_rnmsg->msg_options = trmsg->msg_options;
-  p2p_rnmsg->sequence_number = trmsg->sequence_number;
-  memcpy (&p2p_rnmsg->sender, &rnmsg->sender, sizeof (GNUNET_HashCode));
-  p2p_rnmsg->target = trmsg->target;
-  if (priv_msg)
+      p2p_rnmsg->header.size =
+	htons (sizeof (struct P2PReceiveNotificationMessage) + msg_len);
+      if (is_priv)
     {
       memcpy (&p2p_rnmsg[1], encrypted_msg, msg_len);
       memcpy (&p2p_rnmsg->encrypted_key,
@@ -358,9 +449,17 @@
 	      sizeof (struct GNUNET_CRYPTO_RsaEncryptedData));
     }
   else
-    {
       memcpy (&p2p_rnmsg[1], &trmsg[1], msg_len);
     }
+  p2p_rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_MESSAGE_NOTIFICATION);
+  p2p_rnmsg->msg_options = trmsg->msg_options;
+  p2p_rnmsg->sequence_number = trmsg->sequence_number;
+  p2p_rnmsg->timestamp = trmsg->timestamp;
+  p2p_rnmsg->reserved = 0;
+  p2p_rnmsg->sender = rnmsg->sender;
+  p2p_rnmsg->target = trmsg->target;
+  if (is_anon)
+    remember_anonymous_message (p2p_rnmsg);
   GNUNET_CORE_iterate_peers (cfg,
 			     &send_message_noficiation,
 			     p2p_rnmsg);
@@ -591,9 +690,17 @@
 	      "Transmitting P2P confirmation receipt to '%s'\n",
 	      GNUNET_h2s (&receipt->target));
 #endif
+  if (buf == NULL)
+    {
+      /* client disconnected */
+#if DEBUG_CHAT_SERVICE
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+		  "Buffer is NULL, dropping the message\n");
+#endif
+      return 0;
+    }
   msg_size = sizeof (struct P2PConfirmationReceiptMessage);
   GNUNET_assert (size >= msg_size);
-  GNUNET_assert (NULL != buf);
   memcpy (buf, receipt, msg_size);
   GNUNET_free (receipt);
   return msg_size;
@@ -765,9 +872,17 @@
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
 	      "Transmitting P2P leave notification\n");
 #endif
+  if (buf == NULL)
+    {
+      /* client disconnected */
+#if DEBUG_CHAT_SERVICE
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+		  "Buffer is NULL, dropping the message\n");
+#endif
+      return 0;
+    }
   msg_size = sizeof (struct P2PLeaveNotificationMessage);
   GNUNET_assert (size >= msg_size);
-  GNUNET_assert (NULL != buf);
   m = buf;
   m->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_LEAVE_NOTIFICATION);
   m->header.size = htons (msg_size);
@@ -787,7 +902,6 @@
 			const struct GNUNET_TRANSPORT_ATS_Information *atsi)
 {
   struct ChatClient *entry = cls;
-  struct GNUNET_CORE_TransmitHandle *th;
   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key;
   size_t msg_size;
 
@@ -806,14 +920,15 @@
       msg_size = sizeof (struct P2PLeaveNotificationMessage);
       public_key = GNUNET_memdup (&entry->public_key,
 				  sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded));
-      th = GNUNET_CORE_notify_transmit_ready (core,
+      if (NULL == GNUNET_CORE_notify_transmit_ready (core,
 					      1,
 					      MAX_TRANSMIT_DELAY,
 					      peer,
 					      msg_size,
 					      &transmit_leave_notification_to_peer,
-					      public_key);
-      GNUNET_assert (NULL != th);
+						     public_key))
+	GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+		    _("Failed to queue a leave notification\n"));
     }
 }
 
@@ -1108,8 +1223,12 @@
   struct ChatClient *sender;
   struct ChatClient *pos;
   static GNUNET_HashCode all_zeros;
-  int priv_msg;
+  int is_priv;
+  int is_anon;
   uint16_t msg_len;
+  uint16_t room_name_len;
+  char *room_name = NULL;
+  char *text;
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got P2P message notification\n");
   if (ntohs (message->size) <= sizeof (struct P2PReceiveNotificationMessage))
@@ -1119,6 +1238,37 @@
       return GNUNET_SYSERR;
     }
   p2p_rnmsg = (const struct P2PReceiveNotificationMessage *) message;
+  msg_len = ntohs (p2p_rnmsg->header.size) -
+    sizeof (struct P2PReceiveNotificationMessage);
+
+  is_anon = (0 != (ntohl (p2p_rnmsg->msg_options) & GNUNET_CHAT_MSG_ANONYMOUS));
+  if (is_anon)
+    {
+      room_name_len = ntohs (p2p_rnmsg->room_name_len);
+      if (msg_len <= room_name_len)
+	{
+	  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+		      "Malformed message: wrong length of the room name\n");
+	  GNUNET_break_op (0);
+	  return GNUNET_SYSERR;
+	}
+      msg_len -= room_name_len;
+      if (lookup_anonymous_message (p2p_rnmsg))
+	{
+#if DEBUG_CHAT_SERVICE
+	  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+		      "This anonymous message has already been handled.");
+#endif
+	  return GNUNET_OK;
+	}
+      remember_anonymous_message (p2p_rnmsg);
+      room_name = GNUNET_malloc (room_name_len + 1);
+      memcpy (room_name, (char *) &p2p_rnmsg[1], room_name_len);
+      room_name[room_name_len] = '\0';
+      text = (char *) &p2p_rnmsg[1] + room_name_len;
+    }
+  else
+    {
   sender = client_list_head;
   while ((NULL != sender) &&
 	 (0 != memcmp (&sender->id,
@@ -1127,10 +1277,13 @@
     sender = sender->next;
   if (NULL == sender)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+	  /* not an error since the sender may have left before we got the
+	     message */
+#if DEBUG_CHAT_SERVICE
+	  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
 		  "Unknown source. Rejecting the message\n");
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
+#endif
+	  return GNUNET_OK;
     }
   if (sender->msg_sequence_number >= ntohl (p2p_rnmsg->sequence_number))
     {
@@ -1138,15 +1291,19 @@
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
 		  "This message has already been handled."
 		  " Sequence numbers (msg/sender): %u/%u\n",
-		  ntohl (p2p_rnmsg->sequence_number), sender->msg_sequence_number);
+		      ntohl (p2p_rnmsg->sequence_number),
+		      sender->msg_sequence_number);
 #endif
       return GNUNET_OK;
     }
   sender->msg_sequence_number = ntohl (p2p_rnmsg->sequence_number);
-  msg_len = ntohs (p2p_rnmsg->header.size) -
-    sizeof (struct P2PReceiveNotificationMessage);
+      room_name = sender->room;
+      text = (char *) &p2p_rnmsg[1];
+    }
+
 #if DEBUG_CHAT_SERVICE
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending message to local room members\n");
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+	      "Sending message to local room members\n");
 #endif
   rnmsg = GNUNET_malloc (sizeof (struct ReceiveNotificationMessage) + msg_len);
   rnmsg->header.size = htons (sizeof (struct ReceiveNotificationMessage) +
@@ -1154,21 +1311,22 @@
   rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_MESSAGE_NOTIFICATION);
   rnmsg->msg_options = p2p_rnmsg->msg_options;
   rnmsg->sequence_number = p2p_rnmsg->sequence_number;
-  priv_msg = (0 != memcmp (&all_zeros,
+  rnmsg->timestamp = p2p_rnmsg->timestamp;
+  is_priv = (0 != memcmp (&all_zeros,
 			   &p2p_rnmsg->target, sizeof (GNUNET_HashCode)));
-  if (priv_msg)
+  if (is_priv)
     memcpy (&rnmsg->encrypted_key,
 	    &p2p_rnmsg->encrypted_key,
 	    sizeof (struct GNUNET_CRYPTO_RsaEncryptedData));
-  memcpy (&rnmsg->sender, &p2p_rnmsg->sender, sizeof (GNUNET_HashCode));
-  memcpy (&rnmsg[1], &p2p_rnmsg[1], msg_len);
+  rnmsg->sender = p2p_rnmsg->sender;
+  memcpy (&rnmsg[1], text, msg_len);
   pos = client_list_head;
   while (NULL != pos)
     {
-      if ((0 == strcmp (sender->room, pos->room)) &&
+      if ((0 == strcmp (room_name, pos->room)) &&
 	  (NULL != pos->client))
 	{
-	  if (((!priv_msg) ||
+	  if (((!is_priv) ||
 	       (0 == memcmp (&p2p_rnmsg->target,
 			     &pos->id,
 			     sizeof (GNUNET_HashCode)))) &&
@@ -1182,6 +1340,8 @@
 	}
       pos = pos->next;
     }
+  if (is_anon)
+    GNUNET_free (room_name);
 #if DEBUG_CHAT_SERVICE
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
 	      "Broadcasting message notification to neighbour peers\n");
@@ -1441,6 +1601,9 @@
 cleanup_task (void *cls,
 	      const struct GNUNET_SCHEDULER_TaskContext *tc)
 {
+  struct AnonymousMessage *next_msg;
+  struct ChatClient *next_client;
+
   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Cleaning up\n");
   if (NULL != core)
     {
@@ -1452,6 +1615,20 @@
       GNUNET_SERVER_notification_context_destroy (nc);
       nc = NULL;
     }
+  while (NULL != client_list_head)
+    {
+      next_client = client_list_head->next;
+      GNUNET_free (client_list_head->room);
+      GNUNET_free_non_null (client_list_head->member_info);
+      GNUNET_free (client_list_head);
+      client_list_head = next_client;
+    }
+  while (NULL != anonymous_list_head)
+    {
+      next_msg = anonymous_list_head->next;
+      GNUNET_free (anonymous_list_head);
+      anonymous_list_head = next_msg;
+    }
 }
 
 
Index: src/chat/gnunet-chat.c
===================================================================
--- src/chat/gnunet-chat.c	(revision 14325)
+++ src/chat/gnunet-chat.c	(working copy)
@@ -78,6 +78,20 @@
 
 
 /**
+ * Callback used for notification that we have joined the room.
+ *
+ * @param cls closure
+ * @return GNUNET_OK
+ */
+static int
+join_cb (void *cls)
+{
+  fprintf (stdout, _("Joined\n"));
+  return GNUNET_OK;
+}
+
+
+/**
  * Callback used for notification about incoming messages.
  *
  * @param cls closure, NULL
@@ -93,11 +107,13 @@
 receive_cb (void *cls,
 	    struct GNUNET_CHAT_Room *room,
 	    const GNUNET_HashCode *sender,
-	    const struct GNUNET_CONTAINER_MetaData *meta,
+	    const struct GNUNET_CONTAINER_MetaData *member_info,
 	    const char *message,
+	    struct GNUNET_TIME_Absolute timestamp,
 	    enum GNUNET_CHAT_MsgOptions options)
 {
   char *nick;
+  char *time;
   const char *fmt;
 
   if (NULL != sender)
@@ -109,43 +125,43 @@
     {
     case GNUNET_CHAT_MSG_OPTION_NONE:
     case GNUNET_CHAT_MSG_ANONYMOUS:
-      fmt = _("`%s' said: %s\n");
+      fmt = _("(%s) `%s' said: %s\n");
       break;
     case GNUNET_CHAT_MSG_PRIVATE:
-      fmt = _("`%s' said to you: %s\n");
+      fmt = _("(%s) `%s' said to you: %s\n");
       break;
     case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS:
-      fmt = _("`%s' said to you: %s\n");
+      fmt = _("(%s) `%s' said to you: %s\n");
       break;
     case GNUNET_CHAT_MSG_AUTHENTICATED:
-      fmt = _("`%s' said for sure: %s\n");
+      fmt = _("(%s) `%s' said for sure: %s\n");
       break;
     case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED:
-      fmt = _("`%s' said to you for sure: %s\n");
+      fmt = _("(%s) `%s' said to you for sure: %s\n");
       break;
     case GNUNET_CHAT_MSG_ACKNOWLEDGED:
-      fmt = _("`%s' was confirmed that you received: %s\n");
+      fmt = _("(%s) `%s' was confirmed that you received: %s\n");
       break;
     case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
-      fmt = _("`%s' was confirmed that you and only you received: %s\n");
+      fmt = _("(%s) `%s' was confirmed that you and only you received: %s\n");
       break;
     case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_ACKNOWLEDGED:
-      fmt = _("`%s' was confirmed that you received from him or her: %s\n");
+      fmt = _("(%s) `%s' was confirmed that you received from him or her: %s\n");
       break;
     case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
-      fmt =
-	_
-	("`%s' was confirmed that you and only you received from him or her: %s\n");
+      fmt = _("(%s) `%s' was confirmed that you and only you received from him or her: %s\n");
       break;
     case GNUNET_CHAT_MSG_OFF_THE_RECORD:
-      fmt = _("`%s' said off the record: %s\n");
+      fmt = _("(%s) `%s' said off the record: %s\n");
       break;
     default:
-      fmt = _("<%s> said using an unknown message type: %s\n");
+      fmt = _("(%s) <%s> said using an unknown message type: %s\n");
       break;
     }
-  fprintf (stdout, fmt, nick, message);
+  time = GNUNET_STRINGS_absolute_time_to_string (timestamp);
+  fprintf (stdout, fmt, time, nick, message);
   GNUNET_free (nick);
+  GNUNET_free (time);
   return GNUNET_OK;
 }
 
@@ -168,9 +184,7 @@
 		 struct GNUNET_CHAT_Room *room,
 		 uint32_t orig_seq_number,
 		 struct GNUNET_TIME_Absolute timestamp,
-		 const GNUNET_HashCode *receiver,
-		 const GNUNET_HashCode *msg_hash,
-		 const struct GNUNET_CRYPTO_RsaSignature *receipt)
+		 const GNUNET_HashCode *receiver)
 {
   char *nick;
 
@@ -248,18 +262,6 @@
 
 
 static int
-do_transmit (const char *msg, const void *xtra)
-{
-  uint32_t seq;
-  GNUNET_CHAT_send_message (room,
-			    msg,
-			    GNUNET_CHAT_MSG_OPTION_NONE,
-			    NULL, &seq);
-  return GNUNET_OK;
-}
-
-
-static int
 do_join (const char *arg, const void *xtra)
 {
   char *my_name;
@@ -276,6 +278,7 @@
 				meta,
 				room_name,
 				-1,
+				&join_cb, NULL,
 				&receive_cb, NULL,
 				&member_list_cb, NULL,
 				&confirmation_cb, NULL, &me);
@@ -285,7 +288,7 @@
       return GNUNET_SYSERR;
     }
   my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
-  fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
+  fprintf (stdout, _("Joining room `%s' as user `%s'...\n"), room_name, my_name);
   GNUNET_free (my_name);
   return GNUNET_OK;
 }
@@ -315,6 +318,7 @@
 				meta,
 				room_name,
 				-1,
+				&join_cb, NULL,
 				&receive_cb, NULL,
 				&member_list_cb, NULL,
 				&confirmation_cb, NULL, &me);
@@ -355,7 +359,19 @@
 
 
 static int
-do_pm (const char *msg, const void *xtra)
+do_send (const char *msg, const void *xtra)
+{
+  uint32_t seq;
+  GNUNET_CHAT_send_message (room,
+			    msg,
+			    GNUNET_CHAT_MSG_OPTION_NONE,
+			    NULL, &seq);
+  return GNUNET_OK;
+}
+
+
+static int
+do_send_pm (const char *msg, const void *xtra)
 {
   char *user;
   GNUNET_HashCode uid;
@@ -404,7 +420,7 @@
 
 
 static int
-do_transmit_sig (const char *msg, const void *xtra)
+do_send_sig (const char *msg, const void *xtra)
 {
   uint32_t seq;
   GNUNET_CHAT_send_message (room,
@@ -416,7 +432,7 @@
 
 
 static int
-do_transmit_ack (const char *msg, const void *xtra)
+do_send_ack (const char *msg, const void *xtra)
 {
   uint32_t seq;
   GNUNET_CHAT_send_message (room,
@@ -428,6 +444,18 @@
 
 
 static int
+do_send_anonymous (const char *msg, const void *xtra)
+{
+  uint32_t seq;
+  GNUNET_CHAT_send_message (room,
+			    msg,
+			    GNUNET_CHAT_MSG_ANONYMOUS,
+			    NULL, &seq);
+  return GNUNET_OK;
+}
+
+
+static int
 do_quit (const char *args, const void *xtra)
 {
   return GNUNET_SYSERR;
@@ -454,19 +482,24 @@
    gettext_noop
    ("Use `/nick nickname' to change your nickname.  This will cause you to"
     " leave the current room and immediately rejoin it with the new name.")},
-  {"/msg ", &do_pm,
+  {"/msg ", &do_send_pm,
    gettext_noop
    ("Use `/msg nickname message' to send a private message to the specified"
     " user")},
-  {"/notice ", &do_pm,
+  {"/notice ", &do_send_pm,
    gettext_noop ("The `/notice' command is an alias for `/msg'")},
-  {"/query ", &do_pm,
+  {"/query ", &do_send_pm,
    gettext_noop ("The `/query' command is an alias for `/msg'")},
-  {"/sig ", &do_transmit_sig,
+  {"/sig ", &do_send_sig,
    gettext_noop ("Use `/sig message' to send a signed public message")},
-  {"/ack ", &do_transmit_ack,
+  {"/ack ", &do_send_ack,
    gettext_noop
    ("Use `/ack message' to require signed acknowledgment of the message")},
+  {"/anonymous ", &do_send_anonymous,
+   gettext_noop
+   ("Use `/anonymous message' to send a public anonymous message")},
+  {"/anon ", &do_send_anonymous,
+   gettext_noop ("The `/anon' command is an alias for `/anonymous'")},
   {"/quit", &do_quit,
    gettext_noop ("Use `/quit' to terminate gnunet-chat")},
   {"/leave", &do_quit,
@@ -479,13 +512,9 @@
   /* Add standard commands:
      /whois (print metadata),
      /ignore (set flag, check on receive!) */
-  /* Add special commands (currently supported):
-     + anonymous msgs
-     + authenticated msgs
-   */
   /* the following three commands must be last! */
   {"/", &do_unknown, NULL},
-  {"", &do_transmit, NULL},
+  {"", &do_send, NULL},
   {NULL, NULL, NULL},
 };
 
@@ -615,6 +644,7 @@
 				meta,
 				room_name,
 				-1,
+				&join_cb, NULL,
 				&receive_cb, NULL,
 				&member_list_cb, NULL,
 				&confirmation_cb, NULL, &me);
@@ -628,7 +658,7 @@
       return;
     }
   my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
-  fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
+  fprintf (stdout, _("Joining room `%s' as user `%s'...\n"), room_name, my_name);
   GNUNET_free (my_name);
   handle_cmd_task =
     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
Index: src/chat/test_chat_peer1.conf
===================================================================
--- src/chat/test_chat_peer1.conf	(revision 0)
+++ src/chat/test_chat_peer1.conf	(revision 0)
@@ -0,0 +1,72 @@
+[PATHS]
+SERVICEHOME = /tmp/gnunet-test-chat-peer-1/
+DEFAULTCONFIG = test_chat_peer1.conf
+
+[gnunetd]
+HOSTKEY = $SERVICEHOME/.hostkey
+
+[hostlist]
+HTTPPORT = 31000
+OPTIONS = -p
+
+[resolver]
+PORT = 31001
+HOSTNAME = localhost
+UNIXPATH = /tmp/gnunet-chat-p1-service-resolver.sock
+
+[transport]
+PORT = 31002
+UNIXPATH = /tmp/gnunet-chat-p1-service-transport.sock
+PLUGINS = tcp
+
+[transport-tcp]
+PORT = 31003
+
+[arm]
+PORT = 31004
+UNIXPATH = /tmp/gnunet-chat-p1-service-arm.sock
+HOSTNAME = localhost
+DEFAULTSERVICES = resolver transport core topology hostlist statistics chat
+
+[core]
+PORT = 31005
+UNIXPATH = /tmp/gnunet-chat-p1-service-core.sock
+HOSTNAME = localhost
+
+[topology]
+MINIMUM-FRIENDS = 0
+FRIENDS-ONLY = NO
+AUTOCONNECT = YES
+TARGET-CONNECTION-COUNT = 16
+FRIENDS = $SERVICEHOME/friends
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-daemon-topology
+
+[peerinfo]
+PORT = 31006
+UNIXPATH = /tmp/gnunet-chat-p1-service-peerinfo.sock
+HOSTNAME = localhost
+
+[statistics]
+PORT = 31007
+HOSTNAME = localhost
+UNIXPATH = /tmp/gnunet-chat-p1-service-statistics.sock
+
+[chat]
+PORT = 31008
+HOSTNAME = localhost
+HOME = $SERVICEHOME
+CONFIG = $DEFAULTCONFIG
+BINARY = gnunet-service-chat
+
+[testing]
+WEAKRANDOM = YES
+
+[fs]
+AUTOSTART = NO
+
+[datastore]
+AUTOSTART = NO
+
+[dht]
+AUTOSTART = NO
Index: src/include/gnunet_chat_service.h
===================================================================
--- src/include/gnunet_chat_service.h	(revision 14325)
+++ src/include/gnunet_chat_service.h	(working copy)
@@ -89,6 +89,14 @@
 struct GNUNET_CHAT_Room;
 
 /**
+ * Callback used for notification that we have joined the room.
+ *
+ * @param cls closure
+ * @return GNUNET_OK
+ */
+typedef int (*GNUNET_CHAT_JoinCallback) (void *cls);
+
+/**
  * Callback used for notification about incoming messages.
  *
  * @param cls closure
@@ -96,6 +104,7 @@
  * @param sender what is the ID of the sender? (maybe NULL)
  * @param member_info information about the joining member
  * @param message the message text
+ * @param timestamp when was the message sent?
  * @param options options for the message
  * @return GNUNET_OK to accept the message now, GNUNET_NO to
  *         accept (but user is away), GNUNET_SYSERR to signal denied delivery
@@ -105,11 +114,13 @@
 					    const GNUNET_HashCode *sender,
 					    const struct GNUNET_CONTAINER_MetaData *member_info,
 					    const char *message,
+					    struct GNUNET_TIME_Absolute timestamp,
 					    enum GNUNET_CHAT_MsgOptions options);
 
 /**
  * Callback used for notification that another room member has joined or left.
  *
+ * @param cls closure
  * @param member_info will be non-null if the member is joining, NULL if he is
  *        leaving
  * @param member_id hash of public key of the user (for unique identification)
@@ -129,8 +140,6 @@
  * @param orig_seq_number sequence number of the original message
  * @param timestamp when was the message received?
  * @param receiver who is confirming the receipt?
- * @param msg_hash hash of the original message
- * @param receipt signature confirming delivery
  * @return GNUNET_OK to continue, GNUNET_SYSERR to refuse processing further
  *         confirmations from anyone for this message
  */
@@ -138,9 +147,7 @@
 						struct GNUNET_CHAT_Room *room,
 						uint32_t orig_seq_number,
 						struct GNUNET_TIME_Absolute timestamp,
-						const GNUNET_HashCode *receiver,
-						const GNUNET_HashCode *msg_hash,
-						const struct GNUNET_CRYPTO_RsaSignature *receipt);
+						const GNUNET_HashCode *receiver);
 
 /**
  * Join a chat room.
@@ -153,6 +160,8 @@
  * @param member_info information about the joining member
  * @param room_name name of the room
  * @param msg_options message options of the joining user
+ * @param joinCallback which function to call when we've joined the room
+ * @param join_cls argument to callback
  * @param messageCallback which function to call if a message has
  *        been received?
  * @param message_cls argument to callback
@@ -170,6 +179,8 @@
 		       struct GNUNET_CONTAINER_MetaData *member_info,
 		       const char *room_name,
 		       enum GNUNET_CHAT_MsgOptions msg_options,
+		       GNUNET_CHAT_JoinCallback joinCallback,
+		       void *join_cls,
 		       GNUNET_CHAT_MessageCallback messageCallback,
 		       void *message_cls,
 		       GNUNET_CHAT_MemberListCallback memberCallback,
