Imported Upstream version 0.14.4
[platform/upstream/gssdp.git] / tools / gssdp-device-sniffer.c
1 /* 
2  * Copyright (C) 2007 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include <libgssdp/gssdp.h>
21 #include <libgssdp/gssdp-client-private.h>
22 #include <libsoup/soup.h>
23 #include <gtk/gtk.h>
24 #include <string.h>
25 #include <stdlib.h>
26
27 #define UI_FILE "gssdp-device-sniffer.ui"
28 #define MAX_IP_LEN 16
29
30 static char *interface = NULL;
31
32 GtkBuilder *builder;
33 GSSDPResourceBrowser *resource_browser;
34 GSSDPClient *client;
35 char *ip_filter = NULL;
36 gboolean capture_packets = TRUE;
37
38 GOptionEntry entries[] =
39 {
40         {"interface", 'i', 0, G_OPTION_ARG_STRING, &interface, "Network interface to listen on", NULL },
41         { NULL }
42 };
43
44 G_MODULE_EXPORT
45 void
46 on_enable_packet_capture_activate (GtkCheckMenuItem      *menuitem,
47                                    G_GNUC_UNUSED gpointer user_data)
48 {
49         capture_packets = gtk_check_menu_item_get_active (menuitem);
50 }
51
52 static void
53 clear_packet_treeview ()
54 {
55         GtkWidget *treeview;
56         GtkTreeModel *model;
57         GtkTreeIter iter;
58         gboolean more;
59         time_t *arrival_time;
60
61         treeview = GTK_WIDGET(gtk_builder_get_object (builder, "packet-treeview"));
62         g_assert (treeview != NULL);
63         model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
64         more = gtk_tree_model_get_iter_first (model, &iter);
65
66         while (more) {
67                 gtk_tree_model_get (model,
68                                 &iter, 
69                                 5, &arrival_time, -1);
70                 g_free (arrival_time);
71                 more = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
72         }
73 }
74
75 G_MODULE_EXPORT
76 void
77 on_details_activate (GtkWidget *scrolled_window, GtkCheckMenuItem *menuitem)
78 {
79         gboolean active;
80
81         active = gtk_check_menu_item_get_active (menuitem);
82         g_object_set (G_OBJECT (scrolled_window), "visible", active, NULL);
83 }
84
85 static void
86 packet_header_to_string (const char *header_name,
87                  const char *header_val,
88                  GString **text)
89 {
90         g_string_append_printf (*text, "%s: %s\n",
91                          header_name,
92                          header_val);
93 }
94  
95 static void
96 clear_textbuffer (GtkTextBuffer *textbuffer)
97 {
98         GtkTextIter start, end;
99
100         gtk_text_buffer_get_bounds (textbuffer, &start, &end);
101         gtk_text_buffer_delete (textbuffer, &start, &end);
102 }
103
104 static void
105 update_packet_details (char *text, unsigned int len)
106 {
107         GtkWidget *textview;
108         GtkTextBuffer *textbuffer;
109         
110         textview = GTK_WIDGET(gtk_builder_get_object (builder, "packet-details-textview"));
111         g_assert (textview != NULL);
112         textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
113         
114         clear_textbuffer (textbuffer);
115         gtk_text_buffer_insert_at_cursor (textbuffer, text, len);
116 }
117
118 static void
119 display_packet (time_t arrival_time, SoupMessageHeaders *packet_headers)
120 {
121         GString *text;
122
123         text = g_string_new ("");
124         g_string_printf (text, "Received on: %s\nHeaders:\n\n",
125                         ctime (&arrival_time));
126
127         soup_message_headers_foreach (packet_headers,
128                         (SoupMessageHeadersForeachFunc)
129                         packet_header_to_string,
130                         &text);
131
132         update_packet_details (text->str, text->len);
133         g_string_free (text, TRUE);
134 }
135
136 static void
137 on_packet_selected (GtkTreeSelection      *selection,
138                     G_GNUC_UNUSED gpointer user_data)
139 {
140         GtkTreeModel *model;
141         GtkTreeIter iter;
142         time_t *arrival_time;
143
144         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
145                 SoupMessageHeaders *packet_headers;
146
147                 gtk_tree_model_get (model,
148                                 &iter, 
149                                 4, &packet_headers,
150                                 5, &arrival_time, -1);
151                 display_packet (*arrival_time, packet_headers);
152                 g_boxed_free (SOUP_TYPE_MESSAGE_HEADERS, packet_headers);
153         }
154
155         else
156                 update_packet_details ("", 0);
157 }
158
159 G_MODULE_EXPORT
160 void
161 on_clear_packet_capture_activate (G_GNUC_UNUSED GtkMenuItem *menuitem,
162                                   G_GNUC_UNUSED gpointer     user_data)
163 {
164         clear_packet_treeview ();
165 }
166
167 static char *message_types[] = {"M-SEARCH", "RESPONSE", "NOTIFY"};
168
169 static char **
170 packet_to_treeview_data (const gchar        *from_ip,
171                          time_t              arrival_time,
172                          _GSSDPMessageType   type,
173                          SoupMessageHeaders *headers)
174 {
175         char **packet_data;
176         const char *target;
177         struct tm *tm;
178
179         packet_data = g_malloc (sizeof (char *) * 5);
180
181         /* Set the Time */
182         tm = localtime (&arrival_time);
183         packet_data[0] = g_strdup_printf ("%02d:%02d", tm->tm_hour, tm->tm_min);
184
185         /* Now the Source Address */
186         packet_data[1] = g_strdup (from_ip);
187         
188         /* Now the Packet Type */
189         packet_data[2] = g_strdup (message_types[type]);
190         
191         /* Now the Packet Information */
192         if (type == _GSSDP_DISCOVERY_RESPONSE)
193                 target = soup_message_headers_get_one (headers, "ST");
194         else
195                 target = soup_message_headers_get_one (headers, "NT");
196         
197         packet_data[3] = g_strdup (target);
198         packet_data[4] = NULL;
199
200         return packet_data;
201 }
202
203 static void
204 append_packet (const gchar *from_ip,
205                time_t arrival_time,
206                _GSSDPMessageType type,
207                SoupMessageHeaders *headers)
208 {
209         GtkWidget *treeview;
210         GtkListStore *liststore;
211         GtkTreeIter iter;
212         char **packet_data;
213         
214         treeview = GTK_WIDGET(gtk_builder_get_object (builder, "packet-treeview"));
215         g_assert (treeview != NULL);
216         liststore = GTK_LIST_STORE (
217                         gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
218         g_assert (liststore != NULL);
219        
220         packet_data = packet_to_treeview_data (from_ip,
221                         arrival_time,
222                         type,
223                         headers);
224         gtk_list_store_insert_with_values (liststore, &iter, 0,
225                         0, packet_data[0],
226                         1, packet_data[1],
227                         2, packet_data[2],
228                         3, packet_data[3],
229                         4, headers,
230                         5, g_memdup (&arrival_time, sizeof (time_t)),
231                         -1);
232         g_strfreev (packet_data);
233 }
234
235 static void
236 on_ssdp_message (G_GNUC_UNUSED GSSDPClient *client,
237                  G_GNUC_UNUSED const gchar *from_ip,
238                  G_GNUC_UNUSED gushort      from_port,
239                  _GSSDPMessageType          type,
240                  SoupMessageHeaders        *headers,
241                  G_GNUC_UNUSED gpointer     user_data)
242 {
243         time_t arrival_time;
244         
245         arrival_time = time (NULL);
246      
247         if (type == _GSSDP_DISCOVERY_REQUEST)
248                 return;
249         if (ip_filter != NULL && strcmp (ip_filter, from_ip) != 0)
250                 return;
251         if (!capture_packets) 
252                 return;
253
254         append_packet (from_ip, arrival_time, type, headers);
255 }
256
257 static gboolean 
258 find_device (GtkTreeModel *model, const char *uuid, GtkTreeIter *iter)
259 {
260         gboolean found = FALSE;
261         gboolean more;
262        
263         more = gtk_tree_model_get_iter_first (model, iter);
264         while (more) {
265                 char *device_uuid;
266                 gtk_tree_model_get (model,
267                                 iter, 
268                                 0, &device_uuid, -1);
269                 if (device_uuid && strcmp (device_uuid, uuid) == 0) {
270                         found = TRUE;
271                         break;
272                 }
273
274                 g_free (device_uuid);
275                 more = gtk_tree_model_iter_next (model, iter);
276         }
277
278         return found;
279 }
280
281 static void
282 append_device (const char *uuid,
283                const char *first_notify,
284                const char *device_type,
285                const char *location)
286 {
287         GtkWidget *treeview;
288         GtkTreeModel *model;
289         GtkTreeIter iter;
290        
291         treeview = GTK_WIDGET(gtk_builder_get_object (builder, "device-details-treeview"));
292         g_assert (treeview != NULL);
293         model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
294         g_assert (model != NULL);
295        
296         if (!find_device (model, uuid, &iter)) {
297                 gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
298                                 &iter, 0,
299                                 0, uuid,
300                                 1, first_notify,
301                                 3, location, -1);
302         }
303                 
304         if (device_type) {
305                 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
306                                 2, device_type, -1);
307         }
308 }
309
310 static void
311 resource_available_cb (G_GNUC_UNUSED GSSDPResourceBrowser *resource_browser,
312                        const char                         *usn,
313                        GList                              *locations)
314 {
315
316         char **usn_tokens;
317         char *uuid;
318         char *device_type = NULL;
319         time_t current_time;
320         struct tm *tm;
321         char *first_notify;
322                 
323         current_time = time (NULL);
324         tm = localtime (&current_time);
325         first_notify = g_strdup_printf ("%02d:%02d",
326         tm->tm_hour, tm->tm_min);
327
328         usn_tokens = g_strsplit (usn, "::", -1);
329         g_assert (usn_tokens != NULL && usn_tokens[0] != NULL);
330
331         uuid = usn_tokens[0] + 5; /* skip the prefix 'uuid:' */
332
333         if (usn_tokens[1]) {
334                 char **urn_tokens;
335
336                 urn_tokens = g_strsplit (usn_tokens[1], ":device:", -1);
337                         
338                 if (urn_tokens[1])
339                         device_type = g_strdup (urn_tokens[1]);
340                 g_strfreev (urn_tokens);
341         }
342
343         /* Device Announcement */
344         append_device (uuid,
345                        first_notify,
346                        device_type,
347                        (char *) locations->data);
348         
349         if (device_type)
350                 g_free (device_type);
351         g_free (first_notify);
352         g_strfreev (usn_tokens);
353 }
354
355 static void
356 remove_device (const char *uuid)
357 {
358         GtkWidget *treeview;
359         GtkTreeModel *model;
360         GtkTreeIter iter;
361        
362         treeview = GTK_WIDGET(gtk_builder_get_object (builder, "device-details-treeview"));
363         g_assert (treeview != NULL);
364         model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
365         g_assert (model != NULL);
366       
367         if (find_device (model, uuid, &iter)) {
368                 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
369         }
370 }
371
372 static void
373 resource_unavailable_cb (G_GNUC_UNUSED GSSDPResourceBrowser *resource_browser,
374                          const char                         *usn)
375 {
376         char **usn_tokens;
377         char *uuid;
378
379         usn_tokens = g_strsplit (usn, "::", -1);
380         g_assert (usn_tokens != NULL && usn_tokens[0] != NULL);
381         uuid = usn_tokens[0] + 5; /* skip the prefix 'uuid:' */
382         
383         remove_device (uuid);
384         
385         g_strfreev (usn_tokens);
386 }
387
388 G_MODULE_EXPORT
389 void
390 on_use_filter_radiobutton_toggled (GtkToggleButton       *togglebutton,
391                                    G_GNUC_UNUSED gpointer user_data)
392 {
393         GtkWidget *filter_hbox;
394         gboolean use_filter;
395
396         filter_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "address-filter-hbox"));
397         g_assert (filter_hbox != NULL);
398         
399         use_filter = gtk_toggle_button_get_active (togglebutton);
400         gtk_widget_set_sensitive (filter_hbox, use_filter);
401 }
402
403 static char *
404 get_ip_filter ()
405 {
406         int i;
407         char *ip;
408         guint8 quad[4];
409
410         ip = g_malloc (MAX_IP_LEN);
411         for (i=0; i<4; i++) {
412                 GtkWidget *entry;
413                 char entry_name[16];
414                 gint val;
415
416                 sprintf (entry_name, "address-entry%d", i);
417                 entry = GTK_WIDGET(gtk_builder_get_object (builder, entry_name));
418                 g_assert (entry != NULL);
419
420                 val = atoi (gtk_entry_get_text (GTK_ENTRY (entry)));
421                 quad[i] = CLAMP (val, 0, 255);
422         }
423         sprintf (ip, "%u.%u.%u.%u", quad[0], quad[1], quad[2], quad[3]);
424         
425         return ip;
426 }
427
428 G_MODULE_EXPORT
429 void
430 on_address_filter_dialog_response (GtkDialog             *dialog,
431                                    G_GNUC_UNUSED gint     response,
432                                    G_GNUC_UNUSED gpointer user_data)
433 {
434         GtkWidget *use_filter_radio;
435
436         gtk_widget_hide (GTK_WIDGET (dialog));
437         
438         use_filter_radio = GTK_WIDGET(gtk_builder_get_object (builder, "use-filter-radiobutton"));
439         g_assert (use_filter_radio != NULL);
440         
441         if (response != GTK_RESPONSE_OK)
442                 return;
443         
444         g_free (ip_filter);
445         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (use_filter_radio))) {
446                 ip_filter = get_ip_filter ();
447         }
448        
449         else
450                 ip_filter = NULL;
451 }
452
453 static GtkTreeModel *
454 create_packet_treemodel (void)
455 {
456         GtkListStore *store;
457
458         store = gtk_list_store_new (6,
459                         G_TYPE_STRING,
460                         G_TYPE_STRING,
461                         G_TYPE_STRING,
462                         G_TYPE_STRING,
463                         SOUP_TYPE_MESSAGE_HEADERS,
464                         G_TYPE_POINTER);
465
466         return GTK_TREE_MODEL (store);
467 }
468
469 static GtkTreeModel *
470 create_device_treemodel (void)
471 {
472         GtkListStore *store;
473
474         store = gtk_list_store_new (4,
475                         G_TYPE_STRING,
476                         G_TYPE_STRING,
477                         G_TYPE_STRING,
478                         G_TYPE_STRING);
479
480         return GTK_TREE_MODEL (store);
481 }
482
483 static void
484 setup_treeview (GtkWidget *treeview,
485                 GtkTreeModel *model,
486                 char *headers[])
487 {
488         int i;
489
490         /* Set-up columns */
491         for (i=0; headers[i] != NULL; i++) {
492                 GtkCellRenderer *renderer;
493                
494                 renderer = gtk_cell_renderer_text_new ();
495                 gtk_tree_view_insert_column_with_attributes (
496                                 GTK_TREE_VIEW (treeview),
497                                 -1,
498                                 headers[i],
499                                 renderer,
500                                 "text", i,
501                                 NULL);
502         }
503
504         gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
505                         model);
506 }
507
508 static void
509 setup_treeviews ()
510 {
511         GtkWidget *treeviews[2];
512         GtkTreeModel *treemodels[2];
513         char *headers[2][6] = { {"Time",
514                 "Source Address",
515                 "Packet Type",
516                 "Packet Information",
517                 NULL }, {"Unique Identifier",
518                 "First Notify",
519                 "Device Type",
520                 "Location",
521                 NULL } }; 
522         GtkTreeSelection *selection;
523         int i;
524
525         treeviews[0] = GTK_WIDGET(gtk_builder_get_object (builder,
526                         "packet-treeview"));
527         g_assert (treeviews[0] != NULL);
528         treeviews[1] = GTK_WIDGET(gtk_builder_get_object (builder,
529                         "device-details-treeview"));
530         g_assert (treeviews[1] != NULL);
531         
532         treemodels[0] = create_packet_treemodel ();
533         g_assert (treemodels[0] != NULL);
534         treemodels[1] = create_device_treemodel ();
535         g_assert (treemodels[1] != NULL);
536
537         for (i=0; i<2; i++)
538                 setup_treeview (treeviews[i], treemodels[i], headers[i]);
539         
540         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeviews[0]));
541         g_assert (selection != NULL);
542         g_signal_connect (selection,
543                         "changed",
544                         G_CALLBACK (on_packet_selected),
545                         (gpointer *) treeviews[0]);
546 }
547
548 G_MODULE_EXPORT
549 gboolean
550 on_delete_event (G_GNUC_UNUSED GtkWidget *widget,
551                  G_GNUC_UNUSED GdkEvent  *event,
552                  G_GNUC_UNUSED gpointer   user_data)
553 {
554         gtk_main_quit ();
555
556         return TRUE;
557 }
558
559 static gboolean
560 init_ui (gint *argc, gchar **argv[])
561 {
562         GtkWidget *main_window;
563         gint window_width, window_height;
564         gchar *ui_path = NULL;
565         GError *error = NULL;
566         GOptionContext *context;
567
568         context = g_option_context_new ("- graphical SSDP debug tool");
569         g_option_context_add_main_entries (context, entries, NULL);
570         g_option_context_add_group (context, gtk_get_option_group (TRUE));
571         if (!g_option_context_parse (context, argc, argv, &error)) {
572                 g_print ("Failed to parse options: %s\n", error->message);
573                 g_error_free (error);
574
575                 return FALSE;
576         }
577
578         /* Try to fetch the ui file from the CWD first */
579         ui_path = UI_FILE;
580         if (!g_file_test (ui_path, G_FILE_TEST_EXISTS)) {
581                 /* Then Try to fetch it from the system path */
582                 ui_path = UI_DIR "/" UI_FILE;
583
584                 if (!g_file_test (ui_path, G_FILE_TEST_EXISTS))
585                         ui_path = NULL;
586         }
587         
588         if (ui_path == NULL) {
589                 g_critical ("Unable to load the GUI file %s", UI_FILE);
590                 return FALSE;
591         }
592
593         builder = gtk_builder_new();
594         if (gtk_builder_add_from_file(builder, ui_path, NULL) == 0)
595                 return FALSE;
596
597         main_window = GTK_WIDGET(gtk_builder_get_object (builder, "main-window"));
598         g_assert (main_window != NULL);
599
600         /* 80% of the screen but don't get bigger than 1000x800 */
601         window_width = CLAMP ((gdk_screen_width () * 80 / 100), 10, 1000);
602         window_height = CLAMP ((gdk_screen_height () * 80 / 100), 10, 800);
603         gtk_window_set_default_size (GTK_WINDOW (main_window),
604                                      window_width,
605                                      window_height);
606
607         gtk_builder_connect_signals (builder, NULL);
608         setup_treeviews ();
609         gtk_widget_show_all (main_window);
610
611         return TRUE;
612 }
613
614 static void
615 deinit_ui (void)
616 {
617         g_object_unref (builder);
618 }
619
620 static gboolean
621 init_upnp (void)
622 {
623         GError *error;
624         
625         error = NULL;
626         client = g_initable_new (GSSDP_TYPE_CLIENT,
627                                  NULL,
628                                  &error,
629                                  "interface", interface,
630                                  NULL);
631         if (error) {
632                 g_printerr ("Error creating the GSSDP client: %s\n",
633                             error->message);
634
635                 g_error_free (error);
636
637                 return FALSE;
638         }
639
640         resource_browser = gssdp_resource_browser_new (client,
641                                                        GSSDP_ALL_RESOURCES);
642         
643         g_signal_connect (client,
644                           "message-received",
645                           G_CALLBACK (on_ssdp_message),
646                           NULL);
647         g_signal_connect (resource_browser,
648                           "resource-available",
649                           G_CALLBACK (resource_available_cb),
650                           NULL);
651         g_signal_connect (resource_browser,
652                           "resource-unavailable",
653                           G_CALLBACK (resource_unavailable_cb),
654                           NULL);
655
656         gssdp_resource_browser_set_active (resource_browser, TRUE);
657
658         return TRUE;
659 }
660
661 static void
662 deinit_upnp (void)
663 {
664         g_object_unref (resource_browser);
665         g_object_unref (client);
666 }
667
668 gint
669 main (gint argc, gchar *argv[])
670 {
671         if (!init_ui (&argc, &argv)) {
672            return -2;
673         }
674
675         if (!init_upnp ()) {
676            return -3;
677         }
678         
679         gtk_main ();
680        
681         deinit_upnp ();
682         deinit_ui ();
683         
684         return 0;
685 }