View Issue Details

IDProjectCategoryView StatusLast Update
0001657GNUnetotherpublic2011-04-27 16:18
Reportervminko Assigned ToChristian Grothoff  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Summary0001657: Update for the chat app
DescriptionChanges:

1. Added 10 test cases: simple chatting, anonymous chatting, authenticated
chatting, private chatting and acknowledgments (for both CS and P2P layers).
2. Finished anonymity support (P2P layer basically).
3. API changes:
- MessageCallback now receives timestamp of the message
- MessageConfirmation callback no longer accepts signature of the receipts since
  we check it on the service side
- new callback for GNUNET_CHAT_join_room indicating that we've joined the room
4. Fixed three minor bugs in the library and two bugs in the service.
5. Disabled debug output.

The patch should be applied to svn@14325.
And sorry for the doxygen errors :-/
TagsNo tags attached.
Attached Files
gnunet-svn@14325-chat-update.patch (78,000 bytes)   
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,

Activities

Christian Grothoff

2011-02-07 00:27

manager   ~0004254

Applied as SVN 14364.

Issue History

Date Modified Username Field Change
2011-02-04 16:06 vminko New Issue
2011-02-04 16:06 vminko File Added: gnunet-svn@14325-chat-update.patch
2011-02-07 00:27 Christian Grothoff Note Added: 0004254
2011-02-07 00:27 Christian Grothoff Status new => resolved
2011-02-07 00:27 Christian Grothoff Resolution open => fixed
2011-02-07 00:27 Christian Grothoff Assigned To => Christian Grothoff
2011-04-27 16:18 Christian Grothoff Status resolved => closed