Index: AUTHORS =================================================================== --- AUTHORS (revision 14325) +++ AUTHORS (working copy) @@ -53,6 +53,7 @@ Tzvetan Horozov Uli Luckas Vasil Dimov +Vitaly Minko Werner Koch [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, + "", + 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, + "", + 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, + "", + 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, + "", + 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, + "", + 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,