-man page for gnunet-conversation
[oweals/gnunet.git] / src / conversation / gnunet-conversation-new.c
1 /*
2   This file is part of GNUnet.
3   (C) 2013 Christian Grothoff (and other contributing authors)
4   
5   GNUnet is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published
7   by the Free Software Foundation; either version 3, or (at your
8   option) any later version.
9   
10   GNUnet is distributed in the hope that it will be useful, but
11   WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   General Public License for more details.
14   
15   You should have received a copy of the GNU General Public License
16   along with GNUnet; see the file COPYING.  If not, write to the
17   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18   Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file conversation/gnunet-conversation.c
22  * @brief conversation implementation
23  * @author Simon Dieterle
24  * @author Andreas Fuchs
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_constants.h"
29 #include "gnunet_conversation_service.h"
30
31
32 /**
33  * Maximum length allowed for the command line input.
34  */
35 #define MAX_MESSAGE_LENGTH 1024
36
37
38 /**
39  * Possible states of the program.
40  */
41 enum ConversationState
42 {
43   /**
44    * We're waiting for our own idenitty.
45    */
46   CS_LOOKUP_EGO,
47
48   /**
49    * We're listening for calls
50    */
51   CS_LISTEN,
52
53   /**
54    * Our phone is ringing.
55    */
56   CS_RING,
57
58   /**
59    * We accepted an incoming phone call.
60    */
61   CS_ACCEPTED,
62
63   /**
64    * We are looking up some other participant.
65    */
66   CS_RESOLVING,
67
68   /**
69    * We are now ringing the other participant.
70    */
71   CS_RINGING,
72
73   /**
74    * The other party accepted our call and we are now connected.
75    */
76   CS_CONNECTED,
77
78   /**
79    * Internal error
80    */
81   CS_ERROR
82
83 };
84
85
86 /**
87  * Phone handle
88  */
89 static struct GNUNET_CONVERSATION_Phone *phone;
90
91 /**
92  * Call handle
93  */
94 static struct GNUNET_CONVERSATION_Call *call;
95
96 /**
97  * Desired phone line.
98  */
99 static unsigned int line;
100
101 /**
102  * Task which handles the commands
103  */
104 static GNUNET_SCHEDULER_TaskIdentifier handle_cmd_task;
105
106 /**
107  * Our speaker.
108  */
109 static struct GNUNET_SPEAKER_Handle *speaker;
110
111 /**
112  * Our microphone.
113  */
114 static struct GNUNET_MICROPHONE_Handle *mic;
115
116 /**
117  * Our configuration.
118  */
119 static struct GNUNET_CONFIGURATION_Handle *cfg;
120
121 /**
122  * Our ego.
123  */
124 static struct GNUNET_IDENTITY_Ego *caller_id;
125
126 /**
127  * Handle to identity service.
128  */
129 static struct GNUNET_IDENTITY_Handle *id;
130
131 /**
132  * Name of our ego.
133  */
134 static char *ego_name;
135
136 /**
137  * Name of conversation partner (if any).
138  */
139 static char *peer_name;
140
141 /**
142  * File handle for stdin.
143  */
144 static struct GNUNET_DISK_FileHandle *stdin_fh;
145
146 /**
147  * Our current state.
148  */
149 static enum ConversationState state;
150
151 /**
152  * Be verbose.
153  */
154 static int verbose;
155
156
157 /**
158  * Function called with an event emitted by a phone.
159  *
160  * @param cls closure
161  * @param code type of the event on the phone
162  * @param ... additional information, depends on @a code
163  */
164 static void
165 phone_event_handler (void *cls,
166                      enum GNUNET_CONVERSATION_EventCode code,
167                      ...)
168 {
169   va_list va;
170   
171   va_start (va, code);
172   switch (code)
173   {
174   case GNUNET_CONVERSATION_EC_RING:
175     GNUNET_break (CS_LISTEN == state);
176     GNUNET_free_non_null (peer_name);
177     peer_name = GNUNET_strdup (va_arg (va, const char *));
178     FPRINTF (stdout,
179              _("Incoming call from `%s'.\nPlease /accept or /cancel the call.\n"),
180              peer_name);
181     state = CS_RING;
182     break;
183   case GNUNET_CONVERSATION_EC_RINGING:
184     GNUNET_break (0);
185     break;
186   case GNUNET_CONVERSATION_EC_READY:
187     GNUNET_break (0);
188     break;
189   case GNUNET_CONVERSATION_EC_GNS_FAIL:
190     GNUNET_break (0);
191     break;
192   case GNUNET_CONVERSATION_EC_BUSY:
193     GNUNET_break (0);
194     break;
195   case GNUNET_CONVERSATION_EC_TERMINATED:
196     GNUNET_break ( (CS_RING == state) ||
197                    (CS_ACCEPTED == state) );
198     FPRINTF (stdout,
199              _("Call terminated: %s\n"),
200              va_arg (va, const char *));
201     state = CS_LISTEN;
202     break;
203   }
204   va_end (va);
205 }
206
207
208 /**
209  * Start our phone.
210  */
211 static void
212 start_phone ()
213 {
214   if (NULL == caller_id)
215   {
216     FPRINTF (stderr,
217              _("Ego `%s' no longer available, phone is now down.\n"),
218              ego_name);
219     state = CS_LOOKUP_EGO;
220     return;
221   }
222   phone = GNUNET_CONVERSATION_phone_create (cfg,
223                                             caller_id,
224                                             &phone_event_handler, NULL);
225   /* FIXME: get record and print full GNS record info later here... */
226   if (NULL == phone)
227   {
228     FPRINTF (stderr,
229              "%s",
230              _("Failed to setup phone (internal error)\n"));
231     state = CS_ERROR;
232   }
233   else
234   {
235     if (verbose)
236       FPRINTF (stdout,
237                _("Phone active on line %u\n"),
238                (unsigned int) line);
239     state = CS_LISTEN;
240   }
241 }
242
243
244 /**
245  * Function called with an event emitted by a phone.
246  *
247  * @param cls closure
248  * @param code type of the event on the phone
249  * @param ... additional information, depends on @a code
250  */
251 static void
252 call_event_handler (void *cls,
253                     enum GNUNET_CONVERSATION_EventCode code,
254                     ...)
255 {
256   va_list va;
257   
258   va_start (va, code);
259   switch (code)
260   {
261   case GNUNET_CONVERSATION_EC_RING:
262     GNUNET_break (0);
263     break;
264   case GNUNET_CONVERSATION_EC_RINGING:
265     GNUNET_break (CS_RESOLVING == state);
266     if (verbose)
267       FPRINTF (stdout,
268                "%s",
269                _("Resolved address. Now ringing other party.\n"));
270     state = CS_RINGING;
271     break;
272   case GNUNET_CONVERSATION_EC_READY:
273     GNUNET_break (CS_RINGING == state);
274     FPRINTF (stdout,
275              _("Connection established: %s\n"),
276              va_arg (va, const char *));
277     state = CS_CONNECTED;
278     break;
279   case GNUNET_CONVERSATION_EC_GNS_FAIL:
280     GNUNET_break (CS_RESOLVING == state);
281     FPRINTF (stdout,
282              "%s",
283              _("Failed to resolve name\n"));
284     GNUNET_CONVERSATION_call_stop (call, NULL);
285     call = NULL;
286     start_phone ();
287     break;
288   case GNUNET_CONVERSATION_EC_BUSY:
289     GNUNET_break (CS_RINGING == state);
290     FPRINTF (stdout,
291              "%s",
292              _("Line busy\n"));
293     GNUNET_CONVERSATION_call_stop (call, NULL);
294     call = NULL;
295     start_phone ();
296     break;
297   case GNUNET_CONVERSATION_EC_TERMINATED:
298     GNUNET_break ( (CS_RINGING == state) ||
299                    (CS_CONNECTED == state) );
300     FPRINTF (stdout,
301              _("Call terminated: %s\n"),
302              va_arg (va, const char *));
303     GNUNET_CONVERSATION_call_stop (call, NULL);
304     call = NULL;
305     start_phone ();
306     break;
307   }
308   va_end (va);
309 }
310
311
312 /**
313  * Function declareation for executing a action
314  *
315  * @param arguments arguments given to the function
316  */
317 typedef void (*ActionFunction) (const char *arguments);
318
319
320 /**
321  * Structure which defines a command
322  */
323 struct VoipCommand
324 {
325   /**
326    * Command the user needs to enter.
327    */
328   const char *command;
329   
330   /**
331    * Function to call on command.
332    */
333   ActionFunction Action;
334
335   /**
336    * Help text for the command.
337    */
338   const char *helptext;
339 };
340
341
342 /**
343  * Action function to print help for the command shell.
344  *
345  * @param arguments arguments given to the command
346  */
347 static void
348 do_help (const char *args);
349
350
351 /**
352  * Terminate the client
353  *
354  * @param args arguments given to the command
355  */
356 static void
357 do_quit (const char *args)
358 {
359   GNUNET_SCHEDULER_shutdown ();
360 }
361
362
363 /**
364  * Handler for unknown command.
365  *
366  * @param args arguments given to the command
367  */
368 static void
369 do_unknown (const char *msg)
370 {
371   FPRINTF (stderr, 
372            _("Unknown command `%s'\n"), 
373            msg);
374 }
375
376
377 /**
378  * Initiating a new call
379  *
380  * @param args arguments given to the command
381  */
382 static void
383 do_call (const char *arg)
384 {
385   if (NULL == caller_id)
386   {
387     FPRINTF (stderr,
388              _("Ego `%s' not available\n"),
389              ego_name);
390     return;
391   }
392   switch (state)
393   {
394   case CS_LOOKUP_EGO: 
395     FPRINTF (stderr,
396              _("Ego `%s' not available\n"),
397              ego_name);
398     return;
399   case CS_LISTEN:
400     /* ok to call! */
401     break;
402   case CS_RING:
403     FPRINTF (stdout,
404              _("Hanging up on incoming phone call from `%s' to call `%s'.\n"),
405              peer_name,
406              arg);
407     GNUNET_CONVERSATION_phone_hang_up (phone, NULL);
408     break;
409   case CS_ACCEPTED:
410     FPRINTF (stderr,
411              _("You are already in a conversation with `%s', refusing to call `%s'.\n"),
412              peer_name,
413              arg);
414     return;
415   case CS_RESOLVING:
416   case CS_RINGING:
417     FPRINTF (stderr,
418              _("Aborting call to `%s'\n"),
419              peer_name);
420     GNUNET_CONVERSATION_call_stop (call, NULL);
421     call = NULL;
422     break;
423   case CS_CONNECTED:
424     FPRINTF (stderr,
425              _("You are already in a conversation with `%s', refusing to call `%s'.\n"),
426              peer_name,
427              arg);
428     return;
429   case CS_ERROR:
430     /* ok to call */
431     break;
432   }
433   GNUNET_assert (NULL == call);
434   if (NULL != phone)
435   {
436     GNUNET_CONVERSATION_phone_destroy (phone);
437     phone = NULL;
438   }
439   GNUNET_free_non_null (peer_name);
440   peer_name = GNUNET_strdup (arg);
441   call = GNUNET_CONVERSATION_call_start (cfg,
442                                          caller_id,
443                                          arg,
444                                          speaker,
445                                          mic,
446                                          &call_event_handler, NULL);
447   state = CS_RESOLVING;
448 }
449
450
451 /**
452  * Accepting an incoming call
453  *
454  * @param args arguments given to the command
455  */
456 static void
457 do_accept (const char *args)
458 {
459   switch (state)
460   {
461   case CS_LOOKUP_EGO:     
462   case CS_LISTEN:
463   case CS_ERROR:
464     FPRINTF (stderr,
465              _("There is no incoming call to be accepted!\n"));
466     return;
467   case CS_RING:
468     /* this is the expected state */
469     break;
470   case CS_ACCEPTED:
471     FPRINTF (stderr,
472              _("You are already in a conversation with `%s'.\n"),
473              peer_name);
474     return;
475   case CS_RESOLVING:
476   case CS_RINGING:
477     FPRINTF (stderr,
478              _("You are trying to call `%s', cannot accept incoming calls right now.\n"),
479              peer_name);
480     return;
481   case CS_CONNECTED:
482     FPRINTF (stderr,
483              _("You are already in a conversation with `%s'.\n"),
484              peer_name);
485     return;
486   }
487   GNUNET_assert (NULL != phone);
488   GNUNET_CONVERSATION_phone_pick_up (phone, 
489                                      args,
490                                      speaker,
491                                      mic);
492   state = CS_ACCEPTED;
493 }
494
495
496 /**
497  * Accepting an incoming call
498  *
499  * @param args arguments given to the command
500  */
501 static void
502 do_status (const char *args)
503 {
504   switch (state)
505   {
506   case CS_LOOKUP_EGO: 
507     FPRINTF (stdout,
508              _("We are currently trying to locate the private key for the ego `%s'.\n"),
509              ego_name);
510     break;
511   case CS_LISTEN:
512     FPRINTF (stdout,
513              _("We are listening for incoming calls for ego `%s' on line %u.\n"),
514              ego_name,
515              line);
516     break;
517   case CS_RING:
518     FPRINTF (stdout,
519              _("The phone is rining. `%s' is trying to call us.\n"),
520              peer_name);
521     break;
522   case CS_ACCEPTED:
523   case CS_CONNECTED:
524     FPRINTF (stdout,
525              _("You are having a conversation with `%s'.\n"),
526              peer_name);
527     break;
528   case CS_RESOLVING:
529     FPRINTF (stdout,
530              _("We are trying to find the network address to call `%s'.\n"),
531              peer_name);
532     break;
533   case CS_RINGING:
534     FPRINTF (stdout,
535              _("We are calling `%s', his phone should be ringing.\n"),
536              peer_name);
537     break;
538   case CS_ERROR:
539     FPRINTF (stdout,
540              _("We had an internal error setting up our phone line. You can still make calls.\n"));
541     break;
542   }
543 }
544
545
546 /**
547  * Rejecting a call
548  *
549  * @param args arguments given to the command
550  */
551 static void
552 do_reject (const char *args)
553 {
554   switch (state)
555   {
556   case CS_LOOKUP_EGO: 
557   case CS_LISTEN:
558   case CS_ERROR:
559     FPRINTF (stderr,
560              "%s",
561              _("There is no call that could be cancelled right now.\n"));
562     return;
563   case CS_RING:
564   case CS_ACCEPTED:
565   case CS_RESOLVING:
566   case CS_RINGING:
567   case CS_CONNECTED:
568     /* expected state, do rejection logic */
569     break;
570   }
571   if (NULL == call)
572   {
573     GNUNET_assert (NULL != phone);
574     GNUNET_CONVERSATION_phone_hang_up (phone, 
575                                        args);
576     state = CS_LISTEN;
577   }
578   else
579   {
580     GNUNET_CONVERSATION_call_stop (call, args);
581     call = NULL;
582     start_phone ();
583   }
584 }
585
586
587 /**
588  * List of supported commands.
589  */
590 static struct VoipCommand commands[] = {
591   {"/call", &do_call, 
592    gettext_noop ("Use `/call USER.gnu'")},
593   {"/accept", &do_accept,
594    gettext_noop ("Use `/accept MESSAGE' to accept an incoming call")},
595   {"/cancel", &do_reject,
596    gettext_noop ("Use `/cancel MESSAGE' to reject or terminate a call")},
597   {"/status", &do_status,
598    gettext_noop ("Use `/status to print status information")},
599   {"/quit", &do_quit, 
600    gettext_noop ("Use `/quit' to terminate gnunet-conversation")},
601   {"/help", &do_help,
602    gettext_noop ("Use `/help command' to get help for a specific command")},
603   {"", &do_unknown, 
604    NULL},
605   {NULL, NULL, NULL},
606 };
607
608
609 /**
610  * Action function to print help for the command shell.
611  *
612  * @param arguments arguments given to the command
613  */
614 static void
615 do_help (const char *args)
616 {
617   unsigned int i;
618   
619   i = 0; 
620   while ( (NULL != args) &&
621           (0 != strlen (args)) &&
622           (commands[i].Action != &do_help))
623   {
624     if (0 ==
625         strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
626     {
627       FPRINTF (stdout, 
628                "%s\n",
629                gettext (commands[i].helptext));
630       return;
631     }
632     i++;
633   }
634   i = 0;
635   FPRINTF (stdout, 
636            "%s", 
637            "Available commands:\n");
638   while (commands[i].Action != &do_help)
639   {
640     FPRINTF (stdout, 
641              "%s\n", 
642              gettext (commands[i].command));
643     i++;
644   }
645   FPRINTF (stdout,
646            "%s",
647            "\n");
648   FPRINTF (stdout,
649            "%s\n",
650            gettext (commands[i].helptext));
651 }
652
653
654 /**
655  * Task run during shutdown.
656  *
657  * @param cls NULL
658  * @param tc unused
659  */
660 static void
661 do_stop_task (void *cls,
662               const struct GNUNET_SCHEDULER_TaskContext *tc)
663 {
664   if (NULL != call)
665   {
666     GNUNET_CONVERSATION_call_stop (call, NULL);
667     call = NULL;
668   }
669   if (NULL != phone)
670   {
671     GNUNET_CONVERSATION_phone_destroy (phone);
672     phone = NULL;
673   }
674   if (GNUNET_SCHEDULER_NO_TASK != handle_cmd_task)
675   {
676     GNUNET_SCHEDULER_cancel (handle_cmd_task);
677     handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
678   } 
679   if (NULL != id)
680   {
681     GNUNET_IDENTITY_disconnect (id);
682     id = NULL;
683   }
684   GNUNET_SPEAKER_destroy (speaker);
685   speaker = NULL;
686   GNUNET_MICROPHONE_destroy (mic);
687   mic = NULL;
688   GNUNET_free (ego_name);
689   ego_name = NULL;
690   GNUNET_CONFIGURATION_destroy (cfg);
691   cfg = NULL;
692   GNUNET_free_non_null (peer_name);
693   state = CS_ERROR;
694 }
695
696
697 /**
698  * Task to handle commands from the terminal.
699  *
700  * @param cls NULL
701  * @param tc scheduler context
702  */
703 static void
704 handle_command (void *cls,
705                 const struct GNUNET_SCHEDULER_TaskContext *tc)
706 {
707   char message[MAX_MESSAGE_LENGTH + 1];
708   const char *ptr;
709   size_t i;
710
711   handle_cmd_task =
712     GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
713                                     stdin_fh,
714                                     &handle_command, NULL);
715   /* read message from command line and handle it */
716   memset (message, 0, MAX_MESSAGE_LENGTH + 1);
717   if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
718     return;
719   if (0 == strlen (message))
720     return;
721   if (message[strlen (message) - 1] == '\n')
722     message[strlen (message) - 1] = '\0';
723   if (0 == strlen (message))
724     return;
725   i = 0;
726   while ((NULL != commands[i].command) &&
727          (0 != strncasecmp (commands[i].command, message,
728                             strlen (commands[i].command))))
729     i++;
730   ptr = &message[strlen (commands[i].command)];
731   while (isspace ((int) *ptr))
732     ptr++;
733   commands[i].Action (ptr);
734 }
735
736
737 /**
738  * Function called by identity service with information about egos.
739  *
740  * @param cls NULL
741  * @param ego ego handle
742  * @param ctx unused
743  * @param name name of the ego
744  */
745 static void
746 identity_cb (void *cls,
747              struct GNUNET_IDENTITY_Ego *ego,
748              void **ctx,
749              const char *name)
750 {
751   if (NULL == name)
752     return;
753   if (ego == caller_id)
754   {
755     if (verbose)
756       FPRINTF (stdout,
757                _("Name of our ego changed to `%s'\n"),
758                name);
759     GNUNET_free (ego_name);
760     ego_name = GNUNET_strdup (name);
761     return;
762   }
763   if (0 != strcmp (name,
764                    ego_name))
765     return;
766   if (NULL == ego)
767   {    
768     if (verbose)
769       FPRINTF (stdout,
770                _("Our ego `%s' was deleted!\n"),
771                ego_name);
772     caller_id = NULL;
773     return;
774   }
775   caller_id = ego;
776   GNUNET_CONFIGURATION_set_value_number (cfg,
777                                          "CONVERSATION",
778                                          "LINE",
779                                          line);
780   start_phone ();
781 }
782
783
784 /**
785  * Main function that will be run by the scheduler.
786  *
787  * @param cls closure
788  * @param args remaining command-line arguments
789  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
790  * @param c configuration
791  */
792 static void
793 run (void *cls,
794      char *const *args, 
795      const char *cfgfile,
796      const struct GNUNET_CONFIGURATION_Handle *c)
797 {
798   cfg = GNUNET_CONFIGURATION_dup (c);
799   speaker = GNUNET_SPEAKER_create_from_hardware (cfg);
800   mic = GNUNET_MICROPHONE_create_from_hardware (cfg);
801   if (NULL == ego_name)
802   {
803     FPRINTF (stderr,
804              "%s",
805              _("You must specify the NAME of an ego to use\n"));
806     return;
807   }
808   id = GNUNET_IDENTITY_connect (cfg,
809                                 &identity_cb,
810                                 NULL);
811   handle_cmd_task =
812     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
813                                         &handle_command, NULL);
814   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_stop_task,
815                                 NULL);
816 }
817
818
819 /** 
820  * The main function to conversation.
821  *
822  * @param argc number of arguments from the command line
823  * @param argv command line arguments
824  * @return 0 ok, 1 on error
825  */
826 int
827 main (int argc, char *const *argv)
828 {
829   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
830     {'e', "ego", "NAME",
831      gettext_noop ("sets the NAME of the ego to use for the phone (and name resolution)"),
832      1, &GNUNET_GETOPT_set_string, &ego_name},
833     {'p', "phone", "LINE",
834       gettext_noop ("sets the LINE to use for the phone"),
835      1, &GNUNET_GETOPT_set_uint, &line},
836     GNUNET_GETOPT_OPTION_END
837   };
838   int flags;
839   int ret;
840
841   flags = fcntl (0, F_GETFL, 0);
842   flags |= O_NONBLOCK;
843   fcntl (0, F_SETFL, flags);
844   stdin_fh = GNUNET_DISK_get_handle_from_int_fd (0);
845   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
846     return 2;
847   ret = GNUNET_PROGRAM_run (argc, argv,
848                             "gnunet-conversation",
849                             gettext_noop ("Enables having a conversation with other GNUnet users."),
850                             options, &run, NULL);
851   GNUNET_free ((void *) argv);
852   return (GNUNET_OK == ret) ? 0 : 1;
853 }
854
855 /* end of gnunet-conversation.c */