fixes
[oweals/gnunet.git] / src / social / gnunet-social.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2016 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * CLI tool to interact with the social service.
23  *
24  * @author Gabor X Toth
25  */
26
27 #include <inttypes.h>
28
29 #include "platform.h"
30 #include "gnunet_util_lib.h"
31 #include "gnunet_social_service.h"
32 #include "gnunet_core_service.h"
33
34 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
35
36 #define DATA2ARG(data) data, sizeof (data)
37
38 /* operations corresponding to API calls */
39
40 /** --status */
41 static int op_status;
42
43 /** --host-enter */
44 static int op_host_enter;
45
46 /** --host-reconnect */
47 static int op_host_reconnect;
48
49 /** --host-leave */
50 static int op_host_leave;
51
52 /** --host-announce */
53 static int op_host_announce;
54
55 /** --host-assign */
56 static int op_host_assign;
57
58 /** --guest-enter */
59 static int op_guest_enter;
60
61 /** --guest-reconnect */
62 static int op_guest_reconnect;
63
64 /** --guest-leave */
65 static int op_guest_leave;
66
67 /** --guest-talk */
68 static int op_guest_talk;
69
70 /** --replay */
71 static char *op_replay;
72
73 /** --replay-latest */
74 static char *op_replay_latest;
75
76 /** --look-at */
77 static int op_look_at;
78
79 /** --look-for */
80 static int op_look_for;
81
82
83 /* options */
84
85 /** --app */
86 static char *opt_app = "cli";
87
88 /** --place */
89 static char *opt_place;
90
91 /** --ego */
92 static char *opt_ego;
93
94 /** --gns */
95 static char *opt_gns;
96
97 /** --peer */
98 static char *opt_peer;
99
100 /** --follow */
101 static int opt_follow;
102
103 /** --welcome */
104 static int opt_welcome;
105
106 /** --deny */
107 static int opt_deny;
108
109 /** --method */
110 static char *opt_method;
111
112 /** --data */
113 // FIXME: should come from STDIN
114 static char *opt_data;
115
116 /** --name */
117 static char *opt_name;
118
119 /** --start */
120 static uint64_t opt_start;
121
122 /** --until */
123 static uint64_t opt_until;
124
125 /** --limit */
126 static int opt_limit;
127
128
129 /* global vars */
130
131 /** exit code */
132 static int ret = 1;
133
134 /** Task handle for timeout termination. */
135 struct GNUNET_SCHEDULER_Task *timeout_task;
136
137 const struct GNUNET_CONFIGURATION_Handle *cfg;
138
139 struct GNUNET_CORE_Handle *core;
140 struct GNUNET_PeerIdentity peer, this_peer;
141
142 struct GNUNET_SOCIAL_App *app;
143
144 /** public key of connected place */
145 struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key;
146
147 struct GNUNET_PSYC_Slicer *slicer;
148
149 struct GNUNET_SOCIAL_Ego *ego;
150 struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key;
151
152 struct GNUNET_SOCIAL_Host *hst;
153 struct GNUNET_SOCIAL_Guest *gst;
154 struct GNUNET_SOCIAL_Place *plc;
155
156
157 /* DISCONNECT */
158
159
160 /**
161  * Callback called after the host or guest place disconnected.
162  */
163 static void
164 disconnected (void *cls)
165 {
166   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnected()\n");
167   GNUNET_SCHEDULER_shutdown ();
168 }
169
170
171 /**
172  * Callback called after the application disconnected.
173  */
174 static void
175 app_disconnected (void *cls)
176 {
177   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "app_disconnected()\n");
178   if (hst || gst)
179   {
180     if (hst)
181     {
182       GNUNET_SOCIAL_host_disconnect (hst, disconnected, NULL);
183     }
184     if (gst)
185     {
186       GNUNET_SOCIAL_guest_disconnect (gst, disconnected, NULL);
187     }
188   }
189   else
190   {
191     GNUNET_SCHEDULER_shutdown ();
192   }
193 }
194
195
196 /**
197  * Disconnect from connected GNUnet services.
198  */
199 static void
200 disconnect ()
201 {
202   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnect()\n");
203   GNUNET_SOCIAL_app_disconnect (app, app_disconnected, NULL);
204 }
205
206
207 /**
208  * Callback called when the program failed to finish the requested operation in time.
209  */
210 static void
211 timeout (void *cls)
212 {
213   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "timeout()\n");
214   disconnect ();
215 }
216
217 static void
218 schedule_success (void *cls)
219 {
220   ret = 0;
221   disconnect ();
222 }
223
224
225 static void
226 schedule_fail (void *cls)
227 {
228   disconnect ();
229 }
230
231
232 /**
233  * Schedule exit with success result.
234  */
235 static void
236 exit_success ()
237 {
238   if (timeout_task != NULL)
239   {
240     GNUNET_SCHEDULER_cancel (timeout_task);
241     timeout_task = NULL;
242   }
243   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, schedule_success, NULL);
244 }
245
246
247 /**
248  * Schedule exit with failure result.
249  */
250 static void
251 exit_fail ()
252 {
253   if (timeout_task != NULL)
254   {
255     GNUNET_SCHEDULER_cancel (timeout_task);
256     timeout_task = NULL;
257   }
258   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, schedule_fail, NULL);
259 }
260
261
262 /* LEAVE */
263
264
265 /**
266  * Callback notifying about the host has left and stopped hosting the place.
267  *
268  * This also indicates the end of the connection to the service.
269  */
270 static void
271 host_left ()
272 {
273   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
274               "The host has left the place.\n");
275   exit_success ();
276 }
277
278
279 /**
280  * Leave a place permanently and stop hosting a place.
281  */
282 static void
283 host_leave ()
284 {
285   GNUNET_SOCIAL_host_leave (hst, NULL, host_left, NULL);
286   hst = NULL;
287   plc = NULL;
288 }
289
290
291 /**
292  * Callback notifying about the guest has left the place.
293  *
294  * This also indicates the end of the connection to the service.
295  */
296 static void
297 guest_left (void *cls)
298 {
299   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
300               "The guest has left the place.\n");
301 }
302
303
304 /**
305  * Leave a place permanently as guest.
306  */
307 static void
308 guest_leave ()
309 {
310   struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
311   // FIXME: wrong use of vars
312   GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET,
313                        "_message", DATA2ARG ("Leaving."));
314   GNUNET_SOCIAL_guest_leave (gst, env, guest_left, NULL);
315   GNUNET_PSYC_env_destroy (env);
316   gst = NULL;
317   plc = NULL;
318 }
319
320
321 /* ANNOUNCE / ASSIGN / TALK */
322
323
324 struct TransmitClosure
325 {
326   const char *data;
327   size_t size;
328 } tmit;
329
330
331 /**
332  * Callback notifying about available buffer space to write message data
333  * when transmitting messages using host_announce() or guest_talk()
334  */
335 static int
336 notify_data (void *cls, uint16_t *data_size, void *data)
337 {
338   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
339               "Transmit notify data: %u bytes available\n",
340               *data_size);
341
342   struct TransmitClosure *tmit = cls;
343   uint16_t size = tmit->size < *data_size ? tmit->size : *data_size;
344   *data_size = size;
345   memcpy (data, tmit->data, size);
346
347   tmit->size -= size;
348   tmit->data += size;
349
350   if (0 == tmit->size)
351   {
352     if (op_host_announce || op_host_assign || op_guest_talk)
353     {
354       exit_success ();
355     }
356     return GNUNET_YES;
357   }
358   else
359   {
360     return GNUNET_NO;
361   }
362 }
363
364
365 /**
366  * Host announcement - send a message to the place.
367  */
368 static void
369 host_announce (const char *method, const char *data, size_t data_size)
370 {
371   struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
372   GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET,
373                        "_foo", DATA2ARG ("bar baz"));
374
375   tmit = (struct TransmitClosure) {};
376   tmit.data = data;
377   tmit.size = data_size;
378
379   GNUNET_SOCIAL_host_announce (hst, method, env,
380                                notify_data, &tmit,
381                                GNUNET_SOCIAL_ANNOUNCE_NONE);
382 }
383
384
385 /**
386  * Assign a state var of @a name to the value of @a data.
387  */
388 static void
389 host_assign (const char *name, const char *data, size_t data_size)
390 {
391   struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
392   GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN,
393                        name, data, data_size);
394
395   tmit = (struct TransmitClosure) {};
396   GNUNET_SOCIAL_host_announce (hst, "_assign", env,
397                                notify_data, &tmit,
398                                GNUNET_SOCIAL_ANNOUNCE_NONE);
399 }
400
401
402 /**
403  * Guest talk request to host.
404  */
405 static void
406 guest_talk (const char *method,
407             const char *data, size_t data_size)
408 {
409   struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
410   GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET,
411                        "_foo", DATA2ARG ("bar baz"));
412
413   tmit = (struct TransmitClosure) {};
414   tmit.data = data;
415   tmit.size = data_size;
416
417   GNUNET_SOCIAL_guest_talk (gst, method, env,
418                             notify_data, &tmit,
419                             GNUNET_SOCIAL_TALK_NONE);
420 }
421
422
423 /* HISTORY REPLAY */
424
425
426 /**
427  * Callback notifying about the end of history replay results.
428  */
429 static void
430 recv_history_replay_result (void *cls, int64_t result,
431                             const void *data, uint16_t data_size)
432 {
433   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
434               "Received history replay result: %" PRId64 "\n"
435               "%.*s\n",
436               result, data_size, (const char *) data);
437
438   if (op_replay || op_replay_latest)
439   {
440     exit_success ();
441   }
442 }
443
444
445 /**
446  * Replay history between a given @a start and @a end message IDs,
447  * optionally filtered by a method @a prefix.
448  */
449 static void
450 history_replay (uint64_t start, uint64_t end, const char *prefix)
451 {
452   GNUNET_SOCIAL_place_history_replay (plc, start, end, prefix,
453                                       GNUNET_PSYC_HISTORY_REPLAY_LOCAL,
454                                       slicer,
455                                       recv_history_replay_result,
456                                       NULL);
457 }
458
459
460 /**
461  * Replay latest @a limit messages.
462  */
463 static void
464 history_replay_latest (uint64_t limit, const char *prefix)
465 {
466   GNUNET_SOCIAL_place_history_replay_latest (plc, limit, prefix,
467                                              GNUNET_PSYC_HISTORY_REPLAY_LOCAL,
468                                              slicer,
469                                              recv_history_replay_result,
470                                              NULL);
471 }
472
473
474 /* LOOK AT/FOR */
475
476
477 /**
478  * Callback notifying about the end of state var results.
479  */
480 static void
481 look_result (void *cls, int64_t result_code,
482              const void *data, uint16_t data_size)
483 {
484   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
485               "Received look result: %" PRId64 "\n", result_code);
486
487   if (op_look_at || op_look_for)
488   {
489     exit_success ();
490   }
491 }
492
493
494 /**
495  * Callback notifying about a state var result.
496  */
497 static void
498 look_var (void *cls,
499           const struct GNUNET_MessageHeader *mod,
500           const char *name,
501           const void *value,
502           uint32_t value_size,
503           uint32_t full_value_size)
504 {
505   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
506               "Received var: %s\n%.*s\n",
507               name, value_size, (const char *) value);
508 }
509
510
511 /**
512  * Look for a state var using exact match of the name.
513  */
514 static void
515 look_at (const char *full_name)
516 {
517   GNUNET_SOCIAL_place_look_at (plc, full_name, look_var, look_result, NULL);
518 }
519
520
521 /**
522  * Look for state vars by name prefix.
523  */
524 static void
525 look_for (const char *name_prefix)
526 {
527   GNUNET_SOCIAL_place_look_for (plc, name_prefix, look_var, look_result, NULL);
528 }
529
530
531 /* SLICER */
532
533
534 /**
535  * Callback notifying about the start of a new incoming message.
536  */
537 static void
538 slicer_recv_method (void *cls,
539                     const struct GNUNET_PSYC_MessageHeader *msg,
540                     const struct GNUNET_PSYC_MessageMethod *meth,
541                     uint64_t message_id,
542                     const char *method_name)
543 {
544   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
545               "Received method for message ID %" PRIu64 ":\n"
546               "%s (flags: %x)\n",
547               message_id, method_name, ntohl (meth->flags));
548 }
549
550
551 /**
552  * Callback notifying about an incoming modifier.
553  */
554 static void
555 slicer_recv_modifier (void *cls,
556                       const struct GNUNET_PSYC_MessageHeader *msg,
557                       const struct GNUNET_MessageHeader *pmsg,
558                       uint64_t message_id,
559                       enum GNUNET_PSYC_Operator oper,
560                       const char *name,
561                       const void *value,
562                       uint16_t value_size,
563                       uint16_t full_value_size)
564 {
565   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
566               "Received modifier for message ID %" PRIu64 ":\n"
567               "%c%s: %.*s (size: %u)\n",
568               message_id, oper, name, value_size, (const char *) value, value_size);
569 }
570
571
572 /**
573  * Callback notifying about an incoming data fragment.
574  */
575 static void
576 slicer_recv_data (void *cls,
577                   const struct GNUNET_PSYC_MessageHeader *msg,
578                   const struct GNUNET_MessageHeader *pmsg,
579                   uint64_t message_id,
580                   const void *data,
581                   uint16_t data_size)
582 {
583   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
584               "Received data for message ID %" PRIu64 ":\n"
585               "%.*s\n",
586               message_id, data_size, (const char *) data);
587 }
588
589
590 /**
591  * Callback notifying about the end of a message.
592  */
593 static void
594 slicer_recv_eom (void *cls,
595                 const struct GNUNET_PSYC_MessageHeader *msg,
596                 const struct GNUNET_MessageHeader *pmsg,
597                 uint64_t message_id,
598                 uint8_t is_cancelled)
599 {
600   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
601               "Received end of message ID %" PRIu64
602               ", cancelled: %u\n",
603               message_id, is_cancelled);
604 }
605
606
607 /**
608  * Create a slicer for receiving message parts.
609  */
610 static struct GNUNET_PSYC_Slicer *
611 slicer_create ()
612 {
613   slicer = GNUNET_PSYC_slicer_create ();
614
615   /* register slicer to receive incoming messages with any method name */
616   GNUNET_PSYC_slicer_method_add (slicer, "", NULL,
617                                  slicer_recv_method, slicer_recv_modifier,
618                                  slicer_recv_data, slicer_recv_eom, NULL);
619   return slicer;
620 }
621
622
623 /* GUEST ENTER */
624
625
626 /**
627  * Callback called when the guest receives an entry decision from the host.
628  *
629  * It is called once after using guest_enter() or guest_enter_by_name(),
630  * in case of a reconnection only the local enter callback is called.
631  */
632 static void
633 guest_recv_entry_decision (void *cls,
634                            int is_admitted,
635                            const struct GNUNET_PSYC_Message *entry_msg)
636 {
637   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
638               "Guest received entry decision %d\n",
639               is_admitted);
640
641   if (NULL != entry_msg)
642   {
643     struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
644     const char *method_name = NULL;
645     const void *data = NULL;
646     uint16_t data_size = 0;
647     struct GNUNET_PSYC_MessageHeader *
648       pmsg = GNUNET_PSYC_message_header_create_from_psyc (entry_msg);
649     GNUNET_PSYC_message_parse (pmsg, &method_name, env, &data, &data_size);
650     GNUNET_free (pmsg);
651
652     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
653                 "%s\n%.*s\n",
654                 method_name, data_size, (const char *) data);
655   }
656
657   if (op_guest_enter && !opt_follow)
658   {
659     exit_success ();
660   }
661 }
662
663
664 /**
665  * Callback called after a guest connection is established to the local service.
666  */
667 static void
668 guest_recv_local_enter (void *cls, int result,
669                         const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key,
670                         uint64_t max_message_id)
671 {
672   char *pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (pub_key);
673   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
674               "Guest entered to local place: %s, max_message_id: %" PRIu64 "\n",
675               pub_str, max_message_id);
676   GNUNET_assert (0 <= result);
677
678   if (op_guest_enter && !opt_follow)
679   {
680     exit_success ();
681   }
682 }
683
684
685 /**
686  * Create entry requset message.
687  */
688 static struct GNUNET_PSYC_Message *
689 guest_enter_msg_create ()
690 {
691   const char *method_name = "_request_enter";
692   struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create ();
693   GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET,
694                        "_foo", DATA2ARG ("bar"));
695   void *data = "let me in";
696   uint16_t data_size = strlen (data) + 1;
697
698   return GNUNET_PSYC_message_create (method_name, env, data, data_size);
699 }
700
701
702 /**
703  * Enter a place as guest, using its public key and peer ID.
704  */
705 static void
706 guest_enter (const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key,
707              const struct GNUNET_PeerIdentity *peer)
708 {
709   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
710               "Entering to place as guest.\n");
711
712   if (NULL == ego)
713   {
714     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "--ego missing or invalid\n");
715     exit_fail ();
716     return;
717   }
718
719   gst = GNUNET_SOCIAL_guest_enter (app, ego, pub_key,
720                                    GNUNET_PSYC_SLAVE_JOIN_NONE,
721                                    peer, 0, NULL, guest_enter_msg_create (),
722                                    slicer_create (),
723                                    guest_recv_local_enter,
724                                    guest_recv_entry_decision, NULL);
725   plc = GNUNET_SOCIAL_guest_get_place (gst);
726 }
727
728
729 /**
730  * Enter a place as guest using its GNS address.
731  */
732 static void
733 guest_enter_by_name (const char *gns_name)
734 {
735   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
736               "Entering to place by name as guest.\n");
737
738   gst = GNUNET_SOCIAL_guest_enter_by_name (app, ego, gns_name, NULL,
739                                            guest_enter_msg_create (), slicer,
740                                            guest_recv_local_enter,
741                                            guest_recv_entry_decision, NULL);
742   plc = GNUNET_SOCIAL_guest_get_place (gst);
743 }
744
745
746 /* HOST ENTER */
747
748
749 /**
750  * Callback called when a @a nym wants to enter the place.
751  *
752  * The request needs to be replied with an entry decision.
753  */
754 static void
755 host_answer_door (void *cls,
756                   struct GNUNET_SOCIAL_Nym *nym,
757                   const char *method_name,
758                   struct GNUNET_PSYC_Environment *env,
759                   const void *data,
760                   size_t data_size)
761 {
762   const struct GNUNET_CRYPTO_EcdsaPublicKey *
763     nym_key = GNUNET_SOCIAL_nym_get_pub_key (nym);
764   char *
765     nym_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key);
766
767   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
768               "Entry request: %s\n", nym_str);
769   GNUNET_free (nym_str);
770
771   if (opt_welcome)
772   {
773     struct GNUNET_PSYC_Message *
774       resp = GNUNET_PSYC_message_create ("_notice_place_admit", env,
775                                          DATA2ARG ("Welcome, nym!"));
776     GNUNET_SOCIAL_host_entry_decision (hst, nym, GNUNET_YES, resp);
777     GNUNET_free (resp);
778   }
779   else if (opt_deny)
780   {
781     struct GNUNET_PSYC_Message *
782       resp = GNUNET_PSYC_message_create ("_notice_place_refuse", NULL,
783                                          DATA2ARG ("Go away!"));
784     GNUNET_SOCIAL_host_entry_decision (hst, nym, GNUNET_NO, resp);
785     GNUNET_free (resp);
786   }
787
788
789 }
790
791
792 /**
793  * Callback called when a @a nym has left the place.
794  */
795 static void
796 host_farewell (void *cls,
797                const struct GNUNET_SOCIAL_Nym *nym,
798                struct GNUNET_PSYC_Environment *env)
799 {
800   const struct GNUNET_CRYPTO_EcdsaPublicKey *
801     nym_key = GNUNET_SOCIAL_nym_get_pub_key (nym);
802   char *
803     nym_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key);
804
805   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
806               "Farewell: %s\n", nym_str);
807   GNUNET_free (nym_str);
808 }
809
810
811 /**
812  * Callback called after the host entered the place.
813  */
814 static void
815 host_entered (void *cls, int result,
816               const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key,
817               uint64_t max_message_id)
818 {
819   place_pub_key = *pub_key;
820   char *pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (pub_key);
821   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
822               "Host entered: %s, max_message_id: %" PRIu64 "\n",
823               pub_str, max_message_id);
824
825   if (op_host_enter && !opt_follow)
826   {
827     exit_success ();
828   }
829 }
830
831
832 /**
833  * Enter and start hosting a place.
834  */
835 static void
836 host_enter ()
837 {
838   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "host_enter()\n");
839
840   if (NULL == ego)
841   {
842     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "--ego missing or invalid\n");
843     exit_fail ();
844     return;
845   }
846
847   hst = GNUNET_SOCIAL_host_enter (app, ego,
848                                   GNUNET_PSYC_CHANNEL_PRIVATE,
849                                   slicer_create (), host_entered,
850                                   host_answer_door, host_farewell, NULL);
851   plc = GNUNET_SOCIAL_host_get_place (hst);
852 }
853
854
855 /* PLACE RECONNECT */
856
857
858 /**
859  * Perform operations common to both host & guest places.
860  */
861 static void
862 place_reconnected ()
863 {
864   static int first_run = GNUNET_YES;
865   if (GNUNET_NO == first_run)
866     return;
867   first_run = GNUNET_NO;
868
869   if (op_replay) {
870     history_replay (opt_start, opt_until, opt_method);
871   }
872   else if (op_replay_latest) {
873     history_replay_latest (opt_limit, opt_method);
874   }
875   else if (op_look_at) {
876     look_at (opt_name);
877   }
878   else if (op_look_for) {
879     look_for (opt_name);
880   }
881 }
882
883
884 /**
885  * Callback called after reconnecting to a host place.
886  */
887 static void
888 host_reconnected (void *cls, int result,
889                   const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
890                   uint64_t max_message_id)
891 {
892   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
893               "Host reconnected.\n");
894
895   if (op_host_leave) {
896     host_leave ();
897   }
898   else if (op_host_announce) {
899     host_announce (opt_method, opt_data, strlen (opt_data));
900   }
901   else if (op_host_assign) {
902     host_assign (opt_name, opt_data, strlen (opt_data) + 1);
903   }
904   else {
905     place_reconnected ();
906   }
907 }
908
909
910 /**
911  * Callback called after reconnecting to a guest place.
912  */
913 static void
914 guest_reconnected (void *cls, int result,
915                    const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key,
916                    uint64_t max_message_id)
917 {
918   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
919               "Guest reconnected.\n");
920
921   if (op_guest_leave) {
922     guest_leave ();
923   }
924   else if (op_guest_talk) {
925     guest_talk (opt_method, opt_data, strlen (opt_data));
926   }
927   else {
928     place_reconnected ();
929   }
930 }
931
932
933 /* APP */
934
935
936 /**
937  * Callback called after the ego and place callbacks.
938  */
939 static void
940 app_connected (void *cls)
941 {
942   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
943               "App connected: %p\n", cls);
944
945   if (op_status)
946   {
947     exit_success ();
948   }
949   else if (op_host_enter)
950   {
951     host_enter ();
952   }
953   else if (op_guest_enter)
954   {
955     if (opt_gns)
956     {
957       guest_enter_by_name (opt_gns);
958     }
959     else
960     {
961       if (opt_peer)
962       {
963         if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (opt_peer,
964                                                                      strlen (opt_peer),
965                                                                      &peer.public_key))
966         {
967           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
968                       "--peer invalid");
969           exit_fail ();
970           return;
971         }
972       }
973       else
974       {
975         peer = this_peer;
976       }
977       guest_enter (&place_pub_key, &peer);
978     }
979   }
980 }
981
982
983 /**
984  * Callback notifying about a host place available for reconnection.
985  */
986 static void
987 app_recv_host (void *cls,
988                struct GNUNET_SOCIAL_HostConnection *hconn,
989                struct GNUNET_SOCIAL_Ego *ego,
990                const struct GNUNET_CRYPTO_EddsaPublicKey *host_pub_key,
991                enum GNUNET_SOCIAL_AppPlaceState place_state)
992 {
993   char *host_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (host_pub_key);
994   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
995               "Host:  %s\n", host_pub_str);
996   GNUNET_free (host_pub_str);
997
998   if ((op_host_reconnect || op_host_leave || op_host_announce || op_host_assign
999        || op_replay || op_replay_latest
1000        || op_look_at || op_look_for)
1001       && 0 == memcmp (&place_pub_key, host_pub_key, sizeof (*host_pub_key)))
1002   {
1003     hst = GNUNET_SOCIAL_host_enter_reconnect (hconn, slicer_create (), host_reconnected,
1004                                               host_answer_door, host_farewell, NULL);
1005     plc = GNUNET_SOCIAL_host_get_place (hst);
1006   }
1007 }
1008
1009
1010 /**
1011  * Callback notifying about a guest place available for reconnection.
1012  */
1013 static void
1014 app_recv_guest (void *cls,
1015                 struct GNUNET_SOCIAL_GuestConnection *gconn,
1016                 struct GNUNET_SOCIAL_Ego *ego,
1017                 const struct GNUNET_CRYPTO_EddsaPublicKey *guest_pub_key,
1018                 enum GNUNET_SOCIAL_AppPlaceState place_state)
1019 {
1020   char *guest_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (guest_pub_key);
1021   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1022               "Guest: %s\n", guest_pub_str);
1023   GNUNET_free (guest_pub_str);
1024
1025   if ((op_guest_reconnect || op_guest_leave || op_guest_talk
1026        || op_replay || op_replay_latest
1027        || op_look_at || op_look_for)
1028       && 0 == memcmp (&place_pub_key, guest_pub_key, sizeof (*guest_pub_key)))
1029   {
1030     gst = GNUNET_SOCIAL_guest_enter_reconnect (gconn, GNUNET_PSYC_SLAVE_JOIN_NONE,
1031                                                slicer_create (), guest_reconnected, NULL);
1032     plc = GNUNET_SOCIAL_guest_get_place (gst);
1033   }
1034 }
1035
1036
1037 /**
1038  * Callback notifying about an available ego.
1039  */
1040 static void
1041 app_recv_ego (void *cls,
1042               struct GNUNET_SOCIAL_Ego *e,
1043               const struct GNUNET_CRYPTO_EcdsaPublicKey *pub_key,
1044               const char *name)
1045 {
1046   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1047               "Ego:   %s\t%s\n",
1048               GNUNET_CRYPTO_ecdsa_public_key_to_string (pub_key), name);
1049
1050   if (0 == memcmp (&ego_pub_key, pub_key, sizeof (*pub_key))
1051       || (NULL != opt_ego && 0 == strcmp (opt_ego, name)))
1052   {
1053     ego = e;
1054   }
1055
1056 }
1057
1058
1059
1060 /**
1061  * Establish application connection to receive available egos and places.
1062  */
1063 static void
1064 app_connect (void *cls)
1065 {
1066   GNUNET_CORE_disconnect (core);
1067   core = NULL;
1068
1069   app = GNUNET_SOCIAL_app_connect (cfg, opt_app,
1070                                    app_recv_ego,
1071                                    app_recv_host,
1072                                    app_recv_guest,
1073                                    app_connected,
1074                                    NULL);
1075 }
1076
1077
1078 /* CORE */
1079
1080
1081 static void
1082 core_connected (void *cls, const struct GNUNET_PeerIdentity *my_identity)
1083 {
1084   this_peer = *my_identity;
1085   GNUNET_SCHEDULER_add_now (app_connect, NULL);
1086 }
1087
1088
1089 /* RUN */
1090
1091
1092 /**
1093  * Main function run by the scheduler.
1094  *
1095  * @param cls closure
1096  * @param args remaining command-line arguments
1097  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1098  * @param cfg configuration
1099  */
1100 static void
1101 run (void *cls, char *const *args, const char *cfgfile,
1102      const struct GNUNET_CONFIGURATION_Handle *c)
1103 {
1104 //  GNUNET_SIGNAL_handler_install (SIGINT, disconnect);
1105 //  GNUNET_SIGNAL_handler_install (SIGTERM, disconnect);
1106 //  GNUNET_SIGNAL_handler_install (SIGKILL, disconnect);
1107
1108   cfg = c;
1109
1110   if (!opt_method)
1111     opt_method = "message";
1112   if (!opt_data)
1113     opt_data = "";
1114   if (!opt_name)
1115     opt_name = "";
1116
1117   if (! (op_status
1118          || op_host_enter || op_host_reconnect || op_host_leave
1119          || op_host_announce || op_host_assign
1120          || op_guest_enter || op_guest_reconnect
1121          || op_guest_leave || op_guest_talk
1122          || op_replay || op_replay_latest
1123          || op_look_at || op_look_for))
1124   {
1125     op_status = 1;
1126   }
1127
1128   if (!opt_follow)
1129   {
1130     timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, timeout, NULL);
1131   }
1132
1133   if ((op_host_reconnect || op_host_leave || op_host_announce || op_host_assign
1134        || op_guest_reconnect || (op_guest_enter && !opt_gns)
1135        || op_guest_leave || op_guest_talk
1136        || op_replay || op_replay_latest
1137        || op_look_at || op_look_for)
1138       && (!opt_place
1139           || GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (opt_place,
1140                                                                       strlen (opt_place),
1141                                                                       &place_pub_key)))
1142   {
1143     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1144                 _("--place missing or invalid.\n"));
1145     exit_fail ();
1146     return;
1147   }
1148
1149   if (opt_ego)
1150   {
1151     GNUNET_assert (GNUNET_OK ==
1152                    GNUNET_CRYPTO_ecdsa_public_key_from_string (opt_ego,
1153                                                                strlen (opt_ego),
1154                                                                &ego_pub_key));
1155   }
1156
1157   core = GNUNET_CORE_connect (cfg, NULL, &core_connected, NULL, NULL,
1158                               NULL, GNUNET_NO, NULL, GNUNET_NO, NULL);
1159 }
1160
1161
1162 /**
1163  * The main function to obtain peer information.
1164  *
1165  * @param argc number of arguments from the command line
1166  * @param argv command line arguments
1167  * @return 0 ok, 1 on error
1168  */
1169 int
1170 main (int argc, char *const *argv)
1171 {
1172   int res;
1173   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
1174     /*
1175      * gnunet program options in addition to the ones below:
1176      *
1177      * -c, --config=FILENAME
1178      * -l, --logfile=LOGFILE
1179      * -L, --log=LOGLEVEL
1180      * -h, --help
1181      * -v, --version
1182      */
1183
1184     /* operations */
1185
1186     { 'A', "host-assign", NULL,
1187       gettext_noop ("assign --name in state to --data"),
1188       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_host_assign },
1189
1190     { 'B', "guest-leave", NULL,
1191       gettext_noop ("say good-bye and leave somebody else's place"),
1192       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_guest_leave },
1193
1194     { 'C', "host-enter", NULL,
1195       gettext_noop ("create a place"),
1196       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_host_enter },
1197
1198     { 'D', "host-leave", NULL,
1199       gettext_noop ("destroy a place we were hosting"),
1200       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_host_leave },
1201
1202     { 'E', "guest-enter", NULL,
1203       gettext_noop ("enter somebody else's place"),
1204       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_guest_enter },
1205
1206     { 'F', "look-for", NULL,
1207       gettext_noop ("find state matching name prefix"),
1208       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_look_for },
1209
1210     { 'H', "replay-latest", NULL,
1211       gettext_noop ("replay history of messages up to the given --limit"),
1212       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_replay_latest },
1213
1214     { 'N', "host-reconnect", NULL,
1215       gettext_noop ("reconnect to a previously created place"),
1216       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_host_reconnect },
1217
1218     { 'P', "host-announce", NULL,
1219       gettext_noop ("publish something to a place we are hosting"),
1220       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_host_announce },
1221
1222     { 'R', "guest-reconnect", NULL,
1223       gettext_noop ("reconnect to a previously entered place"),
1224       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_guest_reconnect },
1225
1226     { 'S', "look-at", NULL,
1227       gettext_noop ("search for state matching exact name"),
1228       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_look_at },
1229
1230     { 'T', "guest-talk", NULL,
1231       gettext_noop ("submit something to somebody's place"),
1232       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_guest_talk },
1233
1234     { 'U', "status", NULL,
1235       gettext_noop ("list of egos and subscribed places"),
1236       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_status },
1237
1238     { 'X', "replay", NULL,
1239       gettext_noop ("extract and replay history between message IDs --start and --until"),
1240       GNUNET_NO, &GNUNET_GETOPT_set_one, &op_replay },
1241
1242
1243     /* options */
1244
1245     { 'a', "app", "APPLICATION_ID",
1246       gettext_noop ("application ID to use when connecting"),
1247       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_app },
1248
1249     { 'd', "data", "DATA",
1250       gettext_noop ("message body or state value"),
1251       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_data },
1252
1253     { 'e', "ego", "NAME|PUBKEY",
1254       gettext_noop ("name or public key of ego"),
1255       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_ego },
1256
1257     { 'f', "follow", NULL,
1258       gettext_noop ("wait for incoming messages"),
1259       GNUNET_NO, &GNUNET_GETOPT_set_one, &opt_follow },
1260
1261     { 'g', "gns", "GNS_NAME",
1262       gettext_noop ("GNS name"),
1263       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_gns },
1264
1265     { 'i', "peer", "PEER_ID",
1266       gettext_noop ("peer ID for --guest-enter"),
1267       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_peer },
1268
1269     { 'k', "name", "VAR_NAME",
1270       gettext_noop ("name (key) to query from state"),
1271       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_name },
1272
1273     { 'm', "method", "METHOD_NAME",
1274       gettext_noop ("method name"),
1275       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_method },
1276
1277     { 'n', "limit", NULL,
1278       gettext_noop ("number of messages to replay from history"),
1279       GNUNET_YES, &GNUNET_GETOPT_set_ulong, &opt_limit },
1280
1281     { 'p', "place", "PUBKEY",
1282       gettext_noop ("key address of place"),
1283       GNUNET_YES, &GNUNET_GETOPT_set_string, &opt_place },
1284
1285     { 's', "start", NULL,
1286       gettext_noop ("start message ID for history replay"),
1287       GNUNET_YES, &GNUNET_GETOPT_set_ulong, &opt_start },
1288
1289     { 'w', "welcome", NULL,
1290       gettext_noop ("respond to entry requests by admitting all guests"),
1291       GNUNET_NO, &GNUNET_GETOPT_set_one, &opt_welcome },
1292
1293     { 'u', "until", NULL,
1294       gettext_noop ("end message ID for history replay"),
1295       GNUNET_YES, &GNUNET_GETOPT_set_ulong, &opt_until },
1296
1297     { 'y', "deny", NULL,
1298       gettext_noop ("respond to entry requests by refusing all guests"),
1299       GNUNET_NO, &GNUNET_GETOPT_set_one, &opt_deny },
1300
1301     GNUNET_GETOPT_OPTION_END
1302   };
1303
1304   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
1305     return 2;
1306
1307   const char *help =
1308     _ ("gnunet-social - Interact with the social service: enter/leave, send/receive messages, access history and state.\n");
1309   const char *usage =
1310     "gnunet-social [--status]\n"
1311     "\n"
1312     "gnunet-social --host-enter --ego <NAME or PUBKEY> [--follow] [--welcome | --deny]\n"
1313     "gnunet-social --host-reconnect --place <PUBKEY> [--follow] [--welcome | --deny]\n"
1314     "gnunet-social --host-leave --place <PUBKEY>\n"
1315     "gnunet-social --host-assign --place <PUBKEY> --name <NAME> --data <VALUE>\n"
1316 // FIXME: some state ops not implemented yet (no hurry)
1317 //  "gnunet-social --host-augment --place <PUBKEY> --name <NAME> --data <VALUE>\n"
1318 //  "gnunet-social --host-diminish --place <PUBKEY> --name <NAME> --data <VALUE>\n"
1319 //  "gnunet-social --host-set --place <PUBKEY> --name <NAME> --data <VALUE>\n"
1320     "gnunet-social --host-announce --place <PUBKEY> --method <METHOD_NAME> --data <MESSAGE_BODY>\n"
1321     "\n"
1322     "gnunet-social --guest-enter --place <PUBKEY> --peer <PEERID> --ego <NAME or PUBKEY> [--follow]\n"
1323     "gnunet-social --guest-enter --gns <GNS_NAME> --ego <NAME or PUBKEY> [--follow]\n"
1324     "gnunet-social --guest-reconnect --place <PUBKEY> [--follow]\n"
1325     "gnunet-social --guest-leave --place <PUBKEY>\n"
1326     "gnunet-social --guest-talk --place <PUBKEY> --method <METHOD_NAME> --data <MESSAGE_BODY>\n"
1327     "\n"
1328     "gnunet-social --history-replay --place <PUBKEY> --start <MSGID> --until <MSGID>  [--method <METHOD_PREFIX>]\n"
1329     "gnunet-social --history-replay-latest --place <PUBKEY> --limit <MSG_LIMIT> [--method <METHOD_PREFIX>]\n"
1330     "\n"
1331     "gnunet-social --look-at --place <PUBKEY> --name <FULL_NAME>\n"
1332     "gnunet-social --look-for --place <PUBKEY> --name <NAME_PREFIX>\n";
1333
1334   res = GNUNET_PROGRAM_run (argc, argv, help, usage, options, &run, NULL);
1335
1336   GNUNET_free ((void *) argv);
1337
1338   if (GNUNET_OK == res)
1339     return ret;
1340   else
1341     return 1;
1342 }