1# vim: set encoding=utf-8 :
2
3# ***********************IMPORTANT NMAP LICENSE TERMS************************
4# *                                                                         *
5# * The Nmap Security Scanner is (C) 1996-2020 Insecure.Com LLC ("The Nmap  *
6# * Project"). Nmap is also a registered trademark of the Nmap Project.     *
7# *                                                                         *
8# * This program is distributed under the terms of the Nmap Public Source   *
9# * License (NPSL). The exact license text applying to a particular Nmap    *
10# * release or source code control revision is contained in the LICENSE     *
11# * file distributed with that version of Nmap or source code control       *
12# * revision. More Nmap copyright/legal information is available from       *
13# * https://nmap.org/book/man-legal.html, and further information on the    *
14# * NPSL license itself can be found at https://nmap.org/npsl. This header  *
15# * summarizes some key points from the Nmap license, but is no substitute  *
16# * for the actual license text.                                            *
17# *                                                                         *
18# * Nmap is generally free for end users to download and use themselves,    *
19# * including commercial use. It is available from https://nmap.org.        *
20# *                                                                         *
21# * The Nmap license generally prohibits companies from using and           *
22# * redistributing Nmap in commercial products, but we sell a special Nmap  *
23# * OEM Edition with a more permissive license and special features for     *
24# * this purpose. See https://nmap.org/oem                                  *
25# *                                                                         *
26# * If you have received a written Nmap license agreement or contract       *
27# * stating terms other than these (such as an Nmap OEM license), you may   *
28# * choose to use and redistribute Nmap under those terms instead.          *
29# *                                                                         *
30# * The official Nmap Windows builds include the Npcap software             *
31# * (https://npcap.org) for packet capture and transmission. It is under    *
32# * separate license terms which forbid redistribution without special      *
33# * permission. So the official Nmap Windows builds may not be              *
34# * redistributed without special permission (such as an Nmap OEM           *
35# * license).                                                               *
36# *                                                                         *
37# * Source is provided to this software because we believe users have a     *
38# * right to know exactly what a program is going to do before they run it. *
39# * This also allows you to audit the software for security holes.          *
40# *                                                                         *
41# * Source code also allows you to port Nmap to new platforms, fix bugs,    *
42# * and add new features.  You are highly encouraged to submit your         *
43# * changes as a Github PR or by email to the dev@nmap.org mailing list     *
44# * for possible incorporation into the main distribution. Unless you       *
45# * specify otherwise, it is understood that you are offering us very       *
46# * broad rights to use your submissions as described in the Nmap Public    *
47# * Source License Contributor Agreement. This is important because we      *
48# * fund the project by selling licenses with various terms, and also       *
49# * because the inability to relicense code has caused devastating          *
50# * problems for other Free Software projects (such as KDE and NASM).       *
51# *                                                                         *
52# * The free version of Nmap is distributed in the hope that it will be     *
53# * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of  *
54# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties,        *
55# * indemnification and commercial support are all available through the    *
56# * Npcap OEM program--see https://nmap.org/oem.                            *
57# *                                                                         *
58# ***************************************************************************/
59
60import gtk
61import math
62import cairo
63import gobject
64
65import radialnet.util.geometry as geometry
66import radialnet.util.misc as misc
67
68from radialnet.core.Coordinate import PolarCoordinate, CartesianCoordinate
69from radialnet.core.Interpolation import Linear2DInterpolator
70from radialnet.core.Graph import Node
71from radialnet.gui.NodeWindow import NodeWindow
72from radialnet.gui.Image import Icons, get_pixels_for_cairo_image_surface
73
74from zenmapCore.BasePaths import fs_enc
75
76REGION_COLORS = [(1.0, 0.0, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0)]
77REGION_RED = 0
78REGION_YELLOW = 1
79REGION_GREEN = 2
80
81SQUARE_TYPES = ['router', 'switch', 'wap']
82
83ICON_DICT = {'router': 'router',
84             'switch': 'switch',
85             'wap': 'wireless',
86             'firewall': 'firewall'}
87
88POINTER_JUMP_TO = 0
89POINTER_INFO = 1
90POINTER_GROUP = 2
91POINTER_FILL = 3
92
93LAYOUT_SYMMETRIC = 0
94LAYOUT_WEIGHTED = 1
95
96INTERPOLATION_CARTESIAN = 0
97INTERPOLATION_POLAR = 1
98
99FILE_TYPE_PDF = 1
100FILE_TYPE_PNG = 2
101FILE_TYPE_PS = 3
102FILE_TYPE_SVG = 4
103
104
105class RadialNet(gtk.DrawingArea):
106    """
107    Radial network visualization widget
108    """
109    def __init__(self, layout=LAYOUT_SYMMETRIC):
110        """
111        Constructor method of RadialNet widget class
112        @type  number_of_rings: number
113        @param number_of_rings: Number of rings in radial layout
114        """
115        self.__center_of_widget = (0, 0)
116        self.__graph = None
117
118        self.__number_of_rings = 0
119        self.__ring_gap = 30
120        self.__min_ring_gap = 10
121
122        self.__layout = layout
123        self.__interpolation = INTERPOLATION_POLAR
124        self.__interpolation_slow_in_out = True
125
126        self.__animating = False
127        self.__animation_rate = 1000 // 60  # 60Hz (human perception factor)
128        self.__number_of_frames = 60
129
130        self.__scale = 1.0
131        # rotated so that single-host traceroute doesn't have overlapping hosts
132        self.__rotate = 225
133        self.__translation = (0, 0)
134
135        self.__button1_press = False
136        self.__button2_press = False
137        self.__button3_press = False
138
139        self.__last_motion_point = None
140
141        self.__fisheye = False
142        self.__fisheye_ring = 0
143        self.__fisheye_spread = 0.5
144        self.__fisheye_interest = 2
145
146        self.__show_address = True
147        self.__show_hostname = True
148        self.__show_icon = True
149        self.__show_latency = False
150        self.__show_ring = True
151        self.__show_region = True
152        self.__region_color = REGION_RED
153
154        self.__node_views = dict()
155        self.__last_group_node = None
156
157        self.__pointer_status = POINTER_JUMP_TO
158
159        self.__sorted_nodes = list()
160
161        self.__icon = Icons()
162
163        super(RadialNet, self).__init__()
164
165        self.connect('expose_event', self.expose)
166        self.connect('button_press_event', self.button_press)
167        self.connect('button_release_event', self.button_release)
168        self.connect('motion_notify_event', self.motion_notify)
169        self.connect('enter_notify_event', self.enter_notify)
170        self.connect('leave_notify_event', self.leave_notify)
171        self.connect('key_press_event', self.key_press)
172        self.connect('key_release_event', self.key_release)
173        self.connect('scroll_event', self.scroll_event)
174
175        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
176                        gtk.gdk.BUTTON_RELEASE_MASK |
177                        gtk.gdk.ENTER_NOTIFY |
178                        gtk.gdk.LEAVE_NOTIFY |
179                        gtk.gdk.MOTION_NOTIFY |
180                        gtk.gdk.NOTHING |
181                        gtk.gdk.KEY_PRESS_MASK |
182                        gtk.gdk.KEY_RELEASE_MASK |
183                        gtk.gdk.POINTER_MOTION_HINT_MASK |
184                        gtk.gdk.POINTER_MOTION_MASK |
185                        gtk.gdk.SCROLL_MASK)
186
187        self.set_flags(gtk.CAN_FOCUS)
188        self.grab_focus()
189
190    def graph_is_not_empty(function):
191        """
192        Decorator function to prevent the execution when graph not is set
193        @type  function: function
194        @param function: Protected function
195        """
196        def check_graph_status(*args):
197            if args[0].__graph is None:
198                return False
199            return function(*args)
200
201        return check_graph_status
202
203    def not_is_in_animation(function):
204        """
205        Decorator function to prevent the execution when graph is animating
206        @type  function: function
207        @param function: Protected function
208        """
209        def check_animation_status(*args):
210            if args[0].__animating:
211                return False
212            return function(*args)
213
214        return check_animation_status
215
216    def save_drawing_to_file(self, file, type=FILE_TYPE_PNG):
217        """
218        """
219        allocation = self.get_allocation()
220
221        if type == FILE_TYPE_PDF:
222            self.surface = cairo.PDFSurface(file,
223                    allocation.width,
224                    allocation.height)
225        elif type == FILE_TYPE_PNG:
226            self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
227                    allocation.width,
228                    allocation.height)
229        elif type == FILE_TYPE_PS:
230            self.surface = cairo.PSSurface(file,
231                    allocation.width,
232                    allocation.height)
233        elif type == FILE_TYPE_SVG:
234            self.surface = cairo.SVGSurface(file,
235                    allocation.width,
236                    allocation.height)
237        else:
238            raise TypeError('unknown surface type')
239
240        context = cairo.Context(self.surface)
241
242        context.rectangle(0, 0, allocation.width, allocation.height)
243        context.set_source_rgb(1.0, 1.0, 1.0)
244        context.fill()
245
246        self.__draw(context)
247
248        if type == FILE_TYPE_PNG:
249            # write_to_png requires a str, not unicode, in py2cairo 1.8.10 and
250            # earlier.
251            self.surface.write_to_png(fs_enc(file))
252
253        self.surface.flush()
254        self.surface.finish()
255
256        return True
257
258    def get_slow_inout(self):
259        """
260        """
261        return self.__interpolation_slow_in_out
262
263    def set_slow_inout(self, value):
264        """
265        """
266        self.__interpolation_slow_in_out = value
267
268    def get_region_color(self):
269        """
270        """
271        return self.__region_color
272
273    def set_region_color(self, value):
274        """
275        """
276        self.__region_color = value
277
278    def get_show_region(self):
279        """
280        """
281        return self.__show_region
282
283    def set_show_region(self, value):
284        """
285        """
286        self.__show_region = value
287        self.queue_draw()
288
289    def get_pointer_status(self):
290        """
291        """
292        return self.__pointer_status
293
294    def set_pointer_status(self, pointer_status):
295        """
296        """
297        self.__pointer_status = pointer_status
298
299    def get_show_address(self):
300        """
301        """
302        return self.__show_address
303
304    def get_show_hostname(self):
305        """
306        """
307        return self.__show_hostname
308
309    def get_show_ring(self):
310        """
311        """
312        return self.__show_ring
313
314    def set_show_address(self, value):
315        """
316        """
317        self.__show_address = value
318        self.queue_draw()
319
320    def set_show_hostname(self, value):
321        """
322        """
323        self.__show_hostname = value
324        self.queue_draw()
325
326    def set_show_ring(self, value):
327        """
328        """
329        self.__show_ring = value
330        self.queue_draw()
331
332    def get_min_ring_gap(self):
333        """
334        """
335        return self.__min_ring_gap
336
337    @graph_is_not_empty
338    @not_is_in_animation
339    def set_min_ring_gap(self, value):
340        """
341        """
342        self.__min_ring_gap = int(value)
343
344        if self.__ring_gap < self.__min_ring_gap:
345            self.__ring_gap = self.__min_ring_gap
346
347        self.__update_nodes_positions()
348        self.queue_draw()
349
350        return True
351
352    def get_number_of_frames(self):
353        """
354        """
355        return self.__number_of_frames
356
357    @not_is_in_animation
358    def set_number_of_frames(self, number_of_frames):
359        """
360        """
361        if number_of_frames > 2:
362
363            self.__number_of_frames = int(number_of_frames)
364            return True
365
366        self.__number_of_frames = 3
367        return False
368
369    @not_is_in_animation
370    def update_layout(self):
371        """
372        """
373        if self.__graph is None:
374            return
375        self.__animating = True
376        self.__calc_interpolation(self.__graph.get_main_node())
377        self.__livens_up()
378
379    @not_is_in_animation
380    def set_layout(self, layout):
381        """
382        """
383        if self.__layout != layout:
384
385            self.__layout = layout
386
387            if self.__graph is not None:
388
389                self.__animating = True
390                self.__calc_interpolation(self.__graph.get_main_node())
391                self.__livens_up()
392
393            return True
394
395        return False
396
397    def get_layout(self):
398        """
399        """
400        return self.__layout
401
402    @not_is_in_animation
403    def set_interpolation(self, interpolation):
404        """
405        """
406        self.__interpolation = interpolation
407
408        return True
409
410    def get_interpolation(self):
411        """
412        """
413        return self.__interpolation
414
415    def get_number_of_rings(self):
416        """
417        """
418        return self.__number_of_rings
419
420    def get_fisheye_ring(self):
421        """
422        """
423        return self.__fisheye_ring
424
425    def get_fisheye_interest(self):
426        """
427        """
428        return self.__fisheye_interest
429
430    def get_fisheye_spread(self):
431        """
432        """
433        return self.__fisheye_spread
434
435    def get_fisheye(self):
436        """
437        """
438        return self.__fisheye
439
440    def set_fisheye(self, enable):
441        """
442        """
443        self.__fisheye = enable
444
445        self.__update_nodes_positions()
446        self.queue_draw()
447
448    def set_fisheye_ring(self, value):
449        """
450        """
451        self.__fisheye_ring = value
452        self.__check_fisheye_ring()
453
454        self.__update_nodes_positions()
455        self.queue_draw()
456
457    def set_fisheye_interest(self, value):
458        """
459        """
460        self.__fisheye_interest = value
461
462        self.__update_nodes_positions()
463        self.queue_draw()
464
465    def set_fisheye_spread(self, value):
466        """
467        """
468        self.__fisheye_spread = value
469
470        self.__update_nodes_positions()
471        self.queue_draw()
472
473    def get_show_icon(self):
474        """
475        """
476        return self.__show_icon
477
478    def set_show_icon(self, value):
479        """
480        """
481        self.__show_icon = value
482        self.queue_draw()
483
484    def get_show_latency(self):
485        """
486        """
487        return self.__show_latency
488
489    def set_show_latency(self, value):
490        """
491        """
492        self.__show_latency = value
493        self.queue_draw()
494
495    def get_scale(self):
496        """
497        """
498        return self.__scale
499
500    def get_zoom(self):
501        """
502        """
503        return int(round(self.__scale * 100))
504
505    def set_scale(self, scale):
506        """
507        """
508        if scale >= 0.01:
509
510            self.__scale = scale
511            self.queue_draw()
512
513    def set_zoom(self, zoom):
514        """
515        """
516        if float(zoom) >= 1:
517
518            self.set_scale(float(zoom) / 100.0)
519            self.queue_draw()
520
521    def get_ring_gap(self):
522        """
523        """
524        return self.__ring_gap
525
526    @not_is_in_animation
527    def set_ring_gap(self, ring_gap):
528        """
529        """
530        if ring_gap >= self.__min_ring_gap:
531
532            self.__ring_gap = ring_gap
533            self.__update_nodes_positions()
534            self.queue_draw()
535
536    def scroll_event(self, widget, event):
537        """
538        """
539        if event.direction == gtk.gdk.SCROLL_UP:
540            self.set_scale(self.__scale + 0.01)
541
542        if event.direction == gtk.gdk.SCROLL_DOWN:
543            self.set_scale(self.__scale - 0.01)
544
545        self.queue_draw()
546
547    @graph_is_not_empty
548    @not_is_in_animation
549    def key_press(self, widget, event):
550        """
551        """
552        key = gtk.gdk.keyval_name(event.keyval)
553
554        if key == 'KP_Add':
555            self.set_ring_gap(self.__ring_gap + 1)
556
557        elif key == 'KP_Subtract':
558            self.set_ring_gap(self.__ring_gap - 1)
559
560        elif key == 'Page_Up':
561            self.set_scale(self.__scale + 0.01)
562
563        elif key == 'Page_Down':
564            self.set_scale(self.__scale - 0.01)
565
566        self.queue_draw()
567
568        return True
569
570    @graph_is_not_empty
571    def key_release(self, widget, event):
572        """
573        """
574        key = gtk.gdk.keyval_name(event.keyval)
575
576        if key == 'c':
577            self.__translation = (0, 0)
578
579        elif key == 'r':
580            self.__show_ring = not self.__show_ring
581
582        elif key == 'a':
583            self.__show_address = not self.__show_address
584
585        elif key == 'h':
586            self.__show_hostname = not self.__show_hostname
587
588        elif key == 'i':
589            self.__show_icon = not self.__show_icon
590
591        elif key == 'l':
592            self.__show_latency = not self.__show_latency
593
594        self.queue_draw()
595
596        return True
597
598    @graph_is_not_empty
599    @not_is_in_animation
600    def enter_notify(self, widget, event):
601        """
602        """
603        self.grab_focus()
604        return False
605
606    @graph_is_not_empty
607    @not_is_in_animation
608    def leave_notify(self, widget, event):
609        """
610        """
611        for node in self.__graph.get_nodes():
612            node.set_draw_info({'over': False})
613
614        self.queue_draw()
615
616        return False
617
618    @graph_is_not_empty
619    def button_press(self, widget, event):
620        """
621        Drawing callback
622        @type  widget: GtkWidget
623        @param widget: Gtk widget superclass
624        @type  event: GtkEvent
625        @param event: Gtk event of widget
626        @rtype: boolean
627        @return: Indicator of the event propagation
628        """
629        result = self.__get_node_by_coordinate(self.get_pointer())
630
631        if event.button == 1:
632            self.__button1_press = True
633
634        # animate if node is pressed
635        if self.__pointer_status == POINTER_JUMP_TO and event.button == 1:
636
637            # prevent double animation
638            if self.__animating:
639                return False
640
641            if result is not None:
642
643                node, point = result
644                main_node = self.__graph.get_main_node()
645
646                if node != main_node:
647
648                    if node.get_draw_info('group'):
649
650                        node.set_draw_info({'group': False})
651                        node.set_subtree_info({'grouped': False,
652                                               'group_node': None})
653
654                    self.__animating = True
655                    self.__calc_interpolation(node)
656                    self.__livens_up()
657
658        # group node if it's pressed
659        elif self.__pointer_status == POINTER_GROUP and event.button == 1:
660
661            # prevent group on animation
662            if self.__animating:
663                return False
664
665            if result is not None:
666
667                node, point = result
668                main_node = self.__graph.get_main_node()
669
670                if node != main_node:
671
672                    if node.get_draw_info('group'):
673
674                        node.set_draw_info({'group': False})
675                        node.set_subtree_info({'grouped': False,
676                                               'group_node': None})
677
678                    else:
679
680                        self.__last_group_node = node
681
682                        node.set_draw_info({'group': True})
683                        node.set_subtree_info({'grouped': True,
684                                               'group_node': node})
685
686                self.__animating = True
687                self.__calc_interpolation(self.__graph.get_main_node())
688                self.__livens_up()
689
690        # setting to show node's region
691        elif self.__pointer_status == POINTER_FILL and event.button == 1:
692
693            if result is not None:
694
695                node, point = result
696
697                if node.get_draw_info('region') == self.__region_color:
698                    node.set_draw_info({'region': None})
699
700                else:
701                    node.set_draw_info({'region': self.__region_color})
702
703                self.queue_draw()
704
705        # show node details
706        elif event.button == 3 or self.__pointer_status == POINTER_INFO:
707
708            if event.button == 3:
709                self.__button3_press = True
710
711            if result is not None:
712
713                xw, yw = self.window.get_origin()
714                node, point = result
715                x, y = point
716
717                if node in self.__node_views.keys():
718
719                    self.__node_views[node].present()
720
721                elif node.get_draw_info('scanned'):
722
723                    view = NodeWindow(node, (int(xw + x), int(yw + y)))
724
725                    def close_view(view, event, node):
726                        view.destroy()
727                        del self.__node_views[node]
728
729                    view.connect("delete-event", close_view, node)
730                    view.show_all()
731                    self.__node_views[node] = view
732
733        return False
734
735    @graph_is_not_empty
736    def button_release(self, widget, event):
737        """
738        Drawing callback
739        @type  widget: GtkWidget
740        @param widget: Gtk widget superclass
741        @type  event: GtkEvent
742        @param event: Gtk event of widget
743        @rtype: boolean
744        @return: Indicator of the event propagation
745        """
746        if event.button == 1:
747            self.__button1_press = False
748
749        if event.button == 2:
750            self.__button2_press = False
751
752        if event.button == 3:
753            self.__button3_press = False
754
755        self.grab_focus()
756
757        return False
758
759    @graph_is_not_empty
760    def motion_notify(self, widget, event):
761        """
762        Drawing callback
763        @type  widget: GtkWidget
764        @param widget: Gtk widget superclass
765        @type  event: GtkEvent
766        @param event: Gtk event of widget
767        @rtype: boolean
768        @return: Indicator of the event propagation
769        """
770        pointer = self.get_pointer()
771
772        for node in self.__graph.get_nodes():
773            node.set_draw_info({'over': False})
774
775        result = self.__get_node_by_coordinate(self.get_pointer())
776
777        if result is not None:
778            result[0].set_draw_info({'over': True})
779
780        elif self.__button1_press and self.__last_motion_point is not None:
781
782            ax, ay = pointer
783            ox, oy = self.__last_motion_point
784            tx, ty = self.__translation
785
786            self.__translation = (tx + ax - ox, ty - ay + oy)
787
788        self.__last_motion_point = pointer
789
790        self.grab_focus()
791        self.queue_draw()
792
793        return False
794
795    def expose(self, widget, event):
796        """
797        Drawing callback
798        @type  widget: GtkWidget
799        @param widget: Gtk widget superclass
800        @type  event: GtkEvent
801        @param event: Gtk event of widget
802        @rtype: boolean
803        @return: Indicator of the event propagation
804        """
805        context = widget.window.cairo_create()
806
807        context.rectangle(*event.area)
808        context.set_source_rgb(1.0, 1.0, 1.0)
809        context.fill()
810
811        self.__draw(context)
812
813        return False
814
815    @graph_is_not_empty
816    def __draw(self, context):
817        """
818        Drawing method
819        """
820        # getting allocation reference
821        allocation = self.get_allocation()
822
823        self.__center_of_widget = (allocation.width / 2,
824                                   allocation.height / 2)
825
826        xc, yc = self.__center_of_widget
827
828        ax, ay = self.__translation
829
830        # xc = 320 yc = 240
831
832        # -1.5 | -0.5 ( 480,  360)
833        # -1.0 |  0.0 ( 320,  240)
834        # -0.5 |  0.5 ( 160,  120)
835        #  0.0 |  1.0 (   0,    0)
836        #  0.5 |  1.5 (-160, -120)
837        #  1.0 |  2.0 (-320, -240)
838        #  1.5 |  2.5 (-480, -360)
839
840        # scaling and translate
841        factor = -(self.__scale - 1)
842
843        context.translate(xc * factor + ax, yc * factor - ay)
844
845        if self.__scale != 1.0:
846            context.scale(self.__scale, self.__scale)
847
848        # drawing over node's region
849        if self.__show_region and not self.__animating:
850
851            for node in self.__sorted_nodes:
852
853                not_grouped = not node.get_draw_info('grouped')
854
855                if node.get_draw_info('region') is not None and not_grouped:
856
857                    xc, yc = self.__center_of_widget
858                    r, g, b = REGION_COLORS[node.get_draw_info('region')]
859
860                    start, final = node.get_draw_info('range')
861
862                    i_radius = node.get_coordinate_radius()
863                    f_radius = self.__calc_radius(self.__number_of_rings - 1)
864
865                    is_fill_all = abs(final - start) == 360
866
867                    final = math.radians(final + self.__rotate)
868                    start = math.radians(start + self.__rotate)
869
870                    context.move_to(xc, yc)
871                    context.set_source_rgba(r, g, b, 0.1)
872                    context.new_path()
873                    context.arc(xc, yc, i_radius, -final, -start)
874                    context.arc_negative(xc, yc, f_radius, -start, -final)
875                    context.close_path()
876                    context.fill()
877                    context.stroke()
878
879                    if not is_fill_all:
880
881                        context.set_source_rgb(r, g, b)
882                        context.set_line_width(1)
883
884                        xa, ya = PolarCoordinate(
885                                i_radius, final).to_cartesian()
886                        xb, yb = PolarCoordinate(
887                                f_radius, final).to_cartesian()
888
889                        context.move_to(xc + xa, yc - ya)
890                        context.line_to(xc + xb, yc - yb)
891                        context.stroke()
892
893                        xa, ya = PolarCoordinate(
894                                i_radius, start).to_cartesian()
895                        xb, yb = PolarCoordinate(
896                                f_radius, start).to_cartesian()
897
898                        context.move_to(xc + xa, yc - ya)
899                        context.line_to(xc + xb, yc - yb)
900                        context.stroke()
901
902        # drawing network rings
903        if self.__show_ring and not self.__animating:
904
905            for i in range(1, self.__number_of_rings):
906
907                radius = self.__calc_radius(i)
908
909                context.arc(xc, yc, radius, 0, 2 * math.pi)
910                context.set_source_rgb(0.8, 0.8, 0.8)
911                context.set_line_width(1)
912                context.stroke()
913
914        # drawing nodes and your connections
915        for edge in self.__graph.get_edges():
916
917            # check group constraints for edges
918            a, b = edge.get_nodes()
919
920            a_is_grouped = a.get_draw_info('grouped')
921            b_is_grouped = b.get_draw_info('grouped')
922
923            a_is_group = a.get_draw_info('group')
924            b_is_group = b.get_draw_info('group')
925
926            a_group = a.get_draw_info('group_node')
927            b_group = b.get_draw_info('group_node')
928
929            a_is_child = a in b.get_draw_info('children')
930            b_is_child = b in a.get_draw_info('children')
931
932            last_group = self.__last_group_node
933            groups = [a_group, b_group]
934
935            if last_group in groups and last_group is not None:
936                self.__draw_edge(context, edge)
937
938            elif not a_is_grouped or not b_is_grouped:
939
940                if not (a_is_group and b_is_child or
941                        b_is_group and a_is_child):
942                    self.__draw_edge(context, edge)
943
944            elif a_group != b_group:
945                self.__draw_edge(context, edge)
946
947        for node in reversed(self.__sorted_nodes):
948
949            # check group constraints for nodes
950            group = node.get_draw_info('group_node')
951            grouped = node.get_draw_info('grouped')
952
953            if group == self.__last_group_node or not grouped:
954                self.__draw_node(context, node)
955
956    def __draw_edge(self, context, edge):
957        """
958        Draw the connection between two nodes
959        @type  : Edge
960        @param : The second node that will be connected
961        """
962        a, b = edge.get_nodes()
963
964        xa, ya = a.get_cartesian_coordinate()
965        xb, yb = b.get_cartesian_coordinate()
966        xc, yc = self.__center_of_widget
967
968        a_children = a.get_draw_info('children')
969        b_children = b.get_draw_info('children')
970
971        latency = edge.get_weights_mean()
972
973        # check if isn't an hierarchy connection
974        if a not in b_children and b not in a_children:
975            context.set_source_rgba(1.0, 0.6, 0.1, 0.8)
976
977        elif a.get_draw_info('no_route') or b.get_draw_info('no_route'):
978            context.set_source_rgba(0.0, 0.0, 0.0, 0.8)
979
980        else:
981            context.set_source_rgba(0.1, 0.5, 1.0, 0.8)
982
983        # calculating line thickness by latency
984        if latency is not None:
985
986            min = self.__graph.get_min_edge_mean_weight()
987            max = self.__graph.get_max_edge_mean_weight()
988
989            if max != min:
990                thickness = (latency - min) * 4 / (max - min) + 1
991
992            else:
993                thickness = 1
994
995            context.set_line_width(thickness)
996
997        else:
998
999            context.set_dash([2, 2])
1000            context.set_line_width(1)
1001
1002        context.move_to(xc + xa, yc - ya)
1003        context.line_to(xc + xb, yc - yb)
1004        context.stroke()
1005
1006        context.set_dash([1, 0])
1007
1008        if not self.__animating and self.__show_latency:
1009
1010            if latency is not None:
1011
1012                context.set_font_size(8)
1013                context.set_line_width(1)
1014                context.move_to(xc + (xa + xb) / 2 + 1,
1015                                     yc - (ya + yb) / 2 + 4)
1016                context.show_text(str(round(latency, 2)))
1017                context.stroke()
1018
1019    def __draw_node(self, context, node):
1020        """
1021        Draw nodes and your information
1022        @type  : NetNode
1023        @param : The node to be drawn
1024        """
1025        x, y = node.get_cartesian_coordinate()
1026        xc, yc = self.__center_of_widget
1027        r, g, b = node.get_draw_info('color')
1028        radius = node.get_draw_info('radius')
1029
1030        type = node.get_info('device_type')
1031
1032        x_gap = radius + 2
1033        y_gap = 0
1034
1035        # draw group indication
1036        if node.get_draw_info('group'):
1037
1038            x_gap += 5
1039
1040            if type in SQUARE_TYPES:
1041                context.rectangle(xc + x - radius - 5,
1042                                       yc - y - radius - 5,
1043                                       2 * radius + 10,
1044                                       2 * radius + 10)
1045
1046            else:
1047                context.arc(xc + x, yc - y, radius + 5, 0, 2 * math.pi)
1048
1049            context.set_source_rgb(1.0, 1.0, 1.0)
1050            context.fill_preserve()
1051
1052            if node.deep_search_child(self.__graph.get_main_node()):
1053                context.set_source_rgb(0.0, 0.0, 0.0)
1054
1055            else:
1056                context.set_source_rgb(0.1, 0.5, 1.0)
1057
1058            context.set_line_width(2)
1059            context.stroke()
1060
1061        # draw over node
1062        if node.get_draw_info('over'):
1063
1064            context.set_line_width(0)
1065
1066            if type in SQUARE_TYPES:
1067                context.rectangle(xc + x - radius - 5,
1068                                       yc - y - radius - 5,
1069                                       2 * radius + 10,
1070                                       2 * radius + 10)
1071
1072            else:
1073                context.arc(xc + x, yc - y, radius + 5, 0, 2 * math.pi)
1074
1075            context.set_source_rgb(0.1, 0.5, 1.0)
1076            context.fill_preserve()
1077            context.stroke()
1078
1079        # draw node
1080        if type in SQUARE_TYPES:
1081            context.rectangle(xc + x - radius,
1082                                   yc - y - radius,
1083                                   2 * radius,
1084                                   2 * radius)
1085
1086        else:
1087            context.arc(xc + x, yc - y, radius, 0, 2 * math.pi)
1088
1089        # draw icons
1090        if not self.__animating and self.__show_icon:
1091
1092            icons = list()
1093
1094            if type in ICON_DICT.keys():
1095                icons.append(self.__icon.get_pixbuf(ICON_DICT[type]))
1096
1097            if node.get_info('filtered'):
1098                icons.append(self.__icon.get_pixbuf('padlock'))
1099
1100            for icon in icons:
1101
1102                stride, data = get_pixels_for_cairo_image_surface(icon)
1103
1104                # Cairo documentation says that the correct way to obtain a
1105                # legal stride value is using the function
1106                # cairo.ImageSurface.format_stride_for_width().
1107                # But this method is only available since cairo 1.6. So we are
1108                # using the stride returned by
1109                # get_pixels_for_cairo_image_surface() function.
1110                surface = cairo.ImageSurface.create_for_data(data,
1111                        cairo.FORMAT_ARGB32,
1112                        icon.get_width(),
1113                        icon.get_height(),
1114                        stride)
1115
1116                context.set_source_surface(surface,
1117                        round(xc + x + x_gap),
1118                        round(yc - y + y_gap - 6))
1119                context.paint()
1120
1121                x_gap += 13
1122
1123        # draw node text
1124        context.set_source_rgb(r, g, b)
1125        context.fill_preserve()
1126
1127        if node.get_draw_info('valid'):
1128            context.set_source_rgb(0.0, 0.0, 0.0)
1129
1130        else:
1131            context.set_source_rgb(0.1, 0.5, 1.0)
1132
1133        if not self.__animating and self.__show_address:
1134
1135            context.set_font_size(8)
1136            context.move_to(round(xc + x + x_gap),
1137                                 round(yc - y + y_gap + 4))
1138
1139            hostname = node.get_info('hostname')
1140
1141            if hostname is not None and self.__show_hostname:
1142                context.show_text(hostname)
1143
1144            elif node.get_info('ip') is not None:
1145                context.show_text(node.get_info('ip'))
1146
1147        context.set_line_width(1)
1148        context.stroke()
1149
1150    def __check_fisheye_ring(self):
1151        """
1152        """
1153        if self.__fisheye_ring >= self.__number_of_rings:
1154            self.__fisheye_ring = self.__number_of_rings - 1
1155
1156    def __set_number_of_rings(self, value):
1157        """
1158        """
1159        self.__number_of_rings = value
1160        self.__check_fisheye_ring()
1161
1162    def __fisheye_function(self, ring):
1163        """
1164        """
1165        distance = abs(self.__fisheye_ring - ring)
1166        level_of_detail = self.__ring_gap * self.__fisheye_interest
1167        spread_distance = distance - distance * self.__fisheye_spread
1168
1169        value = level_of_detail / (spread_distance + 1)
1170
1171        if value < self.__min_ring_gap:
1172            value = self.__min_ring_gap
1173
1174        return value
1175
1176    @graph_is_not_empty
1177    @not_is_in_animation
1178    def __update_nodes_positions(self):
1179        """
1180        """
1181        for node in self.__sorted_nodes:
1182
1183            if node.get_draw_info('grouped'):
1184
1185                # deep group check
1186                group = node.get_draw_info('group_node')
1187
1188                while group.get_draw_info('group_node') is not None:
1189                    group = group.get_draw_info('group_node')
1190
1191                ring = group.get_draw_info('ring')
1192                node.set_coordinate_radius(self.__calc_radius(ring))
1193
1194            else:
1195                ring = node.get_draw_info('ring')
1196                node.set_coordinate_radius(self.__calc_radius(ring))
1197
1198    @graph_is_not_empty
1199    def __get_node_by_coordinate(self, point):
1200        """
1201        """
1202        xc, yc = self.__center_of_widget
1203
1204        for node in self.__graph.get_nodes():
1205
1206            if node.get_draw_info('grouped'):
1207                continue
1208
1209            ax, ay = self.__translation
1210
1211            xn, yn = node.get_cartesian_coordinate()
1212            center = (xc + xn * self.__scale + ax, yc - yn * self.__scale - ay)
1213            radius = node.get_draw_info('radius') * self.__scale
1214
1215            type = node.get_info('device_type')
1216
1217            if type in SQUARE_TYPES:
1218                if geometry.is_in_square(point, radius, center):
1219                    return node, center
1220
1221            else:
1222                if geometry.is_in_circle(point, radius, center):
1223                    return node, center
1224
1225        return None
1226
1227    def __calc_radius(self, ring):
1228        """
1229        """
1230        if self.__fisheye:
1231
1232            radius = 0
1233
1234            while ring > 0:
1235
1236                radius += self.__fisheye_function(ring)
1237                ring -= 1
1238
1239        else:
1240            radius = ring * self.__ring_gap
1241
1242        return radius
1243
1244    @graph_is_not_empty
1245    def __arrange_nodes(self):
1246        """
1247        """
1248        new_nodes = set([self.__graph.get_main_node()])
1249        old_nodes = set()
1250
1251        number_of_needed_rings = 1
1252        ring = 0
1253
1254        # while new nodes were found
1255        while len(new_nodes) > 0:
1256
1257            tmp_nodes = set()
1258
1259            # for each new nodes
1260            for node in new_nodes:
1261
1262                old_nodes.add(node)
1263
1264                # set ring location
1265                node.set_draw_info({'ring': ring})
1266
1267                # check group constraints
1268                if (node.get_draw_info('group') or
1269                        node.get_draw_info('grouped')):
1270                    children = node.get_draw_info('children')
1271
1272                else:
1273
1274                    # getting connections and fixing multiple fathers
1275                    children = set()
1276                    for child in self.__graph.get_node_connections(node):
1277                        if child in old_nodes or child in new_nodes:
1278                            continue
1279                        if child.get_draw_info('grouped'):
1280                            continue
1281                        children.add(child)
1282
1283                # setting father foreign
1284                for child in children:
1285                    child.set_draw_info({'father': node})
1286
1287                node.set_draw_info(
1288                        {'children': misc.sort_children(children, node)})
1289                tmp_nodes.update(children)
1290
1291            # check group influence in number of rings
1292            for node in tmp_nodes:
1293
1294                if not node.get_draw_info('grouped'):
1295
1296                    number_of_needed_rings += 1
1297                    break
1298
1299            # update new nodes set
1300            new_nodes.update(tmp_nodes)
1301            new_nodes.difference_update(old_nodes)
1302
1303            ring += 1
1304
1305        self.__set_number_of_rings(number_of_needed_rings)
1306
1307    def __weighted_layout(self):
1308        """
1309        """
1310        # calculating the space needed by each node
1311        self.__graph.get_main_node().set_draw_info({'range': (0, 360)})
1312        new_nodes = set([self.__graph.get_main_node()])
1313
1314        self.__graph.get_main_node().calc_needed_space()
1315
1316        while len(new_nodes) > 0:
1317
1318            node = new_nodes.pop()
1319
1320            # add only no grouped nodes
1321            children = set()
1322            for child in node.get_draw_info('children'):
1323
1324                if not child.get_draw_info('grouped'):
1325                    children.add(child)
1326                    new_nodes.add(child)
1327
1328            if len(children) > 0:
1329
1330                min, max = node.get_draw_info('range')
1331
1332                node_total = max - min
1333                children_need = node.get_draw_info('children_need')
1334
1335                for child in children:
1336
1337                    child_need = child.get_draw_info('space_need')
1338                    child_total = node_total * child_need / children_need
1339
1340                    theta = child_total / 2 + min + self.__rotate
1341
1342                    child.set_coordinate_theta(theta)
1343                    child.set_draw_info({'range': (min, min + child_total)})
1344
1345                    min += child_total
1346
1347    def __symmetric_layout(self):
1348        """
1349        """
1350        self.__graph.get_main_node().set_draw_info({'range': (0, 360)})
1351        new_nodes = set([self.__graph.get_main_node()])
1352
1353        while len(new_nodes) > 0:
1354
1355            node = new_nodes.pop()
1356
1357            # add only no grouped nodes
1358            children = set()
1359            for child in node.get_draw_info('children'):
1360
1361                if not child.get_draw_info('grouped'):
1362                    children.add(child)
1363                    new_nodes.add(child)
1364
1365            if len(children) > 0:
1366
1367                min, max = node.get_draw_info('range')
1368                factor = float(max - min) / len(children)
1369
1370                for child in children:
1371
1372                    theta = factor / 2 + min + self.__rotate
1373
1374                    child.set_coordinate_theta(theta)
1375                    child.set_draw_info({'range': (min, min + factor)})
1376
1377                    min += factor
1378
1379    @graph_is_not_empty
1380    def __calc_layout(self, reference):
1381        """
1382        """
1383        # selecting layout algorithm
1384        if self.__layout == LAYOUT_SYMMETRIC:
1385            self.__symmetric_layout()
1386
1387        elif self.__layout == LAYOUT_WEIGHTED:
1388            self.__weighted_layout()
1389
1390        # rotating focus' children to keep orientation
1391        if reference is not None:
1392
1393            father, angle = reference
1394            theta = father.get_coordinate_theta()
1395            factor = theta - angle
1396
1397            for node in self.__graph.get_nodes():
1398
1399                theta = node.get_coordinate_theta()
1400                node.set_coordinate_theta(theta - factor)
1401
1402                a, b = node.get_draw_info('range')
1403                node.set_draw_info({'range': (a - factor, b - factor)})
1404
1405    @graph_is_not_empty
1406    def __calc_node_positions(self, reference=None):
1407        """
1408        """
1409        # set nodes' hierarchy
1410        self.__arrange_nodes()
1411        self.calc_sorted_nodes()
1412
1413        # set nodes' coordinate radius
1414        for node in self.__graph.get_nodes():
1415
1416            ring = node.get_draw_info('ring')
1417            node.set_coordinate_radius(self.__calc_radius(ring))
1418
1419        # set nodes' coordinate theta
1420        self.__calc_layout(reference)
1421
1422    def __calc_interpolation(self, focus):
1423        """
1424        """
1425        old_main_node = self.__graph.get_main_node()
1426        self.__graph.set_main_node(focus)
1427
1428        # getting initial coordinates
1429        for node in self.__graph.get_nodes():
1430
1431            if self.__interpolation == INTERPOLATION_POLAR:
1432                coordinate = node.get_polar_coordinate()
1433
1434            elif self.__interpolation == INTERPOLATION_CARTESIAN:
1435                coordinate = node.get_cartesian_coordinate()
1436
1437            node.set_draw_info({'start_coordinate': coordinate})
1438
1439        father = focus.get_draw_info('father')
1440
1441        # calculate nodes positions (and father orientation)?
1442        if father is not None:
1443
1444            xa, ya = father.get_cartesian_coordinate()
1445            xb, yb = focus.get_cartesian_coordinate()
1446
1447            angle = math.atan2(yb - ya, xb - xa)
1448            angle = math.degrees(angle)
1449
1450            self.__calc_node_positions((father, 180 + angle))
1451
1452        else:
1453            self.__calc_node_positions()
1454
1455        # steps for slow-in/slow-out animation
1456        steps = range(self.__number_of_frames)
1457
1458        for i in range(len(steps) / 2):
1459            steps[self.__number_of_frames - 1 - i] = steps[i]
1460
1461        # normalize angles and calculate interpolated points
1462        for node in self.__sorted_nodes:
1463
1464            l2di = Linear2DInterpolator()
1465
1466            # change grouped nodes coordinate
1467            if node.get_draw_info('grouped'):
1468
1469                group_node = node.get_draw_info('group_node')
1470                a, b = group_node.get_draw_info('final_coordinate')
1471
1472                if self.__interpolation == INTERPOLATION_POLAR:
1473                    node.set_polar_coordinate(a, b)
1474
1475                elif self.__interpolation == INTERPOLATION_CARTESIAN:
1476                    node.set_cartesian_coordinate(a, b)
1477
1478            # change interpolation method
1479            if self.__interpolation == INTERPOLATION_POLAR:
1480
1481                coordinate = node.get_polar_coordinate()
1482                node.set_draw_info({'final_coordinate': coordinate})
1483
1484                # adjusting polar coordinates
1485                ri, ti = node.get_draw_info('start_coordinate')
1486                rf, tf = node.get_draw_info('final_coordinate')
1487
1488                # normalization [0, 360]
1489                ti = geometry.normalize_angle(ti)
1490                tf = geometry.normalize_angle(tf)
1491
1492                # against longest path
1493                ti, tf = geometry.calculate_short_path(ti, tf)
1494
1495                # main node goes direct to center (no arc)
1496                if node == self.__graph.get_main_node():
1497                    tf = ti
1498
1499                # old main node goes direct to new position (no arc)
1500                if node == old_main_node:
1501                    ti = tf
1502
1503                node.set_draw_info({'start_coordinate': (ri, ti)})
1504                node.set_draw_info({'final_coordinate': (rf, tf)})
1505
1506            elif self.__interpolation == INTERPOLATION_CARTESIAN:
1507
1508                coordinate = node.get_cartesian_coordinate()
1509                node.set_draw_info({'final_coordinate': coordinate})
1510
1511            # calculate interpolated points
1512            ai, bi = node.get_draw_info('start_coordinate')
1513            af, bf = node.get_draw_info('final_coordinate')
1514
1515            l2di.set_start_point(ai, bi)
1516            l2di.set_final_point(af, bf)
1517
1518            if self.__interpolation_slow_in_out:
1519                points = l2di.get_weighed_points(
1520                        self.__number_of_frames, steps)
1521
1522            else:
1523                points = l2di.get_points(self.__number_of_frames)
1524
1525            node.set_draw_info({'interpolated_coordinate': points})
1526
1527        return True
1528
1529    def __livens_up(self, index=0):
1530        """
1531        """
1532        if self.__graph is None:
1533            # Bail out if the graph became empty during an animation.
1534            self.__last_group_node = None
1535            self.__animating = False
1536            return False
1537
1538        # prepare interpolated points
1539        if index == 0:
1540
1541            # prevent unnecessary animation
1542            no_need_to_move = True
1543
1544            for node in self.__graph.get_nodes():
1545
1546                ai, bi = node.get_draw_info('start_coordinate')
1547                af, bf = node.get_draw_info('final_coordinate')
1548
1549                start_c = round(ai), round(bi)
1550                final_c = round(af), round(bf)
1551
1552                if start_c != final_c:
1553                    no_need_to_move = False
1554
1555            if no_need_to_move:
1556
1557                self.__animating = False
1558                return False
1559
1560        # move all nodes for pass 'index'
1561        for node in self.__graph.get_nodes():
1562
1563            a, b = node.get_draw_info('interpolated_coordinate')[index]
1564
1565            if self.__interpolation == INTERPOLATION_POLAR:
1566                node.set_polar_coordinate(a, b)
1567
1568            elif self.__interpolation == INTERPOLATION_CARTESIAN:
1569                node.set_cartesian_coordinate(a, b)
1570
1571        self.queue_draw()
1572
1573        # animation continue condition
1574        if index < self.__number_of_frames - 1:
1575            gobject.timeout_add(self.__animation_rate,  # time to recall
1576                                self.__livens_up,       # recursive call
1577                                index + 1)              # next iteration
1578        else:
1579            self.__last_group_node = None
1580            self.__animating = False
1581
1582        return False
1583
1584    @not_is_in_animation
1585    def set_graph(self, graph):
1586        """
1587        Set graph to be displayed in layout
1588        @type  : Graph
1589        @param : Set the graph used in visualization
1590        """
1591        if graph.get_number_of_nodes() > 0:
1592
1593            self.__graph = graph
1594
1595            self.__calc_node_positions()
1596            self.queue_draw()
1597
1598        else:
1599            self.__graph = None
1600
1601    def get_scanned_nodes(self):
1602        """
1603        """
1604        nodes = list()
1605        if self.__graph is None:
1606            return nodes
1607
1608        for node in self.__graph.get_nodes():
1609
1610            if node.get_draw_info('scanned'):
1611                nodes.append(node)
1612
1613        return nodes
1614
1615    def get_graph(self):
1616        """
1617        """
1618        return self.__graph
1619
1620    def set_empty(self):
1621        """
1622        """
1623        del(self.__graph)
1624        self.__graph = None
1625
1626        self.queue_draw()
1627
1628    def get_rotation(self):
1629        """
1630        """
1631        return self.__rotate
1632
1633    @graph_is_not_empty
1634    def set_rotation(self, angle):
1635        """
1636        """
1637        delta = angle - self.__rotate
1638        self.__rotate = angle
1639
1640        for node in self.__graph.get_nodes():
1641
1642            theta = node.get_coordinate_theta()
1643            node.set_coordinate_theta(theta + delta)
1644
1645        self.queue_draw()
1646
1647    def get_translation(self):
1648        """
1649        """
1650        return self.__translation
1651
1652    @graph_is_not_empty
1653    def set_translation(self, translation):
1654        """
1655        """
1656        self.__translation = translation
1657        self.queue_draw()
1658
1659    def is_empty(self):
1660        """
1661        """
1662        return self.__graph is None
1663
1664    def is_in_animation(self):
1665        """
1666        """
1667        return self.__animating
1668
1669    def calc_sorted_nodes(self):
1670        """
1671        """
1672        self.__sorted_nodes = list(self.__graph.get_nodes())
1673        self.__sorted_nodes.sort(key=lambda n: n.get_draw_info('ring'))
1674
1675
1676class NetNode(Node):
1677    """
1678    Node class for radial network widget
1679    """
1680    def __init__(self):
1681        """
1682        """
1683        self.__draw_info = dict()
1684        """Hash with draw information"""
1685        self.__coordinate = PolarCoordinate()
1686
1687        super(NetNode, self).__init__()
1688
1689    def get_host(self):
1690        """
1691        Set the HostInfo that this node represents
1692        """
1693        return self.get_data()
1694
1695    def set_host(self, host):
1696        """
1697        Set the HostInfo that this node represents
1698        """
1699        self.set_data(host)
1700
1701    def get_info(self, info):
1702        """Return various information extracted from the host set with
1703        set_host."""
1704        host = self.get_data()
1705        if host is not None:
1706            if info == "number_of_open_ports":
1707                return host.get_port_count_by_states(["open"])
1708            elif info == "vulnerability_score":
1709                num_open_ports = host.get_port_count_by_states(["open"])
1710                if num_open_ports < 3:
1711                    return 0
1712                elif num_open_ports < 7:
1713                    return 1
1714                else:
1715                    return 2
1716            elif info == "addresses":
1717                addresses = []
1718                if host.ip is not None:
1719                    addresses.append(host.ip)
1720                if host.ipv6 is not None:
1721                    addresses.append(host.ipv6)
1722                if host.mac is not None:
1723                    addresses.append(host.mac)
1724                return addresses
1725            elif info == "ip":
1726                for addr in (host.ip, host.ipv6, host.mac):
1727                    if addr:
1728                        return addr.get("addr")
1729            elif info == "hostnames":
1730                hostnames = []
1731                for hostname in host.hostnames:
1732                    copy = {}
1733                    copy["name"] = hostname.get("hostname", "")
1734                    copy["type"] = hostname.get("hostname_type", "")
1735                    hostnames.append(copy)
1736                return hostnames
1737            elif info == "hostname":
1738                return host.get_hostname()
1739            elif info == "uptime":
1740                if host.uptime.get("seconds") or host.uptime.get("lastboot"):
1741                    return host.uptime
1742            elif info == "device_type":
1743                osmatch = host.get_best_osmatch()
1744                if osmatch is None:
1745                    return None
1746                osclasses = osmatch['osclasses']
1747                if len(osclasses) == 0:
1748                    return None
1749                types = ["router", "wap", "switch", "firewall"]
1750                for type in types:
1751                    if type in osclasses[0].get("type", "").lower():
1752                        return type
1753            elif info == "os":
1754                os = {}
1755
1756                # osmatches
1757                if len(host.osmatches) > 0 and \
1758                   host.osmatches[0]["accuracy"] != "" and \
1759                   host.osmatches[0]["name"] != "":
1760                    if os is None:
1761                        os = {}
1762                    os["matches"] = host.osmatches
1763                    os["matches"][0]["db_line"] = 0     # not supported
1764
1765                    os_classes = []
1766                    for osclass in host.osmatches[0]["osclasses"]:
1767                        os_class = {}
1768
1769                        os_class["type"] = osclass.get("type", "")
1770                        os_class["vendor"] = osclass.get("vendor", "")
1771                        os_class["accuracy"] = osclass.get("accuracy", "")
1772                        os_class["os_family"] = osclass.get("osfamily", "")
1773                        os_class["os_gen"] = osclass.get("osgen", "")
1774
1775                        os_classes.append(os_class)
1776                    os["classes"] = os_classes
1777
1778                # ports_used
1779                if len(host.ports_used) > 0:
1780                    if os is None:
1781                        os = {}
1782                    os_portsused = []
1783
1784                    for portused in host.ports_used:
1785                        os_portused = {}
1786
1787                        os_portused["state"] = portused.get("state", "")
1788                        os_portused["protocol"] = portused.get("proto", "")
1789                        os_portused["id"] = int(portused.get("portid", "0"))
1790
1791                        os_portsused.append(os_portused)
1792
1793                    os["used_ports"] = os_portsused
1794
1795                if len(os) > 0:
1796                    os["fingerprint"] = ""
1797                    return os
1798            elif info == "sequences":
1799                # getting sequences information
1800                sequences = {}
1801                # If all fields are empty, we don't put it into the sequences
1802                # list
1803                if reduce(lambda x, y: x + y,
1804                        host.tcpsequence.values(), "") != "":
1805                    tcp = {}
1806                    if host.tcpsequence.get("index", "") != "":
1807                        tcp["index"] = int(host.tcpsequence["index"])
1808                    else:
1809                        tcp["index"] = 0
1810                    tcp["class"] = ""   # not supported
1811                    tcp["values"] = host.tcpsequence.get(
1812                            "values", "").split(",")
1813                    tcp["difficulty"] = host.tcpsequence.get("difficulty", "")
1814                    sequences["tcp"] = tcp
1815                if reduce(lambda x, y: x + y,
1816                        host.ipidsequence.values(), "") != "":
1817                    ip_id = {}
1818                    ip_id["class"] = host.ipidsequence.get("class", "")
1819                    ip_id["values"] = host.ipidsequence.get(
1820                            "values", "").split(",")
1821                    sequences["ip_id"] = ip_id
1822                if reduce(lambda x, y: x + y,
1823                        host.tcptssequence.values(), "") != "":
1824                    tcp_ts = {}
1825                    tcp_ts["class"] = host.tcptssequence.get("class", "")
1826                    tcp_ts["values"] = host.tcptssequence.get(
1827                            "values", "").split(",")
1828                    sequences["tcp_ts"] = tcp_ts
1829                return sequences
1830            elif info == "filtered":
1831                if (len(host.extraports) > 0 and
1832                        host.extraports[0]["state"] == "filtered"):
1833                    return True
1834                else:
1835                    for port in host.ports:
1836                        if port["port_state"] == "filtered":
1837                            return True
1838                return False
1839            elif info == "ports":
1840                ports = list()
1841                for host_port in host.ports:
1842                    port = dict()
1843                    state = dict()
1844                    service = dict()
1845
1846                    port["id"] = int(host_port.get("portid", ""))
1847                    port["protocol"] = host_port.get("protocol", "")
1848
1849                    state["state"] = host_port.get("port_state", "")
1850                    state["reason"] = ""        # not supported
1851                    state["reason_ttl"] = ""    # not supported
1852                    state["reason_ip"] = ""     # not supported
1853
1854                    service["name"] = host_port.get("service_name", "")
1855                    service["conf"] = host_port.get("service_conf", "")
1856                    service["method"] = host_port.get("service_method", "")
1857                    service["version"] = host_port.get("service_version", "")
1858                    service["product"] = host_port.get("service_product", "")
1859                    service["extrainfo"] = host_port.get(
1860                            "service_extrainfo", "")
1861
1862                    port["state"] = state
1863                    port["scripts"] = None      # not supported
1864                    port["service"] = service
1865
1866                    ports.append(port)
1867                return ports
1868            elif info == "extraports":
1869                # extraports
1870                all_extraports = list()
1871                for extraport in host.extraports:
1872                    extraports = dict()
1873                    extraports["count"] = int(extraport.get("count", ""))
1874                    extraports["state"] = extraport.get("state", "")
1875                    extraports["reason"] = list()       # not supported
1876                    extraports["all_reason"] = list()   # not supported
1877
1878                    all_extraports.append(extraports)
1879                return all_extraports
1880            elif info == "trace":
1881                # getting traceroute information
1882                if len(host.trace) > 0:
1883                    trace = {}
1884                    hops = []
1885
1886                    for host_hop in host.trace.get("hops", []):
1887                        hop = {}
1888                        hop["ip"] = host_hop.get("ipaddr", "")
1889                        hop["ttl"] = int(host_hop.get("ttl", ""))
1890                        hop["rtt"] = host_hop.get("rtt", "")
1891                        hop["hostname"] = host_hop.get("host", "")
1892
1893                        hops.append(hop)
1894
1895                    trace["hops"] = hops
1896                    trace["port"] = host.trace.get("port", "")
1897                    trace["protocol"] = host.trace.get("proto", "")
1898
1899                    return trace
1900        else:  # host is None
1901            pass
1902
1903        return None
1904
1905    def get_coordinate_theta(self):
1906        """
1907        """
1908        return self.__coordinate.get_theta()
1909
1910    def get_coordinate_radius(self):
1911        """
1912        """
1913        return self.__coordinate.get_radius()
1914
1915    def set_coordinate_theta(self, value):
1916        """
1917        """
1918        self.__coordinate.set_theta(value)
1919
1920    def set_coordinate_radius(self, value):
1921        """
1922        """
1923        self.__coordinate.set_radius(value)
1924
1925    def set_polar_coordinate(self, r, t):
1926        """
1927        Set polar coordinate
1928        @type  r: number
1929        @param r: The radius of coordinate
1930        @type  t: number
1931        @param t: The angle (theta) of coordinate in radians
1932        """
1933        self.__coordinate.set_coordinate(r, t)
1934
1935    def get_polar_coordinate(self):
1936        """
1937        Get cartesian coordinate
1938        @rtype: tuple
1939        @return: Cartesian coordinates (x, y)
1940        """
1941        return self.__coordinate.get_coordinate()
1942
1943    def set_cartesian_coordinate(self, x, y):
1944        """
1945        Set cartesian coordinate
1946        """
1947        cartesian = CartesianCoordinate(x, y)
1948        r, t = cartesian.to_polar()
1949
1950        self.set_polar_coordinate(r, math.degrees(t))
1951
1952    def get_cartesian_coordinate(self):
1953        """
1954        Get cartesian coordinate
1955        @rtype: tuple
1956        @return: Cartesian coordinates (x, y)
1957        """
1958        return self.__coordinate.to_cartesian()
1959
1960    def get_draw_info(self, info=None):
1961        """
1962        Get draw information about node
1963        @type  : string
1964        @param : Information name
1965        @rtype: mixed
1966        @return: The requested information
1967        """
1968        if info is None:
1969            return self.__draw_info
1970
1971        return self.__draw_info.get(info)
1972
1973    def set_draw_info(self, info):
1974        """
1975        Set draw information
1976        @type  : dict
1977        @param : Draw information dictionary
1978        """
1979        for key in info:
1980            self.__draw_info[key] = info[key]
1981
1982    def deep_search_child(self, node):
1983        """
1984        """
1985        for child in self.get_draw_info('children'):
1986
1987            if child == node:
1988                return True
1989
1990            elif child.deep_search_child(node):
1991                return True
1992
1993        return False
1994
1995    def set_subtree_info(self, info):
1996        """
1997        """
1998        for child in self.get_draw_info('children'):
1999
2000            child.set_draw_info(info)
2001
2002            if not child.get_draw_info('group'):
2003                child.set_subtree_info(info)
2004
2005    def calc_needed_space(self):
2006        """
2007        """
2008        number_of_children = len(self.get_draw_info('children'))
2009
2010        sum_angle = 0
2011        own_angle = 0
2012
2013        if number_of_children > 0 and not self.get_draw_info('group'):
2014
2015            for child in self.get_draw_info('children'):
2016
2017                child.calc_needed_space()
2018                sum_angle += child.get_draw_info('space_need')
2019
2020        distance = self.get_coordinate_radius()
2021        size = self.get_draw_info('radius') * 2
2022        own_angle = geometry.angle_from_object(distance, size)
2023
2024        self.set_draw_info({'children_need': sum_angle})
2025        self.set_draw_info({'space_need': max(sum_angle, own_angle)})
2026