/* rlc_lte_graph.c * By Martin Mathieson * Based upon tcp_graph.c * * $Id$ * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #if GTK_CHECK_VERSION(3,0,0) # include #endif #include #include #include #include #include "../globals.h" #include "ui/simple_dialog.h" #include "../stat_menu.h" #include "ui/gtk/gui_utils.h" #include "ui/gtk/dlg_utils.h" #include "ui/gtk/gui_stat_menu.h" #include "ui/gtk/tap_param_dlg.h" #include "ui/gtk/old-gtk-compat.h" #define AXIS_HORIZONTAL 0 #define AXIS_VERTICAL 1 #define WINDOW_TITLE_LENGTH 256 #define MOUSE_BUTTON_LEFT 1 #define MOUSE_BUTTON_MIDDLE 2 #define MOUSE_BUTTON_RIGHT 3 #define MAX_PIXELS_PER_SN 90 #define MAX_PIXELS_PER_SECOND 50000 extern int proto_rlc_lte; struct segment { struct segment *next; guint32 num; /* framenum */ guint32 rel_secs; guint32 rel_usecs; guint32 abs_secs; guint32 abs_usecs; gboolean isControlPDU; guint16 SN; guint16 ACKNo; #define MAX_NACKs 128 guint16 noOfNACKs; guint16 NACKs[MAX_NACKs]; guint16 ueid; guint16 channelType; guint16 channelId; guint8 rlcMode; guint8 direction; }; struct line { double x1, y1, x2, y2; }; struct irect { int x, y, width, height; }; typedef enum { ELMT_NONE=0, ELMT_LINE=1 } ElementType; struct line_params { struct line dim; }; struct element { ElementType type; GdkColor *elment_color_p; struct segment *parent; union { struct line_params line; } p; }; struct element_list { struct element_list *next; struct element *elements; }; struct axis { struct graph *g; /* which graph we belong to */ GtkWidget *drawing_area; /* Double-buffering to avoid flicker */ #if GTK_CHECK_VERSION(2,22,0) cairo_surface_t *surface[2]; #else GdkPixmap *pixmap[2]; #endif /* Which of the 2 buffers we are currently showing */ int displayed; #define AXIS_ORIENTATION 1 << 0 int flags; /* dim and orig (relative to origin of window) of axis' pixmap */ struct irect p; /* dim and orig (relative to origin of axis' pixmap) of scale itself */ struct irect s; gdouble min, max; gdouble major, minor; /* major and minor ticks */ const char **label; }; #define HAXIS_INIT_HEIGHT 70 #define VAXIS_INIT_WIDTH 100 #define TITLEBAR_HEIGHT 50 #define RMARGIN_WIDTH 30 struct style_rlc_lte { GdkColor seq_color; GdkColor ack_color[2]; int flags; }; /* style flags */ #define TIME_ORIGIN 0x10 /* show time from beginning of capture as opposed to time from beginning * of the connection */ #define TIME_ORIGIN_CAP 0x10 #define TIME_ORIGIN_CONN 0x00 struct cross { int x, y; int draw; /* indicates whether we should draw cross at all */ int erase_needed; /* indicates whether currently drawn at recorded position */ }; struct bounds { double x0, y0, width, height; }; struct zoom { double x, y; }; struct zooms { double x, y; double step_x, step_y; struct zoom initial; #define ZOOM_OUT (1 << 0) int flags; }; struct grab { int grabbed; int x, y; }; struct graph { #define GRAPH_DESTROYED (1 << 0) int flags; GtkWidget *toplevel; /* keypress handler needs this */ GtkWidget *drawing_area; PangoFontDescription *font; /* font used for annotations etc. */ /* Double-buffering */ #if GTK_CHECK_VERSION(2,22,0) cairo_surface_t *title_surface; cairo_surface_t *surface[2]; #else GdkPixmap *title_pixmap; GdkPixmap *pixmap[2]; #endif int displayed; /* which of both pixmaps is on screen right now */ /* Next 4 attribs describe the graph in natural units, before any scaling. * For example, if we want to display graph of TCP conversation that * started 112.309845 s after beginning of the capture and ran until * 479.093582 s, 237019 B went through the connection (in one direction) * starting with isn 31934022, then (bounds.x0, bounds.y0)=(112.309845, * 31934022) and (bounds.width, bounds.height)=(366.783737, 237019). */ struct bounds bounds; /* dimensions and position of the graph, both expressed already in pixels. * x and y give the position of upper left corner of the graph relative * to origin of the graph window, size is basically bounds*zoom */ struct irect geom; /* viewport (=graph window area which is reserved for graph itself), its * size and position relative to origin of the graph window */ struct irect wp; struct grab grab; /* If we need to display 237019 sequence numbers (=bytes) onto say 500 * pixels, we have to scale the graph down by factor of 0.002109. This * number would be zoom.y. Obviously, both directions have separate zooms.*/ struct zooms zoom; struct cross cross; struct axis *x_axis, *y_axis; /* List of segments to show */ struct segment *segments; struct segment *current; /* Lists of elements to draw */ struct element_list *elists; /* element lists */ /* Colours, etc to be used in drawing */ struct style_rlc_lte style; }; #if !GTK_CHECK_VERSION(3,0,0) static GdkGC *xor_gc = NULL; #endif static int refnum=0; #define debug(section) if (debugging & section) /* print function entry points */ #define DBS_FENTRY (1 << 0) #define DBS_AXES_TICKS (1 << 1) #define DBS_AXES_DRAWING (1 << 2) #define DBS_GRAPH_DRAWING (1 << 3) #define DBS_TPUT_ELMTS (1 << 4) /*int debugging = DBS_FENTRY;*/ /*static int debugging = 1; */ /*int debugging = DBS_AXES_TICKS;*/ /*int debugging = DBS_AXES_DRAWING;*/ /*int debugging = DBS_GRAPH_DRAWING;*/ /*int debugging = DBS_TPUT_ELMTS;*/ int debugging = 0; static void create_gui(struct graph * ); static void create_drawing_area(struct graph * ); static void callback_toplevel_destroy(GtkWidget * , gpointer ); static void callback_create_help(GtkWidget * , gpointer ); static void get_mouse_position(GtkWidget *, int *pointer_x, int *pointer_y, GdkModifierType *mask); static rlc_lte_tap_info *select_rlc_lte_session(capture_file *, struct segment * ); static int compare_headers(guint16 ueid1, guint16 channelType1, guint16 channelId1, guint8 rlcMode1, guint8 direction1, guint16 ueid2, guint16 channelType2, guint16 channelId2, guint8 rlcMode2, guint8 direction2, gboolean isControlFrame); static void get_data_control_counts(struct graph *g, int *data, int *acks, int *nacks); static struct graph *graph_new(void); static void graph_destroy(struct graph * ); static void graph_initialize_values(struct graph * ); static void graph_init_sequence(struct graph * ); static void draw_element_line(struct graph * , struct element * , cairo_t * , GdkColor *new_color); static void graph_display(struct graph * ); static void graph_pixmaps_create(struct graph * ); static void graph_pixmaps_switch(struct graph * ); static void graph_pixmap_draw(struct graph * ); static void graph_pixmap_display(struct graph * ); static void graph_element_lists_make(struct graph * ); static void graph_element_lists_free(struct graph * ); static void graph_element_lists_initialize(struct graph * ); static void graph_title_pixmap_create(struct graph * ); static void graph_title_pixmap_draw(struct graph * ); static void graph_title_pixmap_display(struct graph * ); static void graph_segment_list_get(struct graph * ); static void graph_segment_list_free(struct graph * ); static void graph_select_segment(struct graph * , int , int ); static int line_detect_collision(struct element * , int , int ); static void axis_pixmaps_create(struct axis * ); static void axis_pixmaps_switch(struct axis * ); static void axis_display(struct axis * ); static void v_axis_pixmap_draw(struct axis * ); static void h_axis_pixmap_draw(struct axis * ); static void axis_pixmap_display(struct axis * ); static void axis_compute_ticks(struct axis * , double , double , int ); static double axis_zoom_get(struct axis * , int ); static void axis_ticks_up(int * , int * ); static void axis_ticks_down(int * , int * ); static void axis_destroy(struct axis * ); static int get_label_dim(struct axis * , int , double ); static void toggle_crosshairs(struct graph *); static void cross_draw(struct graph * , int x, int y); static void cross_erase(struct graph * ); static gboolean motion_notify_event(GtkWidget * , GdkEventMotion * , gpointer ); static void toggle_time_origin(struct graph * ); static void restore_initial_graph_view(struct graph *g); static gboolean configure_event(GtkWidget * , GdkEventConfigure * , gpointer ); #if GTK_CHECK_VERSION(3,0,0) static gboolean draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data); #else static gboolean expose_event(GtkWidget * , GdkEventExpose * , gpointer ); #endif static gboolean button_press_event(GtkWidget * , GdkEventButton * , gpointer ); static gboolean button_release_event(GtkWidget * , GdkEventButton * , gpointer ); static gboolean key_press_event(GtkWidget * , GdkEventKey * , gpointer ); static void graph_initialize(struct graph *); static void graph_get_bounds(struct graph *); static void graph_read_config(struct graph *); static void rlc_lte_make_elmtlist(struct graph *); #if defined(_WIN32) && !defined(__MINGW32__) static int rint(double ); /* compiler template for Windows */ #endif /* * Uncomment the following define to revert WIN32 to * use original mouse button controls */ /* XXX - what about OS X? */ static char helptext[] = "Here's what you can do:\n" "\n" " Left Mouse Button selects segment under cursor in Wireshark's packet list\n" " Middle Mouse Button zooms in (towards area under cursor)\n" " Right Mouse Button moves the graph (if zoomed in)\n" "\n" " toggles crosshairs on/off\n" "\n" " 'i' or '+' zoom in (towards area under mouse pointer)\n" " 'o' or '-' zoom out\n" " (add shift to lock Y axis, control to lock X axis)\n" " 'r' or restore graph to initial state (zoom out max)\n" " 't' toggle time axis to being at zero, or to use time in capture\n" "\n" " move view left by 100 pixels (if zoomed in)\n" " move view right 100 pixels (if zoomed in)\n" " move view up by 100 pixels (if zoomed in)\n" " move view down by 100 pixels (if zoomed in)\n" "\n" " move view left by 10 pixels (if zoomed in)\n" " move view right 10 pixels (if zoomed in)\n" " move view up by 10 pixels (if zoomed in)\n" " move view down by 10 pixels (if zoomed in)\n" "\n" " move view left by 1 pixel (if zoomed in)\n" " move view right 1 pixel (if zoomed in)\n" " move view up by 1 pixel (if zoomed in)\n" " move view down by 1 pixel (if zoomed in)\n" "\n" " move up by a large number of pixels (if zoomed in)\n" " move down by a large number of pixels (if zoomed in)\n" ; static void set_busy_cursor(GdkWindow *w) { GdkCursor* cursor = gdk_cursor_new(GDK_WATCH); gdk_window_set_cursor(w, cursor); gdk_flush(); #if GTK_CHECK_VERSION(3,0,0) g_object_unref(cursor); #else gdk_cursor_unref(cursor); #endif } static void unset_busy_cursor(GdkWindow *w) { gdk_window_set_cursor(w, NULL); gdk_flush(); } void rlc_lte_graph_cb(GtkAction *action _U_, gpointer user_data _U_) { struct segment current; struct graph *g; debug(DBS_FENTRY) puts("rlc_lte_graph_cb()"); /* Can we choose an RLC channel from the selected frame? */ if (!select_rlc_lte_session(&cfile, ¤t)) { return; } if (!(g = graph_new())) { return; } refnum++; graph_initialize_values(g); /* Get our list of segments from the packet list */ graph_segment_list_get(g); create_gui(g); graph_init_sequence(g); } static void create_gui(struct graph *g) { debug(DBS_FENTRY) puts("create_gui()"); /* create_text_widget(g); */ create_drawing_area(g); } static void create_drawing_area(struct graph *g) { #if GTK_CHECK_VERSION(3,0,0) GtkStyleContext *context; #else GdkColormap *colormap; GdkColor color; #endif char *display_name; char window_title[WINDOW_TITLE_LENGTH]; struct segment current; rlc_lte_tap_info *thdr; GtkAllocation widget_alloc; debug(DBS_FENTRY) puts("create_drawing_area()"); thdr=select_rlc_lte_session(&cfile, ¤t); display_name = cf_get_display_name(&cfile); /* Set channel details in title */ g_snprintf(window_title, WINDOW_TITLE_LENGTH, "LTE RLC Graph %d: %s (UE-%u, chan=%s%u %s - %s)", refnum, display_name, thdr->ueid, (thdr->channelType == CHANNEL_TYPE_SRB) ? "SRB" : "DRB", thdr->channelId, (thdr->direction == DIRECTION_UPLINK) ? "UL" : "DL", (thdr->rlcMode == RLC_UM_MODE) ? "UM" : "AM"); g_free(display_name); g->toplevel = dlg_window_new("RLC Graph"); gtk_window_set_title(GTK_WINDOW(g->toplevel), window_title); gtk_widget_set_name(g->toplevel, "Test Graph"); /* Create the drawing area */ g->drawing_area = gtk_drawing_area_new(); g->x_axis->drawing_area = g->y_axis->drawing_area = g->drawing_area; gtk_widget_set_size_request(g->drawing_area, g->wp.width + g->wp.x + RMARGIN_WIDTH, g->wp.height + g->wp.y + g->x_axis->s.height); gtk_widget_show(g->drawing_area); #if GTK_CHECK_VERSION(3,0,0) g_signal_connect(g->drawing_area, "draw", G_CALLBACK(draw_event), g); #else g_signal_connect(g->drawing_area, "expose_event", G_CALLBACK(expose_event), g); #endif g_signal_connect(g->drawing_area, "button_press_event", G_CALLBACK(button_press_event), g); g_signal_connect(g->drawing_area, "button_release_event", G_CALLBACK(button_release_event), g); g_signal_connect(g->toplevel, "destroy", G_CALLBACK(callback_toplevel_destroy), g); g_signal_connect(g->drawing_area, "motion_notify_event", G_CALLBACK(motion_notify_event), g); /* why doesn't drawing area send key_press_signals? */ g_signal_connect(g->toplevel, "key_press_event", G_CALLBACK(key_press_event), g); gtk_widget_set_events(g->toplevel, GDK_KEY_PRESS_MASK|GDK_KEY_RELEASE_MASK); gtk_widget_set_events(g->drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); gtk_container_add(GTK_CONTAINER(g->toplevel), g->drawing_area); gtk_widget_show(g->toplevel); /* In case we didn't get what we asked for */ gtk_widget_get_allocation(GTK_WIDGET(g->drawing_area), &widget_alloc); g->wp.width = widget_alloc.width - g->wp.x - RMARGIN_WIDTH; g->wp.height = widget_alloc.height - g->wp.y - g->x_axis->s.height; #if GTK_CHECK_VERSION(3,0,0) context = gtk_widget_get_style_context(g->drawing_area); gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_FONT, &g->font, NULL); #else g->font = gtk_widget_get_style(g->drawing_area)->font_desc; colormap = gtk_widget_get_colormap(GTK_WIDGET(g->drawing_area)); if (!xor_gc) { xor_gc = gdk_gc_new(gtk_widget_get_window(g->drawing_area)); gdk_gc_set_function(xor_gc, GDK_XOR); if (!gdk_color_parse("gray15", &color)) { /* * XXX - do more than just warn. */ simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK, "Could not parse color gray15."); } if (!gdk_colormap_alloc_color(colormap, &color, FALSE, TRUE)) { /* * XXX - do more than just warn. */ simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK, "Could not allocate color gray15."); } gdk_gc_set_foreground(xor_gc, &color); } #endif g_signal_connect(g->drawing_area, "configure_event", G_CALLBACK(configure_event), g); } static void callback_toplevel_destroy(GtkWidget *widget _U_, gpointer data) { struct graph *g = (struct graph * )data; if (!(g->flags & GRAPH_DESTROYED)) { g->flags |= GRAPH_DESTROYED; graph_destroy((struct graph * )data); } } static void callback_create_help(GtkWidget *widget _U_, gpointer data _U_) { GtkWidget *toplevel, *vbox, *text, *scroll, *bbox, *close_bt; GtkTextBuffer *buf; toplevel = dlg_window_new("Help for LTE RLC graphing"); gtk_window_set_default_size(GTK_WINDOW(toplevel), 540, 540); vbox = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0, FALSE); gtk_container_set_border_width(GTK_CONTAINER(vbox), 12); gtk_container_add(GTK_CONTAINER(toplevel), vbox); scroll = scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); text = gtk_text_view_new(); gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); gtk_text_buffer_set_text(buf, helptext, -1); gtk_container_add(GTK_CONTAINER(scroll), text); /* Button row. */ bbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL); gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); gtk_widget_show(bbox); close_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CLOSE); window_set_cancel_button(toplevel, close_bt, window_cancel_button_cb); g_signal_connect(toplevel, "delete_event", G_CALLBACK(window_delete_event_cb), NULL); gtk_widget_show_all(toplevel); window_present(toplevel); } static void get_mouse_position(GtkWidget *widget, int *pointer_x, int *pointer_y, GdkModifierType *mask) { #if GTK_CHECK_VERSION(3,0,0) gdk_window_get_device_position (gtk_widget_get_window(widget), gdk_device_manager_get_client_pointer( gdk_display_get_device_manager( gtk_widget_get_display(GTK_WIDGET(widget)))), pointer_x, pointer_y, mask); #else gdk_window_get_pointer (gtk_widget_get_window(widget), pointer_x, pointer_y, mask); #endif } static struct graph *graph_new(void) { struct graph *g; g = (struct graph * )g_malloc0(sizeof(struct graph)); graph_element_lists_initialize(g); g->x_axis = (struct axis * )g_malloc0(sizeof(struct axis)); g->y_axis = (struct axis * )g_malloc0(sizeof(struct axis)); g->x_axis->g = g; g->x_axis->flags = 0; g->x_axis->flags |= AXIS_ORIENTATION; g->x_axis->s.x = g->x_axis->s.y = 0; g->x_axis->s.height = HAXIS_INIT_HEIGHT; g->x_axis->p.x = VAXIS_INIT_WIDTH; g->x_axis->p.height = HAXIS_INIT_HEIGHT; g->y_axis->g = g; g->y_axis->flags = 0; g->y_axis->flags &= ~AXIS_ORIENTATION; g->y_axis->p.x = g->y_axis->p.y = 0; g->y_axis->p.width = VAXIS_INIT_WIDTH; g->y_axis->s.x = 0; g->y_axis->s.y = TITLEBAR_HEIGHT; g->y_axis->s.width = VAXIS_INIT_WIDTH; return g; } static void graph_initialize_values(struct graph *g) { g->geom.width = g->wp.width = 750; g->geom.height = g->wp.height = 550; g->geom.x = g->wp.x = VAXIS_INIT_WIDTH; g->geom.y = g->wp.y = TITLEBAR_HEIGHT; g->flags = 0; g->zoom.x = g->zoom.y = 1.0; /* Zooming in step - set same for both dimensions */ g->zoom.step_x = g->zoom.step_y = 1.1; g->zoom.flags = 0; g->cross.draw = g->cross.erase_needed = 0; g->grab.grabbed = 0; } static void graph_init_sequence(struct graph *g) { debug(DBS_FENTRY) puts("graph_init_sequence()"); graph_initialize(g); g->zoom.initial.x = g->zoom.x; g->zoom.initial.y = g->zoom.y; graph_element_lists_make(g); g->x_axis->s.width = g->wp.width; g->x_axis->p.width = g->x_axis->s.width + RMARGIN_WIDTH; g->x_axis->p.y = TITLEBAR_HEIGHT + g->wp.height; g->x_axis->s.height = g->x_axis->p.height = HAXIS_INIT_HEIGHT; g->y_axis->s.height = g->wp.height; g->y_axis->p.height = g->wp.height + TITLEBAR_HEIGHT; graph_pixmaps_create(g); axis_pixmaps_create(g->y_axis); axis_pixmaps_create(g->x_axis); graph_title_pixmap_create(g); graph_title_pixmap_draw(g); graph_title_pixmap_display(g); graph_display(g); axis_display(g->y_axis); axis_display(g->x_axis); } static void graph_initialize(struct graph *g) { debug(DBS_FENTRY) puts("graph_initialize()"); graph_get_bounds(g); /* Want to start with absolute times, rather than being relative to 0 */ g->x_axis->min = g->bounds.x0; g->y_axis->min = 0; graph_read_config(g); } static void graph_destroy(struct graph *g) { debug(DBS_FENTRY) puts("graph_destroy()"); axis_destroy(g->x_axis); axis_destroy(g->y_axis); /* window_destroy(g->drawing_area); */ window_destroy(g->toplevel); /* window_destroy(g->text); */ #if GTK_CHECK_VERSION(2,22,0) if (g->title_surface){ cairo_surface_destroy(g->title_surface); } if (g->surface[0]){ cairo_surface_destroy(g->surface[0]); } if (g->surface[1]){ cairo_surface_destroy(g->surface[1]); } #else g_object_unref(g->pixmap[0]); g_object_unref(g->pixmap[1]); #endif /* GTK_CHECK_VERSION(2,22,0) */ g_free(g->x_axis); g_free(g->y_axis); graph_segment_list_free(g); graph_element_lists_free(g); g_free(g); } typedef struct rlc_scan_t { struct segment *current; int direction; struct graph *g; struct segment *last; } rlc_scan_t; static int tapall_rlc_lte_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip) { rlc_scan_t *ts=(rlc_scan_t *)pct; rlc_lte_tap_info *rlchdr=(rlc_lte_tap_info*)vip; /* See if this one matches current channel */ if (compare_headers(ts->current->ueid, ts->current->channelType, ts->current->channelId, ts->current->rlcMode, ts->current->direction, rlchdr->ueid, rlchdr->channelType, rlchdr->channelId, rlchdr->rlcMode, rlchdr->direction, rlchdr->isControlPDU)) { struct segment *segment = g_malloc(sizeof(struct segment)); /* It matches. Add to end of segment list */ segment->next = NULL; segment->num = pinfo->fd->num; segment->rel_secs = (guint32) pinfo->fd->rel_ts.secs; segment->rel_usecs = pinfo->fd->rel_ts.nsecs/1000; segment->abs_secs = (guint32) pinfo->fd->abs_ts.secs; segment->abs_usecs = pinfo->fd->abs_ts.nsecs/1000; segment->ueid = rlchdr->ueid; segment->channelType = rlchdr->channelType; segment->channelId = rlchdr->channelId; segment->direction = rlchdr->direction; segment->isControlPDU = rlchdr->isControlPDU; if (!rlchdr->isControlPDU) { segment->SN = rlchdr->sequenceNumber; } else { gint n; segment->ACKNo = rlchdr->ACKNo; segment->noOfNACKs = rlchdr->noOfNACKs; for (n=0; n < rlchdr->noOfNACKs; n++) { segment->NACKs[n] = rlchdr->NACKs[n]; } } /* Add to list */ if (ts->g->segments) { /* Add to end of existing last element */ ts->last->next = segment; } else { /* Make this the first (only) segment */ ts->g->segments = segment; } /* This one is now the last one */ ts->last = segment; if (pinfo->fd->num == ts->current->num){ ts->g->current = segment; } } return 0; } /* Here we collect all the external data we will ever need */ static void graph_segment_list_get(struct graph *g) { struct segment current; GString *error_string; rlc_scan_t ts; debug(DBS_FENTRY) puts("graph_segment_list_get()"); select_rlc_lte_session(&cfile, ¤t); /* rescan all the packets and pick up all frames for this channel. * we only filter for LTE RLC here for speed and do the actual compare * in the tap listener */ ts.current = ¤t; ts.g = g; ts.last = NULL; error_string = register_tap_listener("rlc-lte", &ts, "rlc-lte", 0, NULL, tapall_rlc_lte_packet, NULL); if (error_string){ fprintf(stderr, "wireshark: Couldn't register rlc_lte_graph tap: %s\n", error_string->str); g_string_free(error_string, TRUE); exit(1); } cf_retap_packets(&cfile); remove_tap_listener(&ts); } typedef struct _th_t { int num_hdrs; #define MAX_SUPPORTED_CHANNELS 8 rlc_lte_tap_info *rlchdrs[MAX_SUPPORTED_CHANNELS]; } th_t; static int tap_lte_rlc_packet(void *pct, packet_info *pinfo _U_, epan_dissect_t *edt _U_, const void *vip) { int n; gboolean is_unique = TRUE; th_t *th = pct; rlc_lte_tap_info *header = (rlc_lte_tap_info*)vip; /* Check new header details against any/all stored ones */ for (n=0; n < th->num_hdrs; n++) { rlc_lte_tap_info *stored = th->rlchdrs[n]; if (compare_headers(stored->ueid, stored->channelType, stored->channelId, stored->rlcMode, stored->direction, header->ueid, header->channelType, header->channelId, header->rlcMode, header->direction, header->isControlPDU)) { is_unique = FALSE; break; } } /* Add address if unique and have space for it */ if (is_unique && (th->num_hdrs < MAX_SUPPORTED_CHANNELS)) { /* Copy the tap stuct in as next header */ th->rlchdrs[th->num_hdrs] = header; /* Store in direction of data though... */ if (th->rlchdrs[th->num_hdrs]->isControlPDU) { th->rlchdrs[th->num_hdrs]->direction = !th->rlchdrs[th->num_hdrs]->direction; } th->num_hdrs++; } return 0; } /* XXX should be enhanced so that if we have multiple RLC channels in the same MAC frame * then present the user with a dialog where the user can select WHICH RLC * channel to graph. */ static rlc_lte_tap_info *select_rlc_lte_session(capture_file *cf, struct segment *hdrs) { frame_data *fdata; epan_dissect_t edt; dfilter_t *sfcode; GString *error_string; th_t th = {0, {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}}; if (cf->state == FILE_CLOSED) { return NULL; } fdata = cf->current_frame; /* no real filter yet */ if (!dfilter_compile("rlc-lte", &sfcode)) { simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", dfilter_error_msg); return NULL; } /* dissect the current frame */ if (!cf_read_frame(cf, fdata)) { return NULL; /* error reading the frame */ } error_string = register_tap_listener("rlc-lte", &th, NULL, 0, NULL, tap_lte_rlc_packet, NULL); if (error_string){ fprintf(stderr, "wireshark: Couldn't register rlc_lte_graph tap: %s\n", error_string->str); g_string_free(error_string, TRUE); exit(1); } epan_dissect_init(&edt, TRUE, FALSE); epan_dissect_prime_dfilter(&edt, sfcode); tap_queue_init(&edt); epan_dissect_run(&edt, &cf->pseudo_header, cf->pd, fdata, NULL); tap_push_tapped_queue(&edt); epan_dissect_cleanup(&edt); remove_tap_listener(&th); if (th.num_hdrs==0){ /* This "shouldn't happen", as our menu items shouldn't * even be enabled if the selected packet isn't an RLC PDU * as rlc_lte_graph_selected_packet_enabled() is used * to determine whether to enable any of our menu items. */ simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Selected packet doesn't have an RLC PDU"); return NULL; } /* XXX fix this later, we should show a dialog allowing the user to select which session he wants here */ if (th.num_hdrs>1){ /* can only handle a single RLC channel yet */ simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "The selected packet has more than one LTE RLC channel " "in it."); return NULL; } /* For now, still always choose the first/only one */ hdrs->num = fdata->num; hdrs->rel_secs = (guint32) fdata->rel_ts.secs; hdrs->rel_usecs = fdata->rel_ts.nsecs/1000; hdrs->abs_secs = (guint32) fdata->abs_ts.secs; hdrs->abs_usecs = fdata->abs_ts.nsecs/1000; hdrs->ueid = th.rlchdrs[0]->ueid; hdrs->channelType = th.rlchdrs[0]->channelType; hdrs->channelId = th.rlchdrs[0]->channelId; hdrs->rlcMode = th.rlchdrs[0]->rlcMode; hdrs->direction = th.rlchdrs[0]->direction; return th.rlchdrs[0]; } static int compare_headers(guint16 ueid1, guint16 channelType1, guint16 channelId1, guint8 rlcMode1, guint8 direction1, guint16 ueid2, guint16 channelType2, guint16 channelId2, guint8 rlcMode2, guint8 direction2, gboolean frameIsControl) { /* Same direction, data - OK. */ if (!frameIsControl) { return (direction1 == direction2) && (ueid1 == ueid2) && (channelType1 == channelType2) && (channelId1 == channelId2) && (rlcMode1 == rlcMode2); } else { if (frameIsControl && (rlcMode1 == RLC_AM_MODE) && (rlcMode2 == RLC_AM_MODE)) { return ((direction1 != direction2) && (ueid1 == ueid2) && (channelType1 == channelType2) && (channelId1 == channelId2)); } else { return FALSE; } } } /* Free all segments in the graph */ static void graph_segment_list_free(struct graph *g) { struct segment *segment; while (g->segments) { segment = g->segments->next; g_free(g->segments); g->segments = segment; } g->segments = NULL; } static void graph_element_lists_initialize(struct graph *g) { g->elists = (struct element_list *)g_malloc0(sizeof(struct element_list)); g->elists->elements = NULL; g->elists->next = NULL; } static void graph_element_lists_make(struct graph *g) { debug(DBS_FENTRY) puts("graph_element_lists_make()"); rlc_lte_make_elmtlist(g); } static void graph_element_lists_free(struct graph *g) { struct element_list *list, *next_list; for (list=g->elists; list; list=next_list) { g_free(list->elements); next_list = list->next; g_free(list); } g->elists = NULL; /* just to make debugging easier */ } static void graph_title_pixmap_create(struct graph *g) { #if GTK_CHECK_VERSION(2,22,0) if (g->title_surface){ cairo_surface_destroy(g->title_surface); g->title_surface = NULL; } g->title_surface = gdk_window_create_similar_surface(gtk_widget_get_window(g->drawing_area), CAIRO_CONTENT_COLOR, g->x_axis->p.width, g->wp.y); #else if (g->title_pixmap) g_object_unref(g->title_pixmap); g->title_pixmap = gdk_pixmap_new(gtk_widget_get_window(g->drawing_area), g->x_axis->p.width, g->wp.y, -1); #endif } static void graph_title_pixmap_draw(struct graph *g) { gint w, h; PangoLayout *layout; cairo_t *cr; #if GTK_CHECK_VERSION(2,22,0) cr = cairo_create(g->title_surface); #else cr = gdk_cairo_create(g->title_pixmap); #endif cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, 0, g->x_axis->p.width, g->wp.y); cairo_fill(cr); layout = gtk_widget_create_pango_layout(g->drawing_area, ""); pango_layout_get_pixel_size(layout, &w, &h); cairo_move_to(cr, g->wp.width/2 - w/2, 20); pango_cairo_show_layout(cr, layout); g_object_unref(G_OBJECT(layout)); cairo_destroy(cr); } static void graph_title_pixmap_display(struct graph *g) { cairo_t *cr; cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area)); #if GTK_CHECK_VERSION(2,22,0) cairo_set_source_surface(cr, g->title_surface, g->wp.x, 0); #else gdk_cairo_set_source_pixmap(cr, g->title_pixmap, g->wp.x, 0); #endif cairo_rectangle(cr, g->wp.x, 0, g->x_axis->p.width, g->wp.y); cairo_fill(cr); cairo_destroy(cr); } static void graph_pixmaps_create(struct graph *g) { debug(DBS_FENTRY) puts("graph_pixmaps_create()"); #if GTK_CHECK_VERSION(2,22,0) if (g->surface[0]){ cairo_surface_destroy(g->surface[0]); g->surface[0] = NULL; } if (g->surface[1]){ cairo_surface_destroy(g->surface[1]); g->surface[1] = NULL; } g->surface[0] = gdk_window_create_similar_surface(gtk_widget_get_window(g->drawing_area), CAIRO_CONTENT_COLOR, g->wp.width, g->wp.height); g->surface[1] = gdk_window_create_similar_surface(gtk_widget_get_window(g->drawing_area), CAIRO_CONTENT_COLOR, g->wp.width, g->wp.height); g->displayed = 0; #else if (g->pixmap[0]) g_object_unref(g->pixmap[0]); if (g->pixmap[1]) g_object_unref(g->pixmap[1]); g->pixmap[0] = gdk_pixmap_new(gtk_widget_get_window(g->drawing_area), g->wp.width, g->wp.height, -1); g->pixmap[1] = gdk_pixmap_new(gtk_widget_get_window(g->drawing_area), g->wp.width, g->wp.height, -1); g->displayed = 0; #endif /* GTK_CHECK_VERSION(2,22,0) */ } static void graph_display(struct graph *g) { set_busy_cursor(gtk_widget_get_window(g->drawing_area)); graph_pixmap_draw(g); unset_busy_cursor(gtk_widget_get_window(g->drawing_area)); graph_pixmaps_switch(g); graph_pixmap_display(g); } static void graph_pixmap_display(struct graph *g) { cairo_t *cr; cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area)); #if GTK_CHECK_VERSION(2,22,0) cairo_set_source_surface(cr, g->surface[g->displayed], g->wp.x, g->wp.y); #else gdk_cairo_set_source_pixmap(cr, g->pixmap[g->displayed], g->wp.x, g->wp.y); #endif /* GTK_CHECK_VERSION(2,22,0) */ cairo_rectangle(cr, g->wp.x, g->wp.y, g->wp.width, g->wp.height); cairo_fill(cr); cairo_destroy(cr); } static void graph_pixmaps_switch(struct graph *g) { g->displayed = 1 ^ g->displayed; } static void graph_pixmap_draw(struct graph *g) { struct element_list *list; struct element *e; int not_disp; cairo_t *cr; cairo_t *cr_lines; GdkColor *current_color = NULL; GdkColor *color_to_set = NULL; debug(DBS_FENTRY) puts("graph_display()"); not_disp = 1 ^ g->displayed; #if GTK_CHECK_VERSION(2,22,0) cr = cairo_create(g->surface[not_disp]); #else cr = gdk_cairo_create(g->pixmap[not_disp]); #endif /* GTK_CHECK_VERSION(2,22,0) */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, 0, g->wp.width, g->wp.height); cairo_fill(cr); cairo_destroy(cr); /* Create one cairo_t for use with all of the lines, rather than continually creating and destroying one for each line */ #if GTK_CHECK_VERSION(2,22,0) cr_lines = cairo_create(g->surface[not_disp]); #else cr_lines = gdk_cairo_create(g->pixmap[not_disp]); #endif /* Line width is always 1 pixel */ cairo_set_line_width(cr_lines, 1.0); /* Draw all elements */ for (list=g->elists; list; list=list->next) { for (e=list->elements; e->type != ELMT_NONE; e++) { switch (e->type) { case ELMT_LINE: /* Work out if we need to change colour */ if (current_color == e->elment_color_p) { /* No change needed */ color_to_set = NULL; } else { /* Changing colour */ current_color = color_to_set = e->elment_color_p; } /* Draw the line */ draw_element_line(g, e, cr_lines, color_to_set); break; default: /* No other element types supported at the moment */ break; } } } /* Make sure any remaining lines get drawn */ cairo_stroke(cr_lines); cairo_destroy(cr_lines); } static void draw_element_line(struct graph *g, struct element *e, cairo_t *cr, GdkColor *new_color) { int xx1, xx2, yy1, yy2; debug(DBS_GRAPH_DRAWING) printf("\nline element: (%.2f,%.2f)->(%.2f,%.2f), seg %d ...\n", e->p.line.dim.x1, e->p.line.dim.y1, e->p.line.dim.x2, e->p.line.dim.y2, e->parent->num); /* Set our new colour (if changed) */ if (new_color != NULL) { /* First draw any previous lines with old colour */ cairo_stroke(cr); gdk_cairo_set_source_color(cr, new_color); } /* Map point into graph area, and round to nearest int */ xx1 = (int)rint(e->p.line.dim.x1 + g->geom.x - g->wp.x); xx2 = (int)rint(e->p.line.dim.x2 + g->geom.x - g->wp.x); yy1 = (int)rint((g->geom.height-1-e->p.line.dim.y1) + g->geom.y-g->wp.y); yy2 = (int)rint((g->geom.height-1-e->p.line.dim.y2) + g->geom.y-g->wp.y); /* If line completely out of the area, we won't show it */ if ((xx1<0 && xx2<0) || (xx1>=g->wp.width && xx2>=g->wp.width) || (yy1<0 && yy2<0) || (yy1>=g->wp.height && yy2>=g->wp.height)) { debug(DBS_GRAPH_DRAWING) printf(" refusing: (%d,%d)->(%d,%d)\n", xx1, yy1, xx2, yy2); return; } /* If one end of the line is out of bounds, don't worry. Cairo will clip the line to the outside of g->wp at the correct angle! */ debug(DBS_GRAPH_DRAWING) printf("line: (%d,%d)->(%d,%d)\n", xx1, yy1, xx2, yy2); /* Draw from first position to second */ cairo_move_to(cr, xx1+0.5, yy1+0.5); cairo_line_to(cr, xx2+0.5, yy2+0.5); } static void axis_pixmaps_create(struct axis *axis) { debug(DBS_FENTRY) puts("axis_pixmaps_create()"); #if GTK_CHECK_VERSION(2,22,0) if (axis->surface[0]){ cairo_surface_destroy(axis->surface[0]); axis->surface[0] = NULL; } if (axis->surface[1]){ cairo_surface_destroy(axis->surface[1]); axis->surface[1] = NULL; } axis->surface[0] = gdk_window_create_similar_surface(gtk_widget_get_window(axis->drawing_area), CAIRO_CONTENT_COLOR, axis->p.width, axis->p.height); axis->surface[1] = gdk_window_create_similar_surface(gtk_widget_get_window(axis->drawing_area), CAIRO_CONTENT_COLOR, axis->p.width, axis->p.height); axis->displayed = 0; #else if (axis->pixmap[0]) g_object_unref(axis->pixmap[0]); if (axis->pixmap[1]) g_object_unref(axis->pixmap[1]); axis->pixmap[0] = gdk_pixmap_new(gtk_widget_get_window(axis->drawing_area), axis->p.width, axis->p.height, -1); axis->pixmap[1] = gdk_pixmap_new(gtk_widget_get_window(axis->drawing_area), axis->p.width, axis->p.height, -1); axis->displayed = 0; #endif } static void axis_destroy(struct axis *axis) { #if GTK_CHECK_VERSION(2,22,0) if (axis->surface[0]){ cairo_surface_destroy(axis->surface[0]); axis->surface[0] = NULL; } if (axis->surface[1]){ cairo_surface_destroy(axis->surface[1]); axis->surface[1] = NULL; } #else g_object_unref(axis->pixmap[0]); g_object_unref(axis->pixmap[1]); #endif g_free((gpointer)(axis->label) ); } static void axis_display(struct axis *axis) { if (axis->flags & AXIS_ORIENTATION) h_axis_pixmap_draw(axis); else v_axis_pixmap_draw(axis); axis_pixmaps_switch(axis); axis_pixmap_display(axis); } /* These show sequence numbers. Avoid subdividing whole numbers. */ static void v_axis_pixmap_draw(struct axis *axis) { struct graph *g = axis->g; int i; double major_tick; int not_disp, offset, imin, imax; double bottom, top, fl, corr; PangoLayout *layout; cairo_t *cr; debug(DBS_FENTRY) puts("v_axis_pixmap_draw()"); /* Work out extent of axis */ bottom = (g->geom.height - (g->wp.height + g->wp.y + (-g->geom.y))) / (double )g->geom.height * g->bounds.height; bottom += axis->min; top = (g->geom.height - (g->wp.y + (-g->geom.y))) / (double )g->geom.height * g->bounds.height; top += axis->min; axis_compute_ticks(axis, bottom, top, AXIS_VERTICAL); not_disp = 1 ^ axis->displayed; #if GTK_CHECK_VERSION(2,22,0) cr = cairo_create(axis->surface[not_disp]); #else cr = gdk_cairo_create(axis->pixmap[not_disp]); #endif cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, 0, axis->p.width, axis->p.height); cairo_fill(cr); /* axis */ cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, axis->p.width - 1.5, (axis->p.height-axis->s.height)/2.0); cairo_line_to(cr, axis->s.width - 1.5, axis->p.height); offset = g->wp.y + (-g->geom.y); fl = floor(axis->min / axis->major) * axis->major; corr = rint((axis->min - fl) * g->zoom.y); /* major ticks */ major_tick = axis->major * g->zoom.y; imin = (int) ((g->geom.height - offset + corr - g->wp.height) / major_tick + 1); imax = (int) ((g->geom.height - offset + corr) / major_tick); for (i=imin; i <= imax; i++) { gint w, h; char desc[32]; int y = (int) (g->geom.height-1 - (int )rint(i * major_tick) - offset + corr + axis->s.y); debug(DBS_AXES_DRAWING) printf("%f @ %d\n", i*axis->major + fl, y); if (y < 0 || y > axis->p.height) continue; cairo_move_to(cr, axis->p.width - 15, y+0.5); cairo_line_to(cr, axis->s.width - 1, y+0.5); /* Won't be showing any decimal places here... */ g_snprintf(desc, sizeof(desc), "%u", (unsigned int)(i*axis->major + fl)); layout = gtk_widget_create_pango_layout(g->drawing_area, desc); pango_layout_get_pixel_size(layout, &w, &h); cairo_move_to(cr, axis->s.width-14-4-w, y - h/2); pango_cairo_show_layout(cr, layout); g_object_unref(G_OBJECT(layout)); } /* minor ticks */ if (axis->minor) { double minor_tick = axis->minor * g->zoom.y; imin = (int) ((g->geom.height - offset + corr - g->wp.height)/minor_tick + 1); imax = (int) ((g->geom.height - offset + corr) / minor_tick); for (i=imin; i <= imax; i++) { int y = (int) (g->geom.height-1 - (int )rint(i*minor_tick) - offset + corr + axis->s.y); if (y > 0 && y < axis->p.height) cairo_set_line_width(cr, 1.0); cairo_move_to(cr, axis->s.width - 8, y+0.5); cairo_line_to(cr, axis->s.width - 1, y+0.5); } } for (i=0; axis->label[i]; i++) { gint w, h; layout = gtk_widget_create_pango_layout(g->drawing_area, axis->label[i]); pango_layout_get_pixel_size(layout, &w, &h); cairo_move_to(cr, (axis->p.width - w)/2, TITLEBAR_HEIGHT-10 - i*(h+3) - h); pango_cairo_show_layout(cr, layout); g_object_unref(G_OBJECT(layout)); } cairo_stroke(cr); cairo_destroy(cr); } /* TODO: natural time units are subframes (ms), so might be good to always show 3 decimal places? */ static void h_axis_pixmap_draw(struct axis *axis) { struct graph *g = axis->g; int i; double major_tick, minor_tick; int not_disp, rdigits, offset, imin, imax; double left, right, j, fl, corr; PangoLayout *layout; cairo_t *cr; debug(DBS_FENTRY) puts("h_axis_pixmap_draw()"); left = (g->wp.x-g->geom.x) / (double)g->geom.width * g->bounds.width; left += axis->min; right = (g->wp.x-g->geom.x+g->wp.width) / (double)g->geom.width * g->bounds.width; right += axis->min; axis_compute_ticks(axis, left, right, AXIS_HORIZONTAL); /* Work out how many decimal places should be shown */ j = axis->major - floor(axis->major); for (rdigits=0; rdigits<=6; rdigits++) { j *= 10; if (j<=0.000001) break; j = j - floor(j); } not_disp = 1 ^ axis->displayed; #if GTK_CHECK_VERSION(2,22,0) cr = cairo_create(axis->surface[not_disp]); #else cr = gdk_cairo_create(axis->pixmap[not_disp]); #endif cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, 0, axis->p.width, axis->p.height); cairo_fill(cr); /* axis */ cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, 0, 0.5); cairo_line_to(cr, axis->s.width + (axis->p.width-axis->s.width)/2.0, 0.5); offset = g->wp.x - g->geom.x; fl = floor(axis->min / axis->major) * axis->major; corr = rint((axis->min - fl) * g->zoom.x); /* major ticks */ major_tick = axis->major*g->zoom.x; imin = (int) ((offset + corr) / major_tick + 1); imax = (int) ((offset + corr + axis->s.width) / major_tick); for (i=imin; i <= imax; i++) { char desc[32]; int w, h; int x = (int) (rint(i * major_tick) - offset - corr); /* printf("%f @ %d\n", i*axis->major + fl, x); */ if (x < 0 || x > axis->s.width) continue; cairo_move_to(cr, x+0.5, 0); cairo_line_to(cr, x+0.5, 15); g_snprintf(desc, sizeof(desc), "%.*f", rdigits, i*axis->major + fl); layout = gtk_widget_create_pango_layout(g->drawing_area, desc); pango_layout_get_pixel_size(layout, &w, &h); cairo_move_to(cr, x - w/2, 15+4); pango_cairo_show_layout(cr, layout); g_object_unref(G_OBJECT(layout)); } if (axis->minor > 0) { /* minor ticks */ minor_tick = axis->minor*g->zoom.x; imin = (int) ((offset + corr) / minor_tick + 1); imax = (int) ((offset + corr + g->wp.width) / minor_tick); for (i=imin; i <= imax; i++) { int x = (int) (rint(i * minor_tick) - offset - corr); if (x > 0 && x < axis->s.width){ cairo_move_to(cr, x+0.5, 0); cairo_line_to(cr, x+0.5, 8); } } } for (i=0; axis->label[i]; i++) { gint w, h; layout = gtk_widget_create_pango_layout(g->drawing_area, axis->label[i]); pango_layout_get_pixel_size(layout, &w, &h); cairo_move_to(cr, axis->s.width - w - 50, 15+h+15 + i*(h+3)); pango_cairo_show_layout(cr, layout); g_object_unref(G_OBJECT(layout)); } cairo_stroke(cr); cairo_destroy(cr); } static void axis_pixmaps_switch(struct axis *axis) { axis->displayed = 1 ^ axis->displayed; } static void axis_pixmap_display(struct axis *axis) { cairo_t *cr; cr = gdk_cairo_create(gtk_widget_get_window(axis->drawing_area)); #if GTK_CHECK_VERSION(2,22,0) cairo_set_source_surface(cr, axis->surface[axis->displayed], axis->p.x, axis->p.y); #else gdk_cairo_set_source_pixmap(cr, axis->pixmap[axis->displayed], axis->p.x, axis->p.y); #endif cairo_rectangle(cr, axis->p.x, axis->p.y, axis->p.width, axis->p.height); cairo_fill(cr); cairo_destroy(cr); } static void axis_compute_ticks(struct axis *axis, double x0, double xmax, int dir) { int i, j, ii, jj, ms; double zoom, x, steps[3]={ 0.1, 0.5 }; int dim, check_needed, diminished; double majthresh[2]={2.0, 3.0}; debug((DBS_FENTRY | DBS_AXES_TICKS)) puts("axis_compute_ticks()"); debug(DBS_AXES_TICKS) printf("x0=%f xmax=%f dir=%s\n", x0,xmax, dir? "VERTICAL" : "HORIZONTAL"); zoom = axis_zoom_get(axis, dir); x = xmax-x0; for (i=-9; i<=12; i++) { if (x / pow(10, i) < 1) break; } --i; ms = (int )(x / pow(10, i)); if (ms > 5) { j = 0; ++i; } else if (ms > 2) j = 1; else j = 0; axis->major = steps[j] * pow(10, i); if (dir == AXIS_VERTICAL) { /* But don't divide further than whole sequence numbers */ axis->major = MAX(axis->major, 1.0); } debug(DBS_AXES_TICKS) printf("zoom=%.1f, x=%f -> i=%d -> ms=%d -> j=%d ->" " axis->major=%f\n", zoom, x, i, ms, j, axis->major); /* Compute minor ticks */ jj = j; ii = i; axis_ticks_down(&ii, &jj); if ((dir == AXIS_VERTICAL) && (axis->major <= 1)) { /* Ddon't subdivide whole sequence numbers */ axis->minor = 0; } else { axis->minor = steps[jj] * pow(10, ii); /* We don't want minors if they would be less than 10 pixels apart */ if (axis->minor*zoom < 10) { debug(DBS_AXES_TICKS) printf("refusing axis->minor of %f: " "axis->minor*zoom == %f\n", axis->minor, axis->minor*zoom); axis->minor = 0; } } check_needed = TRUE; diminished = FALSE; while (check_needed) { check_needed = FALSE; dim = get_label_dim(axis, dir, xmax); debug(DBS_AXES_TICKS) printf("axis->major==%.1f, axis->minor==%.1f =>" " axis->major*zoom/dim==%f, axis->minor*zoom/dim==%f\n", axis->major, axis->minor, axis->major*zoom/dim, axis->minor*zoom/dim); /* corrections: if majors are less than majthresh[dir] times label * dimension apart, we need to use bigger ones */ if (axis->major*zoom / dim < majthresh[dir]) { axis_ticks_up(&ii, &jj); axis->minor = axis->major; axis_ticks_up(&i, &j); axis->major = steps[j] * pow(10, i); check_needed = TRUE; debug(DBS_AXES_TICKS) printf("axis->major enlarged to %.1f\n", axis->major); } /* if minor ticks are bigger than majthresh[dir] times label dimension, * we could promote them to majors as well */ if (axis->minor*zoom / dim > majthresh[dir] && !diminished) { axis_ticks_down(&i, &j); axis->major = axis->minor; axis_ticks_down(&ii, &jj); axis->minor = steps[jj] * pow(10, ii); check_needed = TRUE; diminished = TRUE; debug(DBS_AXES_TICKS) printf("axis->minor diminished to %.1f\n", axis->minor); if (axis->minor*zoom < 10) { debug(DBS_AXES_TICKS) printf("refusing axis->minor of %f: " "axis->minor*zoom == %f\n", axis->minor, axis->minor*zoom); axis->minor = 0; } } } debug(DBS_AXES_TICKS) printf("corrected: axis->major == %.1f -> " "axis->minor == %.1f\n", axis->major, axis->minor); } static void axis_ticks_up(int *i, int *j) { (*j)++; if (*j>1) { (*i)++; *j=0; } } static void axis_ticks_down(int *i, int *j) { (*j)--; if (*j<0) { (*i)--; *j=1; } } static int get_label_dim(struct axis *axis, int dir, double label) { double y; char str[32]; int rdigits, dim; PangoLayout *layout; /* First, let's compute how many digits to the right of radix * we need to print */ y = axis->major - floor(axis->major); for (rdigits=0; rdigits<=6; rdigits++) { y *= 10; if (y<=0.000001) break; y = y - floor(y); } g_snprintf(str, sizeof(str), "%.*f", rdigits, label); switch (dir) { case AXIS_HORIZONTAL: layout = gtk_widget_create_pango_layout(axis->g->drawing_area, str); pango_layout_get_pixel_size(layout, &dim, NULL); g_object_unref(G_OBJECT(layout)); break; case AXIS_VERTICAL: layout = gtk_widget_create_pango_layout(axis->g->drawing_area, str); pango_layout_get_pixel_size(layout, NULL, &dim); g_object_unref(G_OBJECT(layout)); break; default: puts("initialize axis: an axis must be either horizontal or vertical"); return -1; } return dim; } static double axis_zoom_get(struct axis *axis, int dir) { switch (dir) { case AXIS_HORIZONTAL: return axis->g->zoom.x; case AXIS_VERTICAL: return axis->g->zoom.y; default: return -1; } } static void graph_select_segment(struct graph *g, int x, int y) { struct element_list *list; struct element *e; guint num = 0; debug(DBS_FENTRY) puts("graph_select_segment()"); x -= g->geom.x; y = g->geom.height-1 - (y - g->geom.y); set_busy_cursor(gtk_widget_get_window(g->drawing_area)); for (list=g->elists; list; list=list->next) { for (e=list->elements; e->type != ELMT_NONE; e++) { switch (e->type) { case ELMT_LINE: if (line_detect_collision(e, x, y)) { num = e->parent->num; } break; default: break; } } } if (num) { cf_goto_frame(&cfile, num); } } static int line_detect_collision(struct element *e, int x, int y) { int xx1, yy1, xx2, yy2; /* Get sorted x, y co-ordinates for line */ if (e->p.line.dim.x1 < e->p.line.dim.x2) { xx1 = (int)rint(e->p.line.dim.x1); xx2 = (int)rint(e->p.line.dim.x2); } else { xx1 = (int)rint(e->p.line.dim.x2); xx2 = (int)rint(e->p.line.dim.x1); } if (e->p.line.dim.y1 < e->p.line.dim.y2) { yy1 = (int)rint(e->p.line.dim.y1); yy2 = (int)rint(e->p.line.dim.y2); } else { yy1 = (int)rint(e->p.line.dim.y2); yy2 = (int)rint(e->p.line.dim.y1); } /* printf("line: (%d,%d)->(%d,%d), clicked: (%d,%d)\n", xx1, yy1, xx2, yy2, x, y); */ /* N.B. won't match with diagonal lines... */ if ((xx1==x && xx2==x && yy1<=y && y<=yy2)| /* lies along vertical line */ (yy1==y && yy2==y && xx1<=x && x<=xx2)) { /* lies along horizontal line */ return TRUE; } else { return FALSE; } } static gboolean configure_event(GtkWidget *widget _U_, GdkEventConfigure *event, gpointer user_data) { struct graph *g = user_data; struct zoom new_zoom; int cur_g_width, cur_g_height; int cur_wp_width, cur_wp_height; debug(DBS_FENTRY) puts("configure_event()"); cur_wp_width = g->wp.width; cur_wp_height = g->wp.height; g->wp.width = event->width - g->y_axis->p.width - RMARGIN_WIDTH; g->wp.height = event->height - g->x_axis->p.height - g->wp.y; g->x_axis->s.width = g->wp.width; g->x_axis->p.width = g->wp.width + RMARGIN_WIDTH; g->y_axis->p.height = g->wp.height + g->wp.y; g->y_axis->s.height = g->wp.height; g->x_axis->p.y = g->y_axis->p.height; new_zoom.x = (double)g->wp.width / cur_wp_width; new_zoom.y = (double)g->wp.height / cur_wp_height; cur_g_width = g->geom.width; cur_g_height = g->geom.height; g->geom.width = (int)rint(g->geom.width * new_zoom.x); g->geom.height = (int)rint(g->geom.height * new_zoom.y); g->zoom.x = (double)(g->geom.width - 1) / g->bounds.width; g->zoom.y = (double)(g->geom.height -1) / g->bounds.height; g->geom.x = (int)(g->wp.x - (double)g->geom.width/cur_g_width * (g->wp.x - g->geom.x)); g->geom.y = (int)(g->wp.y - (double)g->geom.height/cur_g_height * (g->wp.y - g->geom.y)); #if 0 printf("configure: graph: (%d,%d), (%d,%d); viewport: (%d,%d), (%d,%d); " "zooms: (%f,%f)\n", g->geom.x, g->geom.y, g->geom.width, g->geom.height, g->wp.x, g->wp.y, g->wp.width, g->wp.height, g->zoom.x, g->zoom.y); #endif graph_element_lists_make(g); graph_pixmaps_create(g); graph_title_pixmap_create(g); axis_pixmaps_create(g->y_axis); axis_pixmaps_create(g->x_axis); /* we don't do actual drawing here; we leave it to expose handler */ graph_pixmap_draw(g); graph_pixmaps_switch(g); graph_title_pixmap_draw(g); h_axis_pixmap_draw(g->x_axis); axis_pixmaps_switch(g->x_axis); v_axis_pixmap_draw(g->y_axis); axis_pixmaps_switch(g->y_axis); return TRUE; } #if GTK_CHECK_VERSION(3,0,0) static gboolean draw_event(GtkWidget *widget _U_, cairo_t *cr, gpointer user_data) { struct graph *g = user_data; debug(DBS_FENTRY) puts("draw_event()"); /* lower left corner */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, g->wp.y + g->wp.height, g->y_axis->p.width, g->x_axis->p.height); cairo_fill(cr); /* right margin */ cairo_rectangle(cr, g->wp.x + g->wp.width, g->wp.y, RMARGIN_WIDTH, g->wp.height); cairo_fill(cr); /* Should these routines be copied here, or be given the cairo_t ?? */ graph_pixmap_display(g); graph_title_pixmap_display(g); axis_pixmap_display(g->x_axis); axis_pixmap_display(g->y_axis); return TRUE; } #else static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { struct graph *g = user_data; cairo_t *cr; debug(DBS_FENTRY) puts("expose_event()"); if (event->count) return TRUE; /* lower left corner */ cr = gdk_cairo_create(gtk_widget_get_window(widget)); cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, g->wp.y + g->wp.height, g->y_axis->p.width, g->x_axis->p.height); cairo_fill(cr); cairo_destroy(cr); cr = NULL; /* right margin */ cr = gdk_cairo_create(gtk_widget_get_window(widget)); cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, g->wp.x + g->wp.width, g->wp.y, RMARGIN_WIDTH, g->wp.height); cairo_fill(cr); cairo_destroy(cr); cr = NULL; graph_pixmap_display(g); graph_title_pixmap_display(g); axis_pixmap_display(g->x_axis); axis_pixmap_display(g->y_axis); return TRUE; } #endif /* Zoom because of keyboard or mouse press */ static void do_zoom_common(struct graph *g, GdkEventButton *event, gboolean lock_vertical, gboolean lock_horizontal) { int cur_width = g->geom.width, cur_height = g->geom.height; struct { double x, y; } factor; int pointer_x, pointer_y; /* Get mouse position */ if (event == NULL) { /* Keyboard - query it */ get_mouse_position(g->drawing_area, &pointer_x, &pointer_y, NULL); } else { /* Mouse - just read it from event */ pointer_x = (int)event->x; pointer_y = (int)event->y; } if (g->zoom.flags & ZOOM_OUT) { /* If can't zoom out anymore so don't waste time redrawing the whole graph! */ if ((g->geom.height <= g->wp.height) && (g->geom.width <= g->wp.width)) { return; } /* Zoom out */ if (lock_horizontal) { factor.x = 1.0; } else { factor.x = 1 / g->zoom.step_x; } if (lock_vertical) { factor.y = 1.0; } else { factor.y = 1 / g->zoom.step_y; } } else { /* Zoom in */ if ((lock_horizontal) || (g->geom.width >= (g->bounds.width * MAX_PIXELS_PER_SECOND))) { factor.x = 1.0; } else { factor.x = g->zoom.step_x; } /* Don't zoom in too far vertically */ if (lock_vertical || (g->geom.height >= (g->bounds.height * MAX_PIXELS_PER_SN))) { factor.y = 1.0; } else { factor.y = g->zoom.step_y; } } /* Multiply by x and y factors */ g->geom.width = (int )rint(g->geom.width * factor.x); g->geom.height = (int )rint(g->geom.height * factor.y); /* Clip to space if necessary */ if (g->geom.width < g->wp.width) g->geom.width = g->wp.width; if (g->geom.height < g->wp.height) g->geom.height = g->wp.height; /* Work out new zoom */ g->zoom.x = (g->geom.width - 1) / g->bounds.width; g->zoom.y = (g->geom.height- 1) / g->bounds.height; /* Move origin to keep mouse position at centre of view */ g->geom.x -= (int )rint((g->geom.width - cur_width) * ((pointer_x - g->geom.x)/(double)cur_width)); g->geom.y -= (int )rint((g->geom.height - cur_height) * ((pointer_y - g->geom.y)/(double)cur_height)); /* Make sure we haven't moved outside the whole graph */ if (g->geom.x > g->wp.x) g->geom.x = g->wp.x; if (g->geom.y > g->wp.y) g->geom.y = g->wp.y; if (g->wp.x + g->wp.width > g->geom.x + g->geom.width) g->geom.x = g->wp.width + g->wp.x - g->geom.width; if (g->wp.y + g->wp.height > g->geom.y + g->geom.height) g->geom.y = g->wp.height + g->wp.y - g->geom.height; #if 0 printf("%s press: graph: (%d,%d), (%d,%d); viewport: (%d,%d), " "(%d,%d); zooms: (%f,%f)\n", (event != NULL) ? "mouse" : "key", g->geom.x, g->geom.y, g->geom.width, g->geom.height, g->wp.x, g->wp.y, g->wp.width, g->wp.height, g->zoom.x, g->zoom.y); #endif graph_element_lists_make(g); graph_display(g); axis_display(g->y_axis); axis_display(g->x_axis); if (g->cross.draw) { g->cross.erase_needed = FALSE; cross_draw(g, pointer_x, pointer_y); } } static void do_zoom_keyboard(struct graph *g, gboolean lock_vertical, gboolean lock_horizontal) { do_zoom_common(g, NULL, lock_vertical, lock_horizontal); } static void do_zoom_mouse(struct graph *g, GdkEventButton *event) { do_zoom_common(g, event, event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK); } static void do_zoom_in_keyboard(struct graph *g, gboolean lock_vertical, gboolean lock_horizontal) { g->zoom.flags &= ~ZOOM_OUT; do_zoom_keyboard(g, lock_vertical, lock_horizontal); } static void do_zoom_out_keyboard(struct graph *g, gboolean lock_vertical, gboolean lock_horizontal) { g->zoom.flags |= ZOOM_OUT; do_zoom_keyboard(g, lock_vertical, lock_horizontal); } static void do_key_motion(struct graph *g) { if (g->geom.x > g->wp.x) { g->geom.x = g->wp.x; } if (g->geom.y > g->wp.y) { g->geom.y = g->wp.y; } if (g->wp.x + g->wp.width > g->geom.x + g->geom.width) { g->geom.x = g->wp.width + g->wp.x - g->geom.width; } if (g->wp.y + g->wp.height > g->geom.y + g->geom.height) { g->geom.y = g->wp.height + g->wp.y - g->geom.height; } graph_display(g); axis_display(g->y_axis); axis_display(g->x_axis); if (g->cross.draw) { int pointer_x, pointer_y; get_mouse_position(g->drawing_area, &pointer_x, &pointer_y, NULL); g->cross.erase_needed = FALSE; cross_draw (g, pointer_x, pointer_y); } } static void do_key_motion_up(struct graph *g, int step) { g->geom.y += step; do_key_motion(g); } static void do_key_motion_down(struct graph *g, int step) { g->geom.y -= step; do_key_motion(g); } static void do_key_motion_left(struct graph *g, int step) { g->geom.x += step; do_key_motion(g); } static void do_key_motion_right(struct graph *g, int step) { g->geom.x -= step; do_key_motion(g); } static gboolean button_press_event(GtkWidget *widget _U_, GdkEventButton *event, gpointer user_data) { struct graph *g = user_data; debug(DBS_FENTRY) puts("button_press_event()"); if (event->button == MOUSE_BUTTON_RIGHT) { /* Turn on grab. N.B. using (maybe) approx mouse position from event... */ g->grab.x = (int )rint (event->x) - g->geom.x; g->grab.y = (int )rint (event->y) - g->geom.y; g->grab.grabbed = TRUE; } else if (event->button == MOUSE_BUTTON_MIDDLE) { do_zoom_mouse(g, event); } else if (event->button == MOUSE_BUTTON_LEFT) { graph_select_segment(g, (int)event->x, (int)event->y); } unset_busy_cursor(gtk_widget_get_window(g->drawing_area)); return TRUE; } static gboolean button_release_event(GtkWidget *widget _U_, GdkEventButton *event _U_, gpointer user_data) { struct graph *g = user_data; /* Turn off grab if right button released */ if (event->button == MOUSE_BUTTON_RIGHT) { g->grab.grabbed = FALSE; } return TRUE; } static gboolean motion_notify_event(GtkWidget *widget _U_, GdkEventMotion *event, gpointer user_data) { struct graph *g = user_data; int x, y; GdkModifierType state; /* debug(DBS_FENTRY) puts ("motion_notify_event()"); */ /* Make sure we have accurate mouse position */ if (event->is_hint) get_mouse_position(g->drawing_area, &x, &y, &state); else { x = (int) event->x; y = (int) event->y; state = event->state; } if (state & GDK_BUTTON3_MASK) { if (g->grab.grabbed) { /* Move view by difference between where we grabbed and where we are now */ g->geom.x = x-g->grab.x; g->geom.y = y-g->grab.y; /* Limit to outer bounds of graph */ if (g->geom.x > g->wp.x) g->geom.x = g->wp.x; if (g->geom.y > g->wp.y) g->geom.y = g->wp.y; if (g->wp.x + g->wp.width > g->geom.x + g->geom.width) g->geom.x = g->wp.width + g->wp.x - g->geom.width; if (g->wp.y + g->wp.height > g->geom.y + g->geom.height) g->geom.y = g->wp.height + g->wp.y - g->geom.height; /* Redraw everything */ g->cross.erase_needed = 0; graph_display(g); axis_display(g->y_axis); axis_display(g->x_axis); if (g->cross.draw) { cross_draw(g, x, y); } } } else { /* Update the cross if its being shown */ if (g->cross.erase_needed) cross_erase(g); if (g->cross.draw) { cross_draw(g, x, y); } } return TRUE; } static gboolean key_press_event(GtkWidget *widget _U_, GdkEventKey *event, gpointer user_data) { struct graph *g = user_data; int step; debug(DBS_FENTRY) puts("key_press_event()"); /* Holding down these keys can affect the step used for moving */ if ((event->state & GDK_CONTROL_MASK) && (event->state & GDK_SHIFT_MASK)) { step = 0; } else if (event->state & GDK_CONTROL_MASK) { step = 1; } else if (event->state & GDK_SHIFT_MASK) { step = 10; } else { step = 100; } switch (event->keyval) { case ' ': toggle_crosshairs(g); break; case 't': /* Toggle betwee showing the time starting at 0, or time in capture */ toggle_time_origin(g); break; case 'r': case GDK_Home: /* Go back to original view, all zoomed out */ restore_initial_graph_view(g); break; /* Zooming in */ case 'i': case 'I': do_zoom_in_keyboard(g, event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK); break; case '+': do_zoom_in_keyboard(g, FALSE, event->state & GDK_CONTROL_MASK); break; /* Zooming out */ case 'o': case 'O': do_zoom_out_keyboard(g, event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK); break; case '-': do_zoom_out_keyboard(g, FALSE, event->state & GDK_CONTROL_MASK); /* Direction keys */ case GDK_Left: do_key_motion_left(g, step); break; case GDK_Up: do_key_motion_up(g, step); break; case GDK_Right: do_key_motion_right(g, step); break; case GDK_Down: do_key_motion_down(g, step); break; case GDK_Page_Up: do_key_motion_up(g, 2000); break; case GDK_Page_Down: do_key_motion_down(g, 2000); break; /* Help */ case GDK_F1: callback_create_help(NULL, NULL); break; default: break; } return TRUE; } static void toggle_crosshairs(struct graph *g) { /* Toggle state */ g->cross.draw ^= 1; /* Draw or erase as needed */ if (g->cross.draw) { int x, y; get_mouse_position(g->drawing_area, &x, &y, NULL); cross_draw(g, x, y); } else if (g->cross.erase_needed) { cross_erase(g); } } static void cross_draw(struct graph *g, int x, int y) { /* Shouldn't draw twice onto the same position if haven't erased in the meantime! */ if (g->cross.erase_needed && (g->cross.x == x) && (g->cross.y == y)) { return; } /* Draw the cross */ if (x > g->wp.x && x < g->wp.x+g->wp.width && y > g->wp.y && y < g->wp.y+g->wp.height) { cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(g->drawing_area)); gdk_cairo_set_source_color(cr, &g->style.seq_color); cairo_set_line_width(cr, 1.0); /* Horizonal line */ cairo_move_to(cr, g->wp.x, y); cairo_line_to(cr, g->wp.x + g->wp.width, y); /* Vertical line */ cairo_move_to(cr, x, g->wp.y); cairo_line_to(cr, x, g->wp.y + g->wp.height); cairo_stroke(cr); cairo_destroy(cr); } /* Update state */ g->cross.x = x; g->cross.y = y; g->cross.erase_needed = TRUE; } static void cross_erase(struct graph *g) { int x = g->cross.x; int y = g->cross.y; if (x > g->wp.x && x < g->wp.x+g->wp.width && y >= g->wp.y && y < g->wp.y+g->wp.height) { /* Just redraw what is in the pixmap buffer */ graph_pixmap_display(g); } g->cross.erase_needed = FALSE; } /* Toggle between showing the time starting at 0, or time in capture */ static void toggle_time_origin(struct graph *g) { g->style.flags ^= TIME_ORIGIN; if ((g->style.flags & TIME_ORIGIN) == TIME_ORIGIN_CAP) { g->x_axis->min = g->bounds.x0; } else { g->x_axis->min = 0; } /* Redraw the axis */ axis_display(g->x_axis); } static void restore_initial_graph_view(struct graph *g) { g->geom.width = g->wp.width; g->geom.height = g->wp.height; g->geom.x = g->wp.x; g->geom.y = g->wp.y; graph_init_sequence(g); /* Set flags so that mouse zoom will zoom in (zooming out is not possible!) */ g->zoom.flags &= ~ZOOM_OUT; if (g->cross.draw) { g->cross.erase_needed = FALSE; } } /* Walk the segment list, totalling up data PDUs, status ACKs and NACKs */ static void get_data_control_counts(struct graph *g, int *data, int *acks, int *nacks) { struct segment *tmp; *data = 0; *acks = 0; *nacks = 0; for (tmp=g->segments; tmp; tmp=tmp->next) { if (tmp->isControlPDU) { (*acks)++; (*nacks) += tmp->noOfNACKs; } else { (*data)++; } } } /* Determine "bounds" * Essentially: look for lowest/highest time and seq in the list of segments * Not currently trying to work out the upper bound of the window, as we * don't reliably know the RLC channel state variables... */ static void graph_get_bounds(struct graph *g) { struct segment *tmp; double tim; gboolean data_frame_seen=FALSE; double data_tim_low=0; double data_tim_high=0; guint32 data_seq_cur; guint32 data_seq_low=0; guint32 data_seq_high=0; gboolean ack_frame_seen=FALSE; double ack_tim_low=0; double ack_tim_high=0; guint32 ack_seq_cur; guint32 ack_seq_low=0; guint32 ack_seq_high=0; /* Go through all segments to determine "bounds" */ for (tmp=g->segments; tmp; tmp=tmp->next) { if (!tmp->isControlPDU) { /* DATA frame */ tim = tmp->rel_secs + tmp->rel_usecs / 1000000.0; data_seq_cur = tmp->SN; /* Want to include a little beyond end, so that cross on last SN will (more likely) fit within bounds */ #define A_FEW_SUBFRAMES 0.005 /* Initialise if first time seen */ if (!data_frame_seen) { data_tim_low = tim; data_tim_high = tim + A_FEW_SUBFRAMES; data_seq_low = data_seq_cur; data_seq_high = data_seq_cur+1; data_frame_seen = TRUE; } /* Update bounds after this frame */ if (tim < data_tim_low) data_tim_low = tim; if (tim+0.02 > data_tim_high) data_tim_high = tim + A_FEW_SUBFRAMES; if (data_seq_cur < data_seq_low) data_seq_low = data_seq_cur; if (data_seq_cur+1 > data_seq_high) data_seq_high = data_seq_cur+1; } else { /* STATUS PDU */ int n; guint32 nack_seq_cur; tim = tmp->rel_secs + tmp->rel_usecs / 1000000.0; ack_seq_cur = tmp->ACKNo; /* Initialise if first status PDU seen */ if (!ack_frame_seen) { ack_tim_low = ack_tim_high = tim; ack_seq_low = ack_seq_cur; ack_seq_high = ack_seq_cur; ack_frame_seen = TRUE; } /* Update bounds after this frame */ if (tim < ack_tim_low) ack_tim_low = tim; if (tim > ack_tim_high) ack_tim_high = tim; if (ack_seq_cur < ack_seq_low) ack_seq_low = ack_seq_cur; if (ack_seq_cur > ack_seq_high) ack_seq_high = ack_seq_cur; /* Also run through any/all NACKs to see if ack_seq_low/ack_seq_high should be extended */ for (n=0; n < tmp->noOfNACKs; n++) { nack_seq_cur = tmp->NACKs[n]; if (nack_seq_cur < ack_seq_low) ack_seq_low = nack_seq_cur; if (nack_seq_cur > ack_seq_high) ack_seq_high = nack_seq_cur; } } } g->bounds.x0 = ((data_tim_low <= ack_tim_low && data_frame_seen) || (!ack_frame_seen)) ? data_tim_low : ack_tim_low; g->bounds.width = (((data_tim_high >= ack_tim_high && data_frame_seen) || (!ack_frame_seen)) ? data_tim_high : ack_tim_high) - g->bounds.x0; g->bounds.y0 = 0; /* We always want the overal bounds to go back down to SN=0 */ g->bounds.height = (((data_seq_high >= ack_seq_high && data_frame_seen) || (!ack_frame_seen)) ? data_seq_high : ack_seq_high); g->zoom.x = (g->geom.width - 1) / g->bounds.width; g->zoom.y = (g->geom.height -1) / g->bounds.height; } static void graph_read_config(struct graph *g) { /* Black */ g->style.seq_color.pixel=0; g->style.seq_color.red=0; g->style.seq_color.green=0; g->style.seq_color.blue=0; /* Blueish */ g->style.ack_color[0].pixel=0; g->style.ack_color[0].red=0x2222; g->style.ack_color[0].green=0x2222; g->style.ack_color[0].blue=0xaaaa; /* Reddish */ g->style.ack_color[1].pixel=0; g->style.ack_color[1].red=0xaaaa; g->style.ack_color[1].green=0x2222; g->style.ack_color[1].blue=0x2222; /* Time origin should be shown as time in capture by default */ g->style.flags = TIME_ORIGIN_CAP; g->y_axis->label = (const char ** )g_malloc(3 * sizeof(char * )); g->y_axis->label[0] = "Number"; g->y_axis->label[1] = "Sequence"; g->y_axis->label[2] = NULL; g->x_axis->label = (const char ** )g_malloc(2 * sizeof(char * )); g->x_axis->label[0] = "Time[s]"; g->x_axis->label[1] = NULL; } static void rlc_lte_make_elmtlist(struct graph *g) { struct segment *tmp; struct element *elements0, *e0; /* list of elmts showing control */ struct element *elements1, *e1; /* list of elmts showing data */ struct segment *last_status_segment = NULL; double xx0, yy0; gboolean data_seen = FALSE; gboolean ack_seen = FALSE; guint32 seq_base; guint32 seq_cur; int n, data, acks, nacks; double previous_data_x=0.0, previous_data_y=0.0; double previous_status_x=0.0, previous_status_y=0.0; debug(DBS_FENTRY) puts("rlc_lte_make_elmtlist()"); /* Allocate all needed elements up-front */ if (g->elists->elements == NULL) { get_data_control_counts(g, &data, &acks, &nacks); /* Allocate elements for status */ n = 2 + (5*acks) + (4*nacks); e0 = elements0 = (struct element *)g_malloc(n*sizeof(struct element)); /* Allocate elements for data */ n = 1 + (5*data); e1 = elements1 = (struct element *)g_malloc(n*sizeof(struct element)); /* Allocate container for 2nd list of elements */ g->elists->next = (struct element_list *)g_malloc0(sizeof(struct element_list)); } else { e0 = elements0 = g->elists->elements; e1 = elements1 = g->elists->next->elements; } xx0 = g->bounds.x0; yy0 = g->bounds.y0; seq_base = (guint32) yy0; for (tmp=g->segments; tmp; tmp=tmp->next) { double secs; double x, y; /****************************************/ /* X axis is time, Y is sequence number */ /****************************************/ secs = tmp->rel_secs + (tmp->rel_usecs / 1000000.0); x = secs - xx0; x *= g->zoom.x; if (!tmp->isControlPDU) { /* DATA */ double yy1, yy2; double xx1, xx2; /* seq_cur is SN */ seq_cur = tmp->SN - seq_base; /* Work out positions around this SN */ #define DATA_CROSS_SIZE 2 y = (g->zoom.y * seq_cur); yy1 = y + DATA_CROSS_SIZE; yy2 = y - DATA_CROSS_SIZE; xx1 = x - DATA_CROSS_SIZE; xx2 = x + DATA_CROSS_SIZE; if (data_seen) { /* Line from previous data point (if any) */ e1->type = ELMT_LINE; e1->parent = tmp; /* So line belongs to this frame... */ e1->elment_color_p = &g->style.seq_color; e1->p.line.dim.x1 = previous_data_x; e1->p.line.dim.y1 = previous_data_y; e1->p.line.dim.x2 = x; e1->p.line.dim.y2 = y; e1++; } /* Diagonal lines */ e1->type = ELMT_LINE; e1->parent = tmp; e1->elment_color_p = &g->style.seq_color; e1->p.line.dim.x1 = xx1; e1->p.line.dim.y1 = yy1; e1->p.line.dim.x2 = xx2; e1->p.line.dim.y2 = yy2; e1++; e1->type = ELMT_LINE; e1->parent = tmp; e1->elment_color_p = &g->style.seq_color; e1->p.line.dim.x1 = xx2; e1->p.line.dim.y1 = yy1; e1->p.line.dim.x2 = xx1; e1->p.line.dim.y2 = yy2; e1++; /* Vertical */ e1->type = ELMT_LINE; e1->parent = tmp; e1->elment_color_p = &g->style.seq_color; e1->p.line.dim.x1 = x; e1->p.line.dim.y1 = yy1; e1->p.line.dim.x2 = x; e1->p.line.dim.y2 = yy2; e1++; /* Horizontal */ e1->type = ELMT_LINE; e1->parent = tmp; e1->elment_color_p = &g->style.seq_color; e1->p.line.dim.x1 = xx2; e1->p.line.dim.y1 = y; e1->p.line.dim.x2 = xx1; e1->p.line.dim.y2 = y; e1++; data_seen = TRUE; previous_data_x = x; previous_data_y = y; } else { /* Remember the last status segment */ last_status_segment = tmp; /* -1 so ACK lines up with last data, rather than showing above it... */ seq_cur = tmp->ACKNo - seq_base - 1; /* Work out positions around this SN */ y = (g->zoom.y * seq_cur); if (ack_seen) { if (y > previous_status_y) { /* Draw from previous ACK point horizontally to this time */ e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[0]; e0->p.line.dim.x1 = previous_status_x; e0->p.line.dim.y1 = previous_status_y; e0->p.line.dim.x2 = x; e0->p.line.dim.y2 = previous_status_y; e0++; /* Now draw up to current ACK */ e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[0]; e0->p.line.dim.x1 = x; e0->p.line.dim.y1 = previous_status_y; e0->p.line.dim.x2 = x; e0->p.line.dim.y2 = y; e0++; } else { /* Want to go down, then along in this case... */ e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[0]; e0->p.line.dim.x1 = previous_status_x; e0->p.line.dim.y1 = previous_status_y; e0->p.line.dim.x2 = previous_status_x; e0->p.line.dim.y2 = y; e0++; /* Now draw up to current ACK */ e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[0]; e0->p.line.dim.x1 = previous_status_x; e0->p.line.dim.y1 = y; e0->p.line.dim.x2 = x; e0->p.line.dim.y2 = y; e0++; } } if (tmp->noOfNACKs > 0) { for (n=0; n < tmp->noOfNACKs; n++) { double nack_y = (g->zoom.y * tmp->NACKs[n]); /* A red cross to show where the NACK is reported */ #define NACK_CROSS_SIZE 8 e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[1]; e0->p.line.dim.x1 = x -NACK_CROSS_SIZE; e0->p.line.dim.y1 = nack_y - NACK_CROSS_SIZE; e0->p.line.dim.x2 = x + NACK_CROSS_SIZE; e0->p.line.dim.y2 = nack_y + NACK_CROSS_SIZE; e0++; e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[1]; e0->p.line.dim.x1 = x - NACK_CROSS_SIZE; e0->p.line.dim.y1 = nack_y + NACK_CROSS_SIZE; e0->p.line.dim.x2 = x + NACK_CROSS_SIZE; e0->p.line.dim.y2 = nack_y - NACK_CROSS_SIZE; e0++; e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[1]; e0->p.line.dim.x1 = x; e0->p.line.dim.y1 = nack_y + NACK_CROSS_SIZE; e0->p.line.dim.x2 = x; e0->p.line.dim.y2 = nack_y - NACK_CROSS_SIZE; e0++; e0->type = ELMT_LINE; e0->parent = tmp; e0->elment_color_p = &g->style.ack_color[1]; e0->p.line.dim.x1 = x - NACK_CROSS_SIZE; e0->p.line.dim.y1 = nack_y; e0->p.line.dim.x2 = x + NACK_CROSS_SIZE; e0->p.line.dim.y2 = nack_y; e0++; } } ack_seen = TRUE; previous_status_x = x; previous_status_y = y; } } if (ack_seen) { /* Add one more line for status, from the last PDU -> rhs of graph */ e0->type = ELMT_LINE; e0->parent = last_status_segment; e0->elment_color_p = &g->style.ack_color[0]; e0->p.line.dim.x1 = previous_status_x; e0->p.line.dim.y1 = previous_status_y; e0->p.line.dim.x2 = g->bounds.width * g->zoom.x; /* right edge of graph area */ e0->p.line.dim.y2 = previous_status_y; e0++; } /* Complete both element lists */ e0->type = ELMT_NONE; e1->type = ELMT_NONE; g->elists->elements = elements0; g->elists->next->elements = elements1; } #if defined(_WIN32) && !defined(__MINGW32__) /* replacement of Unix rint() for Windows */ static int rint(double x) { char *buf; int i,dec,sig; buf = _fcvt(x, 0, &dec, &sig); i = atoi(buf); if (sig == 1) { i = i * -1; } return i; } #endif