]> git.meshlink.io Git - meshlink-tiny/blob - examples/groupchat.c
Add a metering test.
[meshlink-tiny] / examples / groupchat.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <strings.h>
5
6 #include "../src/meshlink-tiny.h"
7 #include "../src/devtools.h"
8
9 #define CHAT_PORT 531
10
11 static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
12         (void) mesh;
13
14         static const char *levelstr[] = {
15                 [MESHLINK_DEBUG] = "\x1b[34mDEBUG",
16                 [MESHLINK_INFO] = "\x1b[32mINFO",
17                 [MESHLINK_WARNING] = "\x1b[33mWARNING",
18                 [MESHLINK_ERROR] = "\x1b[31mERROR",
19                 [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL",
20         };
21         fprintf(stderr, "%s:\x1b[0m %s\n", levelstr[level], text);
22 }
23
24 static void channel_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) {
25         if(!len) {
26                 if(meshlink_errno) {
27                         fprintf(stderr, "Error while reading data from %s: %s\n", channel->node->name, meshlink_strerror(meshlink_errno));
28                 } else {
29                         fprintf(stderr, "Chat connection closed by %s\n", channel->node->name);
30                 }
31
32                 channel->node->priv = NULL;
33                 meshlink_channel_close(mesh, channel);
34                 return;
35         }
36
37         // TODO: we now have TCP semantics, don't expect exactly one message per receive call.
38
39         fprintf(stderr, "%s says: ", channel->node->name);
40         fwrite(data, len, 1, stderr);
41         fputc('\n', stderr);
42 }
43
44 static bool channel_accept(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) {
45         (void)data;
46         (void)len;
47
48         // Only accept connections to the chat port
49         if(port != CHAT_PORT) {
50                 fprintf(stderr, "Rejected incoming channel from '%s' to port %u\n", channel->node->name, port);
51                 return false;
52         }
53
54         fprintf(stderr, "Accepted incoming channel from '%s'\n", channel->node->name);
55
56         // Remember the channel
57         channel->node->priv = channel;
58
59         // Set the receive callback
60         meshlink_set_channel_receive_cb(mesh, channel, channel_receive);
61
62         // Accept this channel
63         return true;
64 }
65
66 static void channel_poll(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t len) {
67         (void)len;
68
69         fprintf(stderr, "Channel to '%s' connected\n", channel->node->name);
70         meshlink_set_channel_poll_cb(mesh, channel, NULL);
71 }
72
73 static void node_status(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) {
74         (void)mesh;
75
76         if(reachable) {
77                 fprintf(stderr, "%s joined.\n", node->name);
78         } else {
79                 fprintf(stderr, "%s left.\n", node->name);
80         }
81 }
82
83 static void parse_command(meshlink_handle_t *mesh, char *buf) {
84         char *arg = strchr(buf, ' ');
85         char *arg1 = NULL;
86
87         if(arg) {
88                 *arg++ = 0;
89
90                 arg1 = strchr(arg, ' ');
91
92                 if(arg1) {
93                         *arg1++ = 0;
94                 }
95
96         }
97
98         if(!strcasecmp(buf, "canonical")) {
99                 bool set;
100                 char *host = NULL, *port = NULL;
101
102                 if(!arg) {
103                         fprintf(stderr, "/canonical requires an argument!\n");
104                         return;
105                 }
106
107                 if((0 == strncasecmp(arg, "-h", 2)) && (strlen(arg) > 2)) {
108                         host = arg + 2;
109                 } else if((0 == strncasecmp(arg, "-p", 2)) && (strlen(arg) > 2)) {
110                         port = arg + 2;
111                 } else {
112                         fprintf(stderr, "Unknown argument: %s!\n", arg);
113                         return;
114                 }
115
116                 if(arg1) {
117                         if((0 == strncasecmp(arg1, "-h", 2)) && (strlen(arg1) > 2)) {
118                                 host = arg1 + 2;
119                         } else if((0 == strncasecmp(arg1, "-p", 2)) && (strlen(arg1) > 2)) {
120                                 port = arg1 + 2;
121                         } else {
122                                 fprintf(stderr, "Unknown argument: %s!\n", arg1);
123                                 return;
124                         }
125                 }
126
127                 if(!host && !port) {
128                         fprintf(stderr, "Unable to set Canonical address because no valid arguments are found!\n");
129                         return;
130                 }
131
132                 set = meshlink_set_canonical_address(mesh, meshlink_get_self(mesh), host, port);
133
134                 if(!set) {
135                         fprintf(stderr, "Could not set canonical address '%s:%s': %s\n", host, port, meshlink_strerror(meshlink_errno));
136                         return;
137                 }
138
139                 fprintf(stderr, "Canonical address set as '%s:%s'\n", host, port);
140         } else if(!strcasecmp(buf, "group")) {
141                 if(!arg) {
142                         fprintf(stderr, "/group requires an argument!\n");
143                         return;
144                 }
145
146                 meshlink_submesh_t *s = meshlink_submesh_open(mesh, arg);
147
148                 if(!s) {
149                         fprintf(stderr, "Could not create group: %s\n", meshlink_strerror(meshlink_errno));
150                 } else {
151                         fprintf(stderr, "Group '%s' created!\n", s->name);
152                 }
153         } else if(!strcasecmp(buf, "join")) {
154                 if(!arg) {
155                         fprintf(stderr, "/join requires an argument!\n");
156                         return;
157                 }
158
159                 meshlink_stop(mesh);
160
161                 if(!meshlink_join(mesh, arg)) {
162                         fprintf(stderr, "Could not join using invitation: %s\n", meshlink_strerror(meshlink_errno));
163                 } else {
164                         fprintf(stderr, "Invitation accepted!\n");
165                 }
166
167                 if(!meshlink_start(mesh)) {
168                         fprintf(stderr, "Could not restart MeshLink: %s\n", meshlink_strerror(meshlink_errno));
169                         exit(1);
170                 }
171         } else if(!strcasecmp(buf, "who")) {
172                 meshlink_submesh_t *node_group = NULL;
173
174                 if(!arg) {
175                         size_t nnodes;
176                         meshlink_node_t **nodes = meshlink_get_all_nodes(mesh, NULL, &nnodes);
177
178                         if(!nnodes) {
179                                 fprintf(stderr, "Could not get list of nodes: %s\n", meshlink_strerror(meshlink_errno));
180                                 return;
181                         }
182
183                         fprintf(stderr, "%lu known nodes:\n", (unsigned long)nnodes);
184
185                         for(size_t i = 0; i < nnodes; i++) {
186                                 fprintf(stderr, " %lu. %s", (unsigned long)i, nodes[i]->name);
187
188                                 if((node_group = meshlink_get_node_submesh(mesh, nodes[i]))) {
189                                         fprintf(stderr, "\t%s", node_group->name);
190                                 }
191
192                                 fprintf(stderr, "\n");
193                         }
194
195                         fprintf(stderr, "\n");
196
197                         free(nodes);
198                 } else {
199                         meshlink_node_t *node = meshlink_get_node(mesh, arg);
200
201                         if(!node) {
202                                 fprintf(stderr, "Error looking up '%s': %s\n", arg, meshlink_strerror(meshlink_errno));
203                         } else {
204                                 fprintf(stderr, "Node %s found", arg);
205
206                                 if((node_group = meshlink_get_node_submesh(mesh, node))) {
207                                         fprintf(stderr, " in group %s", node_group->name);
208                                 }
209
210                                 fprintf(stderr, "\n");
211                         }
212                 }
213         } else if(!strcasecmp(buf, "listgroup")) {
214                 if(!arg) {
215                         fprintf(stderr, "/listgroup requires an argument!\n");
216                         return;
217                 }
218
219                 size_t nmemb;
220                 meshlink_submesh_t **submeshes = devtool_get_all_submeshes(mesh, NULL, &nmemb);
221
222                 if(!submeshes || !nmemb) {
223                         fprintf(stderr, "Group does not exist!\n");
224                         return;
225                 }
226
227                 meshlink_submesh_t *s = NULL;
228
229                 for(size_t i = 0; i < nmemb; i++) {
230                         if(!strcmp(arg, submeshes[i]->name)) {
231                                 s = submeshes[i];
232                                 break;
233                         }
234                 }
235
236                 free(submeshes);
237
238                 if(!s) {
239                         fprintf(stderr, "Group %s does not exist!\n", arg);
240                         return;
241                 }
242
243                 size_t nnodes;
244                 meshlink_node_t **nodes = meshlink_get_all_nodes_by_submesh(mesh, s, NULL, &nnodes);
245
246                 if(!nodes || !nnodes) {
247                         fprintf(stderr, "Group %s does not contain any nodes!\n", arg);
248                         return;
249                 }
250
251                 fprintf(stderr, "%zu known nodes in group %s:", nnodes, arg);
252
253                 for(size_t i = 0; i < nnodes; i++) {
254                         fprintf(stderr, " %s", nodes[i]->name);
255                 }
256
257                 fprintf(stderr, "\n");
258
259                 free(nodes);
260         } else if(!strcasecmp(buf, "quit")) {
261                 fprintf(stderr, "Bye!\n");
262                 fclose(stdin);
263         } else if(!strcasecmp(buf, "help")) {
264                 fprintf(stderr,
265                         "<name>: <message>                        Send a message to the given node.\n"
266                         "                                         Subsequent messages don't need the <name>: prefix.\n"
267                         "/group <name>                            Create a new group"
268                         "/join <invitation>                               Join an existing mesh using an invitation.\n"
269                         "/kick <name>                             Blacklist the given node.\n"
270                         "/who [<name>]                            List all nodes or show information about the given node.\n"
271                         "/listgroup <name>                        List all nodes in a given group.\n"
272                         "/canonical -h<hostname> -p<port> Set Canonical address to be present in invitation.\n"
273                         "                                         Any one of two options an be specified. At least one option must be present\n"
274                         "/quit                                    Exit this program.\n"
275                        );
276         } else {
277                 fprintf(stderr, "Unknown command '/%s'\n", buf);
278         }
279 }
280
281 static void parse_input(meshlink_handle_t *mesh, char *buf) {
282         static meshlink_node_t *destination;
283         size_t len;
284
285         if(!buf) {
286                 return;
287         }
288
289         // Remove newline.
290
291         len = strlen(buf);
292
293         if(len && buf[len - 1] == '\n') {
294                 buf[--len] = 0;
295         }
296
297         if(len && buf[len - 1] == '\r') {
298                 buf[--len] = 0;
299         }
300
301         // Ignore empty lines.
302
303         if(!len) {
304                 return;
305         }
306
307         // Commands start with '/'
308
309         if(*buf == '/') {
310                 parse_command(mesh, buf + 1);
311                 return;
312         }
313
314         // Lines in the form "name: message..." set the destination node.
315
316         char *msg = buf;
317         char *colon = strchr(buf, ':');
318
319         if(colon) {
320                 *colon = 0;
321                 msg = colon + 1;
322
323                 if(*msg == ' ') {
324                         msg++;
325                 }
326
327                 destination = meshlink_get_node(mesh, buf);
328
329                 if(!destination) {
330                         fprintf(stderr, "Error looking up '%s': %s\n", buf, meshlink_strerror(meshlink_errno));
331                         return;
332                 }
333         }
334
335         if(!destination) {
336                 fprintf(stderr, "Who are you talking to? Write 'name: message...'\n");
337                 return;
338         }
339
340         // We want to have one channel per node.
341         // We keep the pointer to the meshlink_channel_t in the priv field of that node.
342         meshlink_channel_t *channel = destination->priv;
343
344         if(!channel) {
345                 fprintf(stderr, "Opening chat channel to '%s'\n", destination->name);
346                 channel = meshlink_channel_open(mesh, destination, CHAT_PORT, channel_receive, NULL, 0);
347
348                 if(!channel) {
349                         fprintf(stderr, "Could not create channel to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno));
350                         return;
351                 }
352
353                 destination->priv = channel;
354                 meshlink_set_channel_poll_cb(mesh, channel, channel_poll);
355         }
356
357         if(!meshlink_channel_send(mesh, channel, msg, strlen(msg))) {
358                 fprintf(stderr, "Could not send message to '%s': %s\n", destination->name, meshlink_strerror(meshlink_errno));
359                 return;
360         }
361
362         fprintf(stderr, "Message sent to '%s'.\n", destination->name);
363 }
364
365 int main(int argc, char *argv[]) {
366         const char *confbase = ".chat";
367         const char *nick = NULL;
368         char buf[1024];
369
370         if(argc > 1) {
371                 confbase = argv[1];
372         }
373
374         if(argc > 2) {
375                 nick = argv[2];
376         }
377
378         meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message);
379
380         meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY);
381
382         if(!mesh) {
383                 fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno));
384                 return 1;
385         }
386
387         meshlink_set_node_status_cb(mesh, node_status);
388         meshlink_set_log_cb(mesh, MESHLINK_INFO, log_message);
389
390         // Set the channel accept callback. This implicitly turns on channels for all nodes.
391         // This replaces the call to meshlink_set_receive_cb().
392         meshlink_set_channel_accept_cb(mesh, channel_accept);
393
394         if(!meshlink_start(mesh)) {
395                 fprintf(stderr, "Could not start MeshLink: %s\n", meshlink_strerror(meshlink_errno));
396                 return 1;
397         }
398
399         fprintf(stderr, "Chat started.\nType /help for a list of commands.\n");
400
401         while(fgets(buf, sizeof(buf), stdin)) {
402                 parse_input(mesh, buf);
403         }
404
405         fprintf(stderr, "Chat stopping.\n");
406
407         meshlink_stop(mesh);
408         meshlink_close(mesh);
409
410         return 0;
411 }