/* This file is part of GNUnet. Copyright (C) 2013, 2016 GNUnet e.V. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file conversation/gnunet-service-conversation.c * @brief conversation service implementation * @author Simon Dieterle * @author Andreas Fuchs * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_protocols.h" #include "gnunet_applications.h" #include "gnunet_constants.h" #include "gnunet_signatures.h" #include "gnunet_cadet_service.h" #include "gnunet_conversation_service.h" #include "conversation.h" /** * How long is our signature on a call valid? Needs to be long enough for time zone * differences and network latency to not matter. No strong need for it to be short, * but we simply like all signatures to eventually expire. */ #define RING_TIMEOUT GNUNET_TIME_UNIT_DAYS /** * A line connects a local client with a cadet channel (or, if it is an * open line, is waiting for a cadet channel). */ struct Line; /** * The possible connection status */ enum ChannelStatus { /** * We just got the connection, but no introduction yet. */ CS_CALLEE_INIT, /** * Our phone is ringing, waiting for the client to pick up. */ CS_CALLEE_RINGING, /** * We are talking! */ CS_CALLEE_CONNECTED, /** * We're in shutdown, sending hangup messages before cleaning up. */ CS_CALLEE_SHUTDOWN, /** * We are waiting for the phone to be picked up. */ CS_CALLER_CALLING, /** * We are talking! */ CS_CALLER_CONNECTED, /** * We're in shutdown, sending hangup messages before cleaning up. */ CS_CALLER_SHUTDOWN }; /** * A `struct Channel` represents a cadet channel, which is a P2P * connection to another conversation service. Multiple channels can * be attached the the same `struct Line`, which represents a local * client. We keep them in a linked list. */ struct Channel { /** * This is a DLL. */ struct Channel *next; /** * This is a DLL. */ struct Channel *prev; /** * Line associated with the channel. */ struct Line *line; /** * Handle for the channel. */ struct GNUNET_CADET_Channel *channel; /** * Message queue for control messages */ struct GNUNET_MQ_Handle *mq; /** * Temporary buffer for audio data in the @e mq. */ struct GNUNET_MQ_Envelope *env; /** * Channel identifier we use for this call with the client. */ uint32_t cid; /** * Current status of this line. */ enum ChannelStatus status; /** * #GNUNET_YES if the channel was suspended by the other peer. */ int8_t suspended_remote; /** * #GNUNET_YES if the channel was suspended by the local client. */ int8_t suspended_local; }; /** * A `struct Line` connects a local client with cadet channels. */ struct Line { /** * This is a DLL. */ struct Channel *channel_head; /** * This is a DLL. */ struct Channel *channel_tail; /** * Handle to the line client. */ struct GNUNET_SERVER_Client *client; /** * Our open port. */ struct GNUNET_CADET_Port *port; /** * Port number we are listening on (to verify signatures). * Only valid if @e port is non-NULL. */ struct GNUNET_HashCode line_port; /** * Generator for channel IDs. */ uint32_t cid_gen; }; /** * Our configuration. */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Notification context containing all connected clients. */ static struct GNUNET_SERVER_NotificationContext *nc; /** * Handle for cadet */ static struct GNUNET_CADET_Handle *cadet; /** * Identity of this peer. */ static struct GNUNET_PeerIdentity my_identity; /** * Given a @a cid, find the corresponding channel given * a @a line. * * @param line a line to search * @param cid what to search for * @return NULL for not found */ static struct Channel * find_channel_by_line (struct Line *line, uint32_t cid) { struct Channel *ch; for (ch = line->channel_head; NULL != ch; ch = ch->next) if (cid == ch->cid) return ch; return NULL; } /** * Function to handle a pickup request message from the client * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_pickup_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientPhonePickupMessage *msg; struct GNUNET_MQ_Envelope *e; struct CadetPhonePickupMessage *mppm; struct Line *line; struct Channel *ch; msg = (const struct ClientPhonePickupMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL == line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } for (ch = line->channel_head; NULL != ch; ch = ch->next) if (msg->cid == ch->cid) break; if (NULL == ch) { /* could have been destroyed asynchronously, ignore message */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid); GNUNET_SERVER_receive_done (client, GNUNET_YES); return; } switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_RINGING: ch->status = CS_CALLEE_CONNECTED; break; case CS_CALLEE_CONNECTED: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_SHUTDOWN: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Ignoring client's PICKUP message, line is in SHUTDOWN\n"); break; case CS_CALLER_CALLING: case CS_CALLER_CONNECTED: case CS_CALLER_SHUTDOWN: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } GNUNET_break (CS_CALLEE_CONNECTED == ch->status); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending PICK_UP message to cadet\n"); e = GNUNET_MQ_msg (mppm, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_PICK_UP); GNUNET_MQ_send (ch->mq, e); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Destroy a channel. * * @param ch channel to destroy. */ static void destroy_line_cadet_channels (struct Channel *ch) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Destroying cadet channels\n"); if (NULL != ch->mq) { GNUNET_MQ_destroy (ch->mq); ch->mq = NULL; } if (NULL != ch->channel) GNUNET_CADET_channel_destroy (ch->channel); } /** * We are done signalling shutdown to the other peer. Close down * the channel. * * @param cls the `struct Channel` to reset/terminate */ static void mq_done_finish_caller_shutdown (void *cls) { struct Channel *ch = cls; switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break (0); break; case CS_CALLEE_RINGING: GNUNET_break (0); break; case CS_CALLEE_CONNECTED: GNUNET_break (0); break; case CS_CALLEE_SHUTDOWN: destroy_line_cadet_channels (ch); break; case CS_CALLER_CALLING: GNUNET_break (0); break; case CS_CALLER_CONNECTED: GNUNET_break (0); break; case CS_CALLER_SHUTDOWN: destroy_line_cadet_channels (ch); break; } } /** * Function to handle a hangup request message from the client * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_hangup_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientPhoneHangupMessage *msg; struct GNUNET_MQ_Envelope *e; struct CadetPhoneHangupMessage *mhum; struct Line *line; struct Channel *ch; msg = (const struct ClientPhoneHangupMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL == line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } for (ch = line->channel_head; NULL != ch; ch = ch->next) if (msg->cid == ch->cid) break; if (NULL == ch) { /* could have been destroyed asynchronously, ignore message */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid); GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received HANGUP for channel %u which is in state %d\n", msg->cid, ch->status); switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_RINGING: ch->status = CS_CALLEE_SHUTDOWN; break; case CS_CALLEE_CONNECTED: ch->status = CS_CALLEE_SHUTDOWN; break; case CS_CALLEE_SHUTDOWN: /* maybe the other peer closed asynchronously... */ GNUNET_SERVER_receive_done (client, GNUNET_OK); return; case CS_CALLER_CALLING: ch->status = CS_CALLER_SHUTDOWN; break; case CS_CALLER_CONNECTED: ch->status = CS_CALLER_SHUTDOWN; break; case CS_CALLER_SHUTDOWN: /* maybe the other peer closed asynchronously... */ GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending HANG_UP message via cadet\n"); e = GNUNET_MQ_msg (mhum, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_HANG_UP); GNUNET_MQ_notify_sent (e, &mq_done_finish_caller_shutdown, ch); GNUNET_MQ_send (ch->mq, e); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Function to handle a suspend request message from the client * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_suspend_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientPhoneSuspendMessage *msg; struct GNUNET_MQ_Envelope *e; struct CadetPhoneSuspendMessage *mhum; struct Line *line; struct Channel *ch; msg = (const struct ClientPhoneSuspendMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL == line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } for (ch = line->channel_head; NULL != ch; ch = ch->next) if (msg->cid == ch->cid) break; if (NULL == ch) { /* could have been destroyed asynchronously, ignore message */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid); GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } if (GNUNET_YES == ch->suspended_local) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received SUSPEND for channel %u which is in state %d\n", msg->cid, ch->status); switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_RINGING: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_CONNECTED: ch->suspended_local = GNUNET_YES; break; case CS_CALLEE_SHUTDOWN: /* maybe the other peer closed asynchronously... */ GNUNET_SERVER_receive_done (client, GNUNET_OK); return; case CS_CALLER_CALLING: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLER_CONNECTED: ch->suspended_local = GNUNET_YES; break; case CS_CALLER_SHUTDOWN: /* maybe the other peer closed asynchronously... */ GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending SUSPEND message via cadet\n"); e = GNUNET_MQ_msg (mhum, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_SUSPEND); GNUNET_MQ_send (ch->mq, e); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Function to handle a resume request message from the client * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_resume_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientPhoneResumeMessage *msg; struct GNUNET_MQ_Envelope *e; struct CadetPhoneResumeMessage *mhum; struct Line *line; struct Channel *ch; msg = (const struct ClientPhoneResumeMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL == line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } for (ch = line->channel_head; NULL != ch; ch = ch->next) if (msg->cid == ch->cid) break; if (NULL == ch) { /* could have been destroyed asynchronously, ignore message */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid); GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } if (GNUNET_YES != ch->suspended_local) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received RESUME for channel %u which is in state %d\n", msg->cid, ch->status); switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_RINGING: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_CONNECTED: ch->suspended_local = GNUNET_NO; break; case CS_CALLEE_SHUTDOWN: /* maybe the other peer closed asynchronously... */ GNUNET_SERVER_receive_done (client, GNUNET_OK); return; case CS_CALLER_CALLING: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLER_CONNECTED: ch->suspended_local = GNUNET_NO; break; case CS_CALLER_SHUTDOWN: /* maybe the other peer closed asynchronously... */ GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending RESUME message via cadet\n"); e = GNUNET_MQ_msg (mhum, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RESUME); GNUNET_MQ_send (ch->mq, e); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Function to handle call request from the client * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_call_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientCallMessage *msg; struct Line *line; struct Channel *ch; struct GNUNET_MQ_Envelope *e; struct CadetPhoneRingMessage *ring; struct CadetPhoneRingInfoPS rs; msg = (const struct ClientCallMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL != line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } line = GNUNET_new (struct Line); line->client = client; line->line_port = msg->line_port; GNUNET_SERVER_client_set_user_context (client, line); GNUNET_SERVER_notification_context_add (nc, client); rs.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING); rs.purpose.size = htonl (sizeof (struct CadetPhoneRingInfoPS)); rs.line_port = line->line_port; rs.target_peer = msg->target; rs.expiration_time = GNUNET_TIME_absolute_hton (GNUNET_TIME_relative_to_absolute (RING_TIMEOUT)); ch = GNUNET_new (struct Channel); ch->line = line; GNUNET_CONTAINER_DLL_insert (line->channel_head, line->channel_tail, ch); ch->status = CS_CALLER_CALLING; ch->channel = GNUNET_CADET_channel_create (cadet, ch, &msg->target, &msg->line_port, GNUNET_CADET_OPTION_RELIABLE); ch->mq = GNUNET_CADET_mq_create (ch->channel); e = GNUNET_MQ_msg (ring, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RING); GNUNET_CRYPTO_ecdsa_key_get_public (&msg->caller_id, &ring->caller_id); ring->expiration_time = rs.expiration_time; GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_ecdsa_sign (&msg->caller_id, &rs.purpose, &ring->signature)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending RING message via CADET\n"); GNUNET_MQ_send (ch->mq, e); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Transmission of audio data via cadet channel finished. * * @param cls the `struct Channel` we are transmitting for */ static void channel_audio_sent_notify (void *cls) { struct Channel *ch = cls; ch->env = NULL; } /** * Function to handle audio data from the client * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_audio_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientAudioMessage *msg; struct ClientAudioMessage *mam; struct Line *line; struct Channel *ch; size_t size; size = ntohs (message->size) - sizeof (struct ClientAudioMessage); msg = (const struct ClientAudioMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL == line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } ch = find_channel_by_line (line, msg->cid); if (NULL == ch) { /* could have been destroyed asynchronously, ignore message */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel %u not found\n", msg->cid); GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } switch (ch->status) { case CS_CALLEE_INIT: case CS_CALLEE_RINGING: case CS_CALLER_CALLING: GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; case CS_CALLEE_CONNECTED: case CS_CALLER_CONNECTED: /* common case, handled below */ break; case CS_CALLEE_SHUTDOWN: case CS_CALLER_SHUTDOWN: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK, "Cadet audio channel in shutdown; audio data dropped\n"); GNUNET_SERVER_receive_done (client, GNUNET_OK); return; } if (GNUNET_YES == ch->suspended_local) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "This channel is suspended locally\n"); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } if (NULL != ch->env) { /* NOTE: we may want to not do this and instead combine the data */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bandwidth insufficient; dropping previous audio data segment\n"); GNUNET_MQ_send_cancel (ch->env); ch->env = NULL; } ch->env = GNUNET_MQ_msg_extra (mam, size, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_AUDIO); GNUNET_memcpy (&mam[1], &msg[1], size); /* FIXME: set options for unreliable transmission */ GNUNET_MQ_notify_sent (ch->env, &channel_audio_sent_notify, ch); GNUNET_MQ_send (ch->mq, ch->env); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Function to handle a ring message incoming over cadet * * @param cls closure, NULL * @param channel the channel over which the message arrived * @param channel_ctx the channel context, can be NULL * or point to the `struct Channel` * @param message the incoming message * @return #GNUNET_OK */ static int handle_cadet_ring_message (void *cls, struct GNUNET_CADET_Channel *channel, void **channel_ctx, const struct GNUNET_MessageHeader *message) { struct Channel *ch = *channel_ctx; struct Line *line = ch->line; const struct CadetPhoneRingMessage *msg; struct ClientPhoneRingMessage cring; struct CadetPhoneRingInfoPS rs; msg = (const struct CadetPhoneRingMessage *) message; rs.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING); rs.purpose.size = htonl (sizeof (struct CadetPhoneRingInfoPS)); rs.line_port = line->line_port; rs.target_peer = my_identity; rs.expiration_time = msg->expiration_time; if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_CONVERSATION_RING, &rs.purpose, &msg->signature, &msg->caller_id)) { GNUNET_break_op (0); return GNUNET_SYSERR; } if (0 == GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_ntoh (msg->expiration_time)).rel_value_us) { /* ancient call, replay? */ GNUNET_break_op (0); /* Note that our reliance on time here is awkward; better would be to use a more complex challenge-response protocol against replay attacks. Left for future work ;-). */ return GNUNET_SYSERR; } if (CS_CALLEE_INIT != ch->status) { GNUNET_break_op (0); return GNUNET_SYSERR; } GNUNET_CADET_receive_done (channel); ch->status = CS_CALLEE_RINGING; cring.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RING); cring.header.size = htons (sizeof (cring)); cring.cid = ch->cid; cring.caller_id = msg->caller_id; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending RING message to client. CID is %u\n", (unsigned int) ch->cid); GNUNET_SERVER_notification_context_unicast (nc, line->client, &cring.header, GNUNET_NO); return GNUNET_OK; } /** * Function to handle a hangup message incoming over cadet * * @param cls closure, NULL * @param channel the channel over which the message arrived * @param channel_ctx the channel context, can be NULL * or point to the `struct Channel` * @param message the incoming message * @return #GNUNET_OK */ static int handle_cadet_hangup_message (void *cls, struct GNUNET_CADET_Channel *channel, void **channel_ctx, const struct GNUNET_MessageHeader *message) { struct Channel *ch = *channel_ctx; struct Line *line = ch->line; struct ClientPhoneHangupMessage hup; enum ChannelStatus status; GNUNET_CADET_receive_done (channel); hup.header.size = htons (sizeof (hup)); hup.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP); hup.cid = ch->cid; status = ch->status; destroy_line_cadet_channels (ch); switch (status) { case CS_CALLEE_INIT: GNUNET_break_op (0); return GNUNET_OK; case CS_CALLEE_RINGING: case CS_CALLEE_CONNECTED: break; case CS_CALLEE_SHUTDOWN: return GNUNET_OK; case CS_CALLER_CALLING: case CS_CALLER_CONNECTED: break; case CS_CALLER_SHUTDOWN: return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending HANG UP message to client\n"); GNUNET_SERVER_notification_context_unicast (nc, line->client, &hup.header, GNUNET_NO); return GNUNET_OK; } /** * Function to handle a pickup message incoming over cadet * * @param cls closure, NULL * @param channel the channel over which the message arrived * @param channel_ctx the channel context, can be NULL * or point to the `struct Channel` * @param message the incoming message * @return #GNUNET_OK if message was OK, * #GNUNET_SYSERR if message violated the protocol */ static int handle_cadet_pickup_message (void *cls, struct GNUNET_CADET_Channel *channel, void **channel_ctx, const struct GNUNET_MessageHeader *message) { struct Channel *ch = *channel_ctx; struct Line *line = ch->line; struct ClientPhonePickedupMessage pick; GNUNET_CADET_receive_done (channel); switch (ch->status) { case CS_CALLEE_INIT: case CS_CALLEE_RINGING: case CS_CALLEE_CONNECTED: GNUNET_break_op (0); destroy_line_cadet_channels (ch); return GNUNET_SYSERR; case CS_CALLEE_SHUTDOWN: GNUNET_break_op (0); destroy_line_cadet_channels (ch); return GNUNET_SYSERR; case CS_CALLER_CALLING: ch->status = CS_CALLER_CONNECTED; break; case CS_CALLER_CONNECTED: GNUNET_break_op (0); return GNUNET_OK; case CS_CALLER_SHUTDOWN: GNUNET_break_op (0); mq_done_finish_caller_shutdown (ch); return GNUNET_SYSERR; } pick.header.size = htons (sizeof (pick)); pick.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICKED_UP); pick.cid = ch->cid; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending PICKED UP message to client\n"); GNUNET_SERVER_notification_context_unicast (nc, line->client, &pick.header, GNUNET_NO); return GNUNET_OK; } /** * Function to handle a suspend message incoming over cadet * * @param cls closure, NULL * @param channel the channel over which the message arrived * @param channel_ctx the channel context, can be NULL * or point to the `struct Channel` * @param message the incoming message * @return #GNUNET_OK */ static int handle_cadet_suspend_message (void *cls, struct GNUNET_CADET_Channel *channel, void **channel_ctx, const struct GNUNET_MessageHeader *message) { struct Channel *ch = *channel_ctx; struct Line *line = ch->line; struct ClientPhoneSuspendMessage suspend; GNUNET_CADET_receive_done (channel); suspend.header.size = htons (sizeof (suspend)); suspend.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND); suspend.cid = ch->cid; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending channel CID: %u\n", ch->cid); switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break_op (0); break; case CS_CALLEE_RINGING: GNUNET_break_op (0); break; case CS_CALLEE_CONNECTED: ch->suspended_remote = GNUNET_YES; break; case CS_CALLEE_SHUTDOWN: return GNUNET_OK; case CS_CALLER_CALLING: GNUNET_break_op (0); break; case CS_CALLER_CONNECTED: ch->suspended_remote = GNUNET_YES; break; case CS_CALLER_SHUTDOWN: return GNUNET_OK; } GNUNET_SERVER_notification_context_unicast (nc, line->client, &suspend.header, GNUNET_NO); return GNUNET_OK; } /** * Function to handle a resume message incoming over cadet * * @param cls closure, NULL * @param channel the channel over which the message arrived * @param channel_ctx the channel context, can be NULL * or point to the `struct Channel` * @param message the incoming message * @return #GNUNET_OK */ static int handle_cadet_resume_message (void *cls, struct GNUNET_CADET_Channel *channel, void **channel_ctx, const struct GNUNET_MessageHeader *message) { struct Channel *ch = *channel_ctx; struct Line *line; struct ClientPhoneResumeMessage resume; if (NULL == ch) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "RESUME message received for non-existing line, dropping channel.\n"); return GNUNET_SYSERR; } line = ch->line; resume.header.size = htons (sizeof (resume)); resume.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME); resume.cid = ch->cid; GNUNET_CADET_receive_done (channel); if (GNUNET_YES != ch->suspended_remote) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "RESUME message received for non-suspended channel, dropping channel.\n"); return GNUNET_SYSERR; } switch (ch->status) { case CS_CALLEE_INIT: GNUNET_break (0); break; case CS_CALLEE_RINGING: GNUNET_break (0); break; case CS_CALLEE_CONNECTED: ch->suspended_remote = GNUNET_NO; break; case CS_CALLEE_SHUTDOWN: return GNUNET_OK; case CS_CALLER_CALLING: GNUNET_break (0); break; case CS_CALLER_CONNECTED: ch->suspended_remote = GNUNET_NO; break; case CS_CALLER_SHUTDOWN: return GNUNET_OK; } GNUNET_SERVER_notification_context_unicast (nc, line->client, &resume.header, GNUNET_NO); return GNUNET_OK; } /** * Function to handle an audio message incoming over cadet * * @param cls closure, NULL * @param channel the channel over which the message arrived * @param channel_ctx the channel context, can be NULL * or point to the `struct Channel` * @param message the incoming message * @return #GNUNET_OK */ static int handle_cadet_audio_message (void *cls, struct GNUNET_CADET_Channel *channel, void **channel_ctx, const struct GNUNET_MessageHeader *message) { struct Channel *ch = *channel_ctx; const struct CadetAudioMessage *msg; size_t msize = ntohs (message->size) - sizeof (struct CadetAudioMessage); char buf[msize + sizeof (struct ClientAudioMessage)] GNUNET_ALIGN; struct ClientAudioMessage *cam; msg = (const struct CadetAudioMessage *) message; GNUNET_CADET_receive_done (channel); if ( (GNUNET_YES == ch->suspended_local) || (GNUNET_YES == ch->suspended_remote) ) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received %u bytes of AUDIO data on suspended channel CID %u; dropping\n", (unsigned int) msize, ch->cid); return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Forwarding %u bytes of AUDIO data to client CID %u\n", (unsigned int) msize, ch->cid); cam = (struct ClientAudioMessage *) buf; cam->header.size = htons (sizeof (buf)); cam->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO); cam->cid = ch->cid; GNUNET_memcpy (&cam[1], &msg[1], msize); GNUNET_SERVER_notification_context_unicast (nc, ch->line->client, &cam->header, GNUNET_YES); return GNUNET_OK; } /** * Method called whenever another peer has added us to a channel * the other peer initiated. * * @param cls the `struct Line` receiving a connection * @param channel new handle to the channel * @param initiator peer that started the channel * @param port port * @param options channel option flags * @return initial channel context for the channel */ static void * inbound_channel (void *cls, struct GNUNET_CADET_Channel *channel, const struct GNUNET_PeerIdentity *initiator, const struct GNUNET_HashCode *port, enum GNUNET_CADET_ChannelOption options) { struct Line *line = cls; struct Channel *ch; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received incoming cadet channel on line %p\n", line); ch = GNUNET_new (struct Channel); ch->status = CS_CALLEE_INIT; ch->line = line; ch->channel = channel; ch->mq = GNUNET_CADET_mq_create (ch->channel); ch->cid = line->cid_gen++; GNUNET_CONTAINER_DLL_insert (line->channel_head, line->channel_tail, ch); return ch; } /** * Function called whenever an inbound channel is destroyed. Should clean up * any associated state. * * @param cls closure (set from #GNUNET_CADET_connect) * @param channel connection to the other end (henceforth invalid) * @param channel_ctx place where local state associated * with the channel is stored; * may point to the `struct Channel` */ static void inbound_end (void *cls, const struct GNUNET_CADET_Channel *channel, void *channel_ctx) { struct Channel *ch = channel_ctx; struct Line *line; struct ClientPhoneHangupMessage hup; if (NULL == ch) { GNUNET_break (0); return; } line = ch->line; GNUNET_assert (channel == ch->channel); ch->channel = NULL; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel destroyed by CADET in state %d\n", ch->status); hup.header.size = htons (sizeof (hup)); hup.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP); hup.cid = ch->cid; switch (ch->status) { case CS_CALLEE_INIT: break; case CS_CALLEE_RINGING: case CS_CALLEE_CONNECTED: if (NULL != line) GNUNET_SERVER_notification_context_unicast (nc, line->client, &hup.header, GNUNET_NO); break; case CS_CALLEE_SHUTDOWN: break; case CS_CALLER_CALLING: case CS_CALLER_CONNECTED: if (NULL != line) GNUNET_SERVER_notification_context_unicast (nc, line->client, &hup.header, GNUNET_NO); break; case CS_CALLER_SHUTDOWN: break; } destroy_line_cadet_channels (ch); if (NULL != line) GNUNET_CONTAINER_DLL_remove (line->channel_head, line->channel_tail, ch); GNUNET_free (ch); } /** * A client disconnected. Remove all of its data structure entries. * * @param cls closure, NULL * @param client identification of the client */ static void handle_client_disconnect (void *cls, struct GNUNET_SERVER_Client *client) { struct Line *line; struct Channel *ch; struct Channel *chn; if (NULL == client) return; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL == line) return; GNUNET_SERVER_client_set_user_context (client, NULL); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client disconnected, closing line\n"); if (NULL != line->port) { GNUNET_CADET_close_port (line->port); line->port = NULL; } for (ch = line->channel_head; NULL != ch; ch = chn) { chn = ch->next; ch->line = NULL; destroy_line_cadet_channels (ch); } GNUNET_free (line); } /** * Function to register a phone. * * @param cls closure, NULL * @param client the client from which the message is * @param message the message from the client */ static void handle_client_register_message (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ClientPhoneRegisterMessage *msg; struct Line *line; msg = (const struct ClientPhoneRegisterMessage *) message; line = GNUNET_SERVER_client_get_user_context (client, struct Line); if (NULL != line) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } line = GNUNET_new (struct Line); line->client = client; GNUNET_SERVER_notification_context_add (nc, client); GNUNET_SERVER_client_set_user_context (client, line); line->line_port = msg->line_port; line->port = GNUNET_CADET_open_port (cadet, &msg->line_port, &inbound_channel, line); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Shutdown nicely * * @param cls closure, NULL */ static void do_shutdown (void *cls) { if (NULL != cadet) { GNUNET_CADET_disconnect (cadet); cadet = NULL; } if (NULL != nc) { GNUNET_SERVER_notification_context_destroy (nc); nc = NULL; } } /** * Main function that will be run by the scheduler. * * @param cls closure * @param server server handle * @param c configuration */ static void run (void *cls, struct GNUNET_SERVER_Handle *server, const struct GNUNET_CONFIGURATION_Handle *c) { static const struct GNUNET_SERVER_MessageHandler server_handlers[] = { {&handle_client_register_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_REGISTER, sizeof (struct ClientPhoneRegisterMessage)}, {&handle_client_pickup_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_PICK_UP, sizeof (struct ClientPhonePickupMessage) }, {&handle_client_suspend_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_SUSPEND, sizeof (struct ClientPhoneSuspendMessage) }, {&handle_client_resume_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_RESUME, sizeof (struct ClientPhoneResumeMessage) }, {&handle_client_hangup_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_HANG_UP, sizeof (struct ClientPhoneHangupMessage) }, {&handle_client_call_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_PHONE_CALL, sizeof (struct ClientCallMessage) }, {&handle_client_audio_message, NULL, GNUNET_MESSAGE_TYPE_CONVERSATION_CS_AUDIO, 0}, {NULL, NULL, 0, 0} }; static struct GNUNET_CADET_MessageHandler cadet_handlers[] = { {&handle_cadet_ring_message, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RING, sizeof (struct CadetPhoneRingMessage)}, {&handle_cadet_hangup_message, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_HANG_UP, sizeof (struct CadetPhoneHangupMessage)}, {&handle_cadet_pickup_message, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_PICK_UP, sizeof (struct CadetPhonePickupMessage)}, {&handle_cadet_suspend_message, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_SUSPEND, sizeof (struct CadetPhoneSuspendMessage)}, {&handle_cadet_resume_message, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_PHONE_RESUME, sizeof (struct CadetPhoneResumeMessage)}, {&handle_cadet_audio_message, GNUNET_MESSAGE_TYPE_CONVERSATION_CADET_AUDIO, 0}, {NULL, 0, 0} }; cfg = c; GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_get_peer_identity (cfg, &my_identity)); cadet = GNUNET_CADET_connect (cfg, NULL, &inbound_end, cadet_handlers); if (NULL == cadet) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } nc = GNUNET_SERVER_notification_context_create (server, 16); GNUNET_SERVER_add_handlers (server, server_handlers); GNUNET_SERVER_disconnect_notify (server, &handle_client_disconnect, NULL); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); } /** * The main function for the conversation service. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { return (GNUNET_OK == GNUNET_SERVICE_run (argc, argv, "conversation", GNUNET_SERVICE_OPTION_NONE, &run, NULL)) ? 0 : 1; } /* end of gnunet-service-conversation.c */