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