diff options
-rw-r--r-- | epan/dissectors/packet-iax2.c | 87 | ||||
-rw-r--r-- | epan/dissectors/packet-iax2.h | 24 | ||||
-rw-r--r-- | gtk/Makefile.common | 2 | ||||
-rw-r--r-- | gtk/iax2_analysis.c | 3558 | ||||
-rw-r--r-- | gtk/iax2_analysis.h | 119 | ||||
-rw-r--r-- | gtk/voip_calls.c | 181 | ||||
-rw-r--r-- | gtk/voip_calls.h | 4 | ||||
-rw-r--r-- | gtk/voip_calls_dlg.c | 3 |
8 files changed, 3965 insertions, 13 deletions
diff --git a/epan/dissectors/packet-iax2.c b/epan/dissectors/packet-iax2.c index 28a31521ef..c094a464f3 100644 --- a/epan/dissectors/packet-iax2.c +++ b/epan/dissectors/packet-iax2.c @@ -47,6 +47,8 @@ #include <epan/emem.h> #include <epan/reassemble.h> #include <epan/aftypes.h> +#include <epan/tap.h> +#include <epan/tap-voip.h> #include "packet-iax2.h" #include <epan/iax2_codec_type.h> @@ -67,6 +69,13 @@ /* Wireshark ID of the IAX2 protocol */ static int proto_iax2 = -1; +/* tap register id */ +static int iax2_tap = -1; + +/* protocol tap info */ +static iax2_info_t ii_arr[1] = {0}; +static iax2_info_t *iax2_info = ii_arr; + /* The following hf_* variables are used to hold the wireshark IDs of * our header fields; they are filled out when we call * proto_register_field_array() in proto_register_iax2() @@ -247,6 +256,19 @@ static const value_string iax_cmd_subclasses[] = { {0,NULL} }; +/* IAX2 to tap-voip call state mapping */ +static const voip_call_state tap_cmd_voip_state[] = { + VOIP_NO_STATE, + VOIP_COMPLETED, /*HANGUP*/ + VOIP_RINGING, /*RING*/ + VOIP_RINGING, /*RINGING*/ + VOIP_IN_CALL, /*ANSWER*/ + VOIP_REJECTED, /*BUSY*/ + VOIP_UNKNOWN, /*TKOFFHK*/ + VOIP_UNKNOWN /*OFFHOOK*/ +}; + + /* Subclassess for Modem packets */ static const value_string iax_modem_subclasses[] = { {0, "(0?)"}, @@ -338,12 +360,6 @@ static const value_string iax_dataformats[] = { {0,NULL} }; -typedef enum { - IAX2_MINI_VOICE_PACKET, - IAX2_FULL_PACKET, - IAX2_MINI_VIDEO_PACKET, - IAX2_META_PACKET -} packet_type; static const value_string iax_packet_types[] = { {IAX2_FULL_PACKET, "Full packet"}, @@ -1046,18 +1062,31 @@ dissect_iax2 (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree) proto_tree_add_item (full_mini_subtree, hf_iax2_scallno, tvb, offset-2, 2, FALSE); } + iax2_info->ptype = type; + iax2_info->scallno = 0; + iax2_info->dcallno = 0; + iax2_info->ftype = 0; + iax2_info->csub = 0; + iax2_info->callState = VOIP_NO_STATE; + iax2_info->payload_len = 0; + iax2_info->timestamp = 0; + iax2_info->payload_data = NULL; + switch( type ) { case IAX2_FULL_PACKET: len = dissect_fullpacket( tvb, offset, scallno, pinfo, full_mini_subtree, tree ); break; case IAX2_MINI_VOICE_PACKET: + iax2_info->messageName = "MINI_VOICE_PACKET"; len = dissect_minipacket( tvb, offset, scallno, pinfo, full_mini_subtree, tree ); break; case IAX2_MINI_VIDEO_PACKET: + iax2_info->messageName = "MINI_VIDEO_PACKET"; len = dissect_minivideopacket( tvb, offset, scallno, pinfo, full_mini_subtree, tree ); break; case IAX2_META_PACKET: /* not implemented yet */ + iax2_info->messageName = "META_PACKET"; len = 0; break; default: @@ -1067,6 +1096,7 @@ dissect_iax2 (tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree) /* update the 'length' of the main IAX2 header field so that it covers just the headers, not the audio data. */ proto_item_set_len(iax2_item, len); + tap_queue_packet(iax2_tap, pinfo, iax2_info); } static proto_item *dissect_datetime_ie(tvbuff_t *tvb, guint32 offset, proto_tree *ies_tree) @@ -1113,6 +1143,13 @@ static guint32 dissect_ies (tvbuff_t * tvb, guint32 offset, if (ies_len != 4) THROW(ReportedBoundsError); ie_data -> dataformat = tvb_get_ntohl(tvb, offset+2); break; + + case IAX_IE_CALLED_NUMBER: + iax2_info->calledParty = g_strdup(tvb_format_text(tvb, offset+2, ies_len)); + break; + case IAX_IE_CALLING_NUMBER: + iax2_info->callingParty = g_strdup(tvb_format_text(tvb, offset+2, ies_len)); + break; case IAX_IE_APPARENT_ADDR: /* The IAX2 I-D says that the "apparent address" structure @@ -1417,15 +1454,18 @@ static void iax2_add_ts_fields(packet_info * pinfo, proto_tree * iax2_tree, iax_ iax_packet->abstime.secs ++; } } + iax2_info->timestamp = longts; - item = proto_tree_add_time(iax2_tree, hf_iax2_absts, NULL, 0, 0, &iax_packet->abstime); - PROTO_ITEM_SET_GENERATED(item); + if (iax2_tree) { + item = proto_tree_add_time(iax2_tree, hf_iax2_absts, NULL, 0, 0, &iax_packet->abstime); + PROTO_ITEM_SET_GENERATED(item); - ts = pinfo->fd->abs_ts; - nstime_delta(&ts, &ts, &iax_packet->abstime); + ts = pinfo->fd->abs_ts; + nstime_delta(&ts, &ts, &iax_packet->abstime); - item = proto_tree_add_time(iax2_tree, hf_iax2_lateness, NULL, 0, 0, &ts); - PROTO_ITEM_SET_GENERATED(item); + item = proto_tree_add_time(iax2_tree, hf_iax2_lateness, NULL, 0, 0, &ts); + PROTO_ITEM_SET_GENERATED(item); + } } /* returns the new offset */ @@ -1457,6 +1497,10 @@ dissect_fullpacket (tvbuff_t * tvb, guint32 offset, ts = tvb_get_ntohl(tvb, offset+2); type = tvb_get_guint8(tvb, offset + 8); csub = tvb_get_guint8(tvb, offset + 9); + iax2_info->ftype = type; + iax2_info->csub = csub; + iax2_info->scallno = scallno; + iax2_info->dcallno = dcallno; /* see if we've seen this packet before */ iax_packet = (iax_packet_data *)p_get_proto_data(pinfo->fd,proto_iax2); @@ -1508,18 +1552,24 @@ dissect_fullpacket (tvbuff_t * tvb, guint32 offset, /* add the type-specific subtree */ packet_type_tree = proto_item_add_subtree (packet_type_base, ett_iax2_type); + } else { + iax2_add_ts_fields(pinfo, iax2_tree, iax_packet, (guint16)ts); } + /* add frame type to info line */ if (check_col (pinfo->cinfo, COL_INFO)) { col_add_fstr (pinfo->cinfo, COL_INFO, "%s, source call# %d, timestamp %ums", val_to_str (type, iax_frame_types, "Unknown (0x%02x)"), scallno, ts); } + iax2_info->messageName = val_to_str (type, iax_frame_types, "Unknown (0x%02x)"); switch( type ) { case AST_FRAME_IAX: offset=dissect_iax2_command(tvb,offset+9,pinfo,packet_type_tree,iax_packet); + iax2_info->messageName = val_to_str (csub, iax_iax_subclasses, "unknown (0x%02x)"); + iax2_info->callState = csub; break; case AST_FRAME_DTMF_BEGIN: @@ -1540,6 +1590,8 @@ dissect_fullpacket (tvbuff_t * tvb, guint32 offset, if (check_col (pinfo->cinfo, COL_INFO)) col_append_fstr (pinfo->cinfo, COL_INFO, " %s", val_to_str (csub, iax_cmd_subclasses, "unknown (0x%02x)")); + iax2_info->messageName = val_to_str (csub, iax_cmd_subclasses, "unknown (0x%02x)"); + if (csub <= 8) iax2_info->callState = tap_cmd_voip_state[csub]; break; case AST_FRAME_VOICE: @@ -1683,6 +1735,8 @@ static guint32 dissect_minivideopacket (tvbuff_t * tvb, guint32 offset, proto_tree_add_item (iax2_tree, hf_iax2_minividts, tvb, offset, 2, FALSE); iax2_add_ts_fields(pinfo, iax2_tree, iax_packet, (guint16)ts); proto_tree_add_item (iax2_tree, hf_iax2_minividmarker, tvb, offset, 2, FALSE); + } else { + iax2_add_ts_fields(pinfo, iax2_tree, iax_packet, (guint16)ts); } offset += 2; @@ -1723,7 +1777,10 @@ dissect_minipacket (tvbuff_t * tvb, guint32 offset, guint16 scallno, packet_info proto_tree_add_uint (iax2_tree, hf_iax2_minits, tvb, offset, 2, ts); iax2_add_ts_fields(pinfo, iax2_tree, iax_packet,(guint16)ts); + } else { + iax2_add_ts_fields(pinfo, iax2_tree, iax_packet, (guint16)ts); } + offset += 2; @@ -2000,6 +2057,9 @@ static void dissect_payload(tvbuff_t *tvb, guint32 offset, proto_tree_add_text( iax2_tree, sub_tvb, 0, -1, "IAX2 payload (%u byte%s)", nbytes, plurality( nbytes, "", "s" )); + + iax2_info->payload_len = nbytes; + iax2_info->payload_data = tvb_get_ptr(sub_tvb, 0, -1); /* pass the rest of the block to a subdissector */ if(iax_packet->call_data) @@ -2089,7 +2149,7 @@ proto_register_iax2 (void) {&hf_iax2_absts, {"Absolute Time", "iax2.abstime", FT_ABSOLUTE_TIME, BASE_NONE, NULL, 0x0, - "The absolute time of this packet (calculated by adding the IAX timestamp to " + "The absoulte time of this packet (calculated by adding the IAX timestamp to " " the start time of this call)", HFILL}}, @@ -2555,6 +2615,7 @@ proto_register_iax2 (void) /* register our init routine to be called at the start of a capture, to clear out our hash tables etc */ register_init_routine(&iax_init_protocol); + iax2_tap = register_tap("IAX2"); } void diff --git a/epan/dissectors/packet-iax2.h b/epan/dissectors/packet-iax2.h index 44cc0de5df..92652155e8 100644 --- a/epan/dissectors/packet-iax2.h +++ b/epan/dissectors/packet-iax2.h @@ -215,4 +215,28 @@ #define IAX_DPSTATUS_IGNOREPAT (1 << 14) #define IAX_DPSTATUS_MATCHMORE (1 << 15) +typedef enum { + IAX2_MINI_VOICE_PACKET, + IAX2_FULL_PACKET, + IAX2_MINI_VIDEO_PACKET, + IAX2_META_PACKET +} packet_type; + +/* Container for tapping relevant data */ +typedef struct _iax2_info_t +{ + packet_type ptype; + guint16 scallno; + guint16 dcallno; + guint8 ftype; + guint8 csub; + guint32 timestamp; + guint payload_len; + voip_call_state callState; + const gchar *messageName; + gchar *callingParty; + gchar *calledParty; + const guint8 *payload_data; +} iax2_info_t; + #endif diff --git a/gtk/Makefile.common b/gtk/Makefile.common index e10847e1a7..0b29b4e842 100644 --- a/gtk/Makefile.common +++ b/gtk/Makefile.common @@ -76,6 +76,7 @@ WIRESHARK_GTK_SRC = \ gui_utils.c \ help_dlg.c \ hostlist_table.c \ + iax2_analysis.c \ macros_dlg.c \ main.c \ main_airpcap_toolbar.c \ @@ -249,6 +250,7 @@ noinst_HEADERS = \ gui_utils.h \ help_dlg.h \ hostlist_table.h \ + iax2_analysis.h \ keys.h \ macros_dlg.h \ main.h \ diff --git a/gtk/iax2_analysis.c b/gtk/iax2_analysis.c new file mode 100644 index 0000000000..8cde90b819 --- /dev/null +++ b/gtk/iax2_analysis.c @@ -0,0 +1,3558 @@ +/* iax2_analysis.c + * IAX2 analysis addition for Wireshark + * + * $Id$ + * + * based on rtp_analysis.c + * Copyright 2003, Alcatel Business Systems + * By Lars Ruoff <lars.ruoff@gmx.net> + * + * based on tap_rtp.c + * Copyright 2003, Iskratel, Ltd, Kranj + * By Miha Jemec <m.jemec@iskratel.si> + * + * Graph. Copyright 2004, Verso Technology + * By Alejandro Vaquero <alejandro.vaquero@verso.com> + * Based on io_stat.c by Ronnie Sahlberg + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <math.h> +#include <string.h> +#include <locale.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include <gtk/gtk.h> + +#include <epan/epan_dissect.h> +#include <epan/filesystem.h> +#include <epan/pint.h> +#include <epan/tap.h> +#include <epan/tap-voip.h> +#include <epan/dissectors/packet-iax2.h> +#include <epan/iax2_codec_type.h> +#include <epan/addr_resolv.h> +#include <epan/stat_cmd_args.h> +#include <epan/strutil.h> + +#include "../util.h" +#include "../register.h" +#include "../g711.h" +#include "../alert_box.h" +#include "../simple_dialog.h" +#include "../stat_menu.h" +#include "../progress_dlg.h" +#include "../color.h" +#include "../tempfile.h" +#include <wsutil/file_util.h> + +#include "gtk/gtkglobals.h" +#include "gtk/dlg_utils.h" +#include "gtk/file_dlg.h" +#include "gtk/gui_utils.h" +#include "gtk/gui_stat_menu.h" +#include "gtk/main.h" +#include "gtk/rtp_analysis.h" +#include "gtk/iax2_analysis.h" +#include "gtk/rtp_stream.h" +#include "gtk/rtp_stream_dlg.h" + +enum +{ + PACKET_COLUMN, + DELTA_COLUMN, + JITTER_COLUMN, + IPBW_COLUMN, + STATUS_COLUMN, + DATE_COLUMN, + LENGTH_COLUMN, + FOREGROUND_COLOR_COL, + BACKGROUND_COLOR_COL, + N_COLUMN /* The number of columns */ +}; + +/****************************************************************************/ + +typedef struct column_arrows { + GtkWidget *table; + GtkWidget *ascend_pm; + GtkWidget *descend_pm; +} column_arrows; + +#define NUM_COLS 7 +#define NUM_GRAPH_ITEMS 100000 +#define MAX_YSCALE 16 +#define AUTO_MAX_YSCALE 0 +#define MAX_GRAPHS 4 +#define GRAPH_FWD_JITTER 0 +#define GRAPH_FWD_DIFF 1 +#define GRAPH_REV_JITTER 2 +#define GRAPH_REV_DIFF 3 +static guint32 yscale_max[MAX_YSCALE] = {AUTO_MAX_YSCALE, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 50000000}; + +#define MAX_PIXELS_PER_TICK 4 +#define DEFAULT_PIXELS_PER_TICK 1 +static guint32 pixels_per_tick[MAX_PIXELS_PER_TICK] = {1, 2, 5, 10}; +static const char *graph_descr[4] = {"Fwd Jitter", "Fwd Difference", "Rvr Jitter", "Rvr Difference"}; +/* unit is in ms */ +#define MAX_TICK_VALUES 5 +#define DEFAULT_TICK_VALUE 1 +static guint tick_interval_values[MAX_TICK_VALUES] = { 1, 10, 100, 1000, 10000 }; +typedef struct _dialog_graph_graph_item_t { + guint32 value; + guint32 flags; +} dialog_graph_graph_item_t; + +typedef struct _dialog_graph_graph_t { + struct _user_data_t *ud; + dialog_graph_graph_item_t items[NUM_GRAPH_ITEMS]; + int plot_style; + gboolean display; + GtkWidget *display_button; + int hf_index; + GdkColor color; + GdkGC *gc; + gchar title[100]; +} dialog_graph_graph_t; + + +typedef struct _dialog_graph_t { + gboolean needs_redraw; + gint32 interval; /* measurement interval in ms */ + guint32 last_interval; + guint32 max_interval; /* XXX max_interval and num_items are redundant */ + guint32 num_items; + struct _dialog_graph_graph_t graph[MAX_GRAPHS]; + GtkWidget *window; + GtkWidget *draw_area; + GdkPixmap *pixmap; + GtkAdjustment *scrollbar_adjustment; + GtkWidget *scrollbar; + int pixmap_width; + int pixmap_height; + int pixels_per_tick; + int max_y_units; + double start_time; +} dialog_graph_t; + +typedef struct _dialog_data_t { + GtkWidget *window; + GtkWidget *list_fwd; + GtkTreeIter iter; + GtkWidget *list_rev; + GtkWidget *label_stats_fwd; + GtkWidget *label_stats_rev; + GtkWidget *selected_list; + guint number_of_nok; + GtkTreeSelection *selected_list_sel; + gint selected_list_row; + GtkWidget *notebook; + GtkWidget *save_voice_as_w; + GtkWidget *save_csv_as_w; + gint notebook_signal_id; + dialog_graph_t dialog_graph; +#ifdef USE_CONVERSATION_GRAPH + GtkWidget *graph_window; +#endif +} dialog_data_t; + +#define OK_TEXT "[ Ok ]" + +/* type of error when saving voice in a file didn't succeed */ +typedef enum { + TAP_RTP_WRONG_CODEC, + TAP_RTP_WRONG_LENGTH, + TAP_RTP_PADDING_ERROR, + TAP_RTP_SHORT_FRAME, + TAP_RTP_FILE_OPEN_ERROR, + TAP_RTP_NO_DATA +} error_type_t; + +typedef struct _tap_iax2_save_info_t { + FILE *fp; + guint32 count; + error_type_t error_type; + gboolean saved; +} tap_iax2_save_info_t; + + +/* structure that holds the information about the forward and reversed direction */ +struct _info_direction { + tap_iax2_stat_t statinfo; + tap_iax2_save_info_t saveinfo; +}; + +#define TMPNAMSIZE 100 + +#define SILENCE_PCMU (guint8)0xFF +#define SILENCE_PCMA (guint8)0x55 + +/* structure that holds general information about the connection +* and structures for both directions */ +typedef struct _user_data_t { + /* tap associated data*/ + address ip_src_fwd; + guint16 port_src_fwd; + address ip_dst_fwd; + guint16 port_dst_fwd; + address ip_src_rev; + guint16 port_src_rev; + address ip_dst_rev; + guint16 port_dst_rev; + + struct _info_direction forward; + struct _info_direction reversed; + + char f_tempname[TMPNAMSIZE]; + char r_tempname[TMPNAMSIZE]; + + /* dialog associated data */ + dialog_data_t dlg; + +#ifdef USE_CONVERSATION_GRAPH + time_series_t series_fwd; + time_series_t series_rev; +#endif +} user_data_t; + + +/* Column titles. */ +static const gchar *titles[7] = { + "Packet", + "Delta (ms)", + "Jitter (ms)", + "IP BW (kbps)", + "Status", + "Date", + "Length" +}; + +#define SAVE_FORWARD_DIRECTION_MASK 0x01 +#define SAVE_REVERSE_DIRECTION_MASK 0x02 +#define SAVE_BOTH_DIRECTION_MASK (SAVE_FORWARD_DIRECTION_MASK|SAVE_REVERSE_DIRECTION_MASK) + +#define SAVE_NONE_FORMAT 0 +#define SAVE_WAV_FORMAT 1 +#define SAVE_AU_FORMAT 2 +#define SAVE_SW_FORMAT 3 +#define SAVE_RAW_FORMAT 4 + + +static void on_refresh_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_); +/****************************************************************************/ +static void enable_graph(dialog_graph_graph_t *dgg) +{ + + dgg->display=TRUE; + +} + +static void dialog_graph_reset(user_data_t* user_data); + + + +/****************************************************************************/ +/* TAP FUNCTIONS */ + +/****************************************************************************/ +/* when there is a [re]reading of packet's */ +static void +iax2_reset(void *user_data_arg) +{ + user_data_t *user_data = user_data_arg; + user_data->forward.statinfo.first_packet = TRUE; + user_data->reversed.statinfo.first_packet = TRUE; + user_data->forward.statinfo.max_delta = 0; + user_data->reversed.statinfo.max_delta = 0; + user_data->forward.statinfo.max_jitter = 0; + user_data->reversed.statinfo.max_jitter = 0; + user_data->forward.statinfo.mean_jitter = 0; + user_data->reversed.statinfo.mean_jitter = 0; + user_data->forward.statinfo.delta = 0; + user_data->reversed.statinfo.delta = 0; + user_data->forward.statinfo.diff = 0; + user_data->reversed.statinfo.diff = 0; + user_data->forward.statinfo.jitter = 0; + user_data->reversed.statinfo.jitter = 0; + user_data->forward.statinfo.bandwidth = 0; + user_data->reversed.statinfo.bandwidth = 0; + user_data->forward.statinfo.total_bytes = 0; + user_data->reversed.statinfo.total_bytes = 0; + user_data->forward.statinfo.bw_start_index = 0; + user_data->reversed.statinfo.bw_start_index = 0; + user_data->forward.statinfo.bw_index = 0; + user_data->reversed.statinfo.bw_index = 0; + user_data->forward.statinfo.timestamp = 0; + user_data->reversed.statinfo.timestamp = 0; + user_data->forward.statinfo.max_nr = 0; + user_data->reversed.statinfo.max_nr = 0; + user_data->forward.statinfo.total_nr = 0; + user_data->reversed.statinfo.total_nr = 0; + user_data->forward.statinfo.sequence = 0; + user_data->reversed.statinfo.sequence = 0; + user_data->forward.statinfo.start_seq_nr = 0; + user_data->reversed.statinfo.start_seq_nr = 1; /* 1 is ok (for statistics in reversed direction) */ + user_data->forward.statinfo.stop_seq_nr = 0; + user_data->reversed.statinfo.stop_seq_nr = 0; + user_data->forward.statinfo.cycles = 0; + user_data->reversed.statinfo.cycles = 0; + user_data->forward.statinfo.under = FALSE; + user_data->reversed.statinfo.under = FALSE; + user_data->forward.statinfo.start_time = 0; + user_data->reversed.statinfo.start_time = 0; + user_data->forward.statinfo.time = 0; + user_data->reversed.statinfo.time = 0; + user_data->forward.statinfo.reg_pt = PT_UNDEFINED; + user_data->reversed.statinfo.reg_pt = PT_UNDEFINED; + + user_data->forward.saveinfo.count = 0; + user_data->reversed.saveinfo.count = 0; + user_data->forward.saveinfo.saved = FALSE; + user_data->reversed.saveinfo.saved = FALSE; + + /* clear the dialog box lists */ + gtk_list_store_clear(GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(user_data->dlg.list_fwd)))); + gtk_list_store_clear(GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(user_data->dlg.list_rev)))); + + /* reset graph info */ + dialog_graph_reset(user_data); + +#ifdef USE_CONVERSATION_GRAPH + if (user_data->dlg.graph_window != NULL) + window_destroy(user_data->dlg.graph_window); + + g_array_free(user_data->series_fwd.value_pairs, TRUE); + user_data->series_fwd.value_pairs = g_array_new(FALSE, FALSE, sizeof(value_pair_t)); + + g_array_free(user_data->series_rev.value_pairs, TRUE); + user_data->series_rev.value_pairs = g_array_new(FALSE, FALSE, sizeof(value_pair_t)); +#endif + + /* XXX check for error at fclose? */ + if (user_data->forward.saveinfo.fp != NULL) + fclose(user_data->forward.saveinfo.fp); + if (user_data->reversed.saveinfo.fp != NULL) + fclose(user_data->reversed.saveinfo.fp); + user_data->forward.saveinfo.fp = ws_fopen(user_data->f_tempname, "wb"); + if (user_data->forward.saveinfo.fp == NULL) + user_data->forward.saveinfo.error_type = TAP_RTP_FILE_OPEN_ERROR; + user_data->reversed.saveinfo.fp = ws_fopen(user_data->r_tempname, "wb"); + if (user_data->reversed.saveinfo.fp == NULL) + user_data->reversed.saveinfo.error_type = TAP_RTP_FILE_OPEN_ERROR; + return; +} + +/****************************************************************************/ +static int iax2_packet_add_graph(dialog_graph_graph_t *dgg, tap_iax2_stat_t *statinfo, packet_info *pinfo, guint32 value) +{ + dialog_graph_graph_item_t *it; + int idx; + double rtp_time; + + /* we sometimes get called when dgg is disabled. + this is a bug since the tap listener should be removed first */ + if(!dgg->display){ + return 0; + } + + dgg->ud->dlg.dialog_graph.needs_redraw=TRUE; + + /* + * Find which interval this is supposed to to in and store the + * interval index as idx + */ + if (dgg->ud->dlg.dialog_graph.start_time == -1){ /* it is the first */ + dgg->ud->dlg.dialog_graph.start_time = statinfo->start_time; + } + rtp_time = nstime_to_sec(&pinfo->fd->rel_ts) - dgg->ud->dlg.dialog_graph.start_time; + if(rtp_time<0){ + return FALSE; + } + idx = (guint32)(rtp_time*1000)/dgg->ud->dlg.dialog_graph.interval; + + /* some sanity checks */ + if((idx<0)||(idx>=NUM_GRAPH_ITEMS)){ + return FALSE; + } + + /* update num_items */ + if((guint32)idx > dgg->ud->dlg.dialog_graph.num_items){ + dgg->ud->dlg.dialog_graph.num_items=idx; + dgg->ud->dlg.dialog_graph.max_interval=idx*dgg->ud->dlg.dialog_graph.interval; + } + + /* + * Find the appropriate dialog_graph_graph_item_t structure + */ + it=&dgg->items[idx]; + + /* + * Use the max value to highlight RTP problems + */ + if (value > it->value) { + it->value=value; + } + it->flags = it->flags | statinfo->flags; + + return TRUE; +} + +/****************************************************************************/ +/* here we can redraw the output */ +/* not used yet */ +static void iax2_draw(void *prs _U_) +{ + return; +} + +/* forward declarations */ +static void add_to_list(GtkWidget *list, user_data_t * user_data, guint32 number, + double delta, double jitter, double bandwidth, gchar *status, + gchar *timeStr, guint32 pkt_len,gchar *color_str, guint32 flags); + +static int iax2_packet_add_info(GtkWidget *list,user_data_t * user_data, + tap_iax2_stat_t *statinfo, packet_info *pinfo, + const struct _iax2_info_t *iax2info); + +static int iax2_packet_save_payload(tap_iax2_save_info_t *saveinfo, + tap_iax2_stat_t *statinfo, + packet_info *pinfo, + const struct _iax2_info_t *iax2info); + + +/****************************************************************************/ +/* whenever a IAX2 packet is seen by the tap listener */ +static int iax2_packet(void *user_data_arg, packet_info *pinfo, epan_dissect_t *edt _U_, const void *iax2info_arg) +{ + user_data_t *user_data = user_data_arg; + const struct _iax2_info_t *iax2info = iax2info_arg; +#ifdef USE_CONVERSATION_GRAPH + value_pair_t vp; +#endif + /* we ignore packets that are not displayed */ + if (pinfo->fd->flags.passed_dfilter == 0) + return 0; + + /* we ignore packets that carry no data */ + if (iax2info->payload_len == 0) + return 0; + + /* is it the forward direction? */ + else if (CMP_ADDRESS(&(user_data->ip_src_fwd), &(pinfo->net_src)) == 0 + && user_data->port_src_fwd == pinfo->srcport + && CMP_ADDRESS(&(user_data->ip_dst_fwd), &(pinfo->net_dst)) == 0 + && user_data->port_dst_fwd == pinfo->destport) { +#ifdef USE_CONVERSATION_GRAPH + vp.time = ((double)pinfo->fd->rel_secs + (double)pinfo->fd->rel_usecs/1000000); + vp.fnumber = pinfo->fd->num; + g_array_append_val(user_data->series_fwd.value_pairs, vp); +#endif + iax2_packet_analyse(&(user_data->forward.statinfo), pinfo, iax2info); + iax2_packet_add_graph(&(user_data->dlg.dialog_graph.graph[GRAPH_FWD_JITTER]), &(user_data->forward.statinfo), pinfo, (guint32)(user_data->forward.statinfo.jitter*1000000)); + iax2_packet_add_graph(&(user_data->dlg.dialog_graph.graph[GRAPH_FWD_DIFF]), &(user_data->forward.statinfo), pinfo, (guint32)(user_data->forward.statinfo.diff*1000000)); + iax2_packet_add_info(user_data->dlg.list_fwd, user_data, + &(user_data->forward.statinfo), pinfo, iax2info); + iax2_packet_save_payload(&(user_data->forward.saveinfo), + &(user_data->forward.statinfo), pinfo, iax2info); + } + /* is it the reversed direction? */ + else if (CMP_ADDRESS(&(user_data->ip_src_rev), &(pinfo->net_src)) == 0 + && user_data->port_src_rev == pinfo->srcport + && CMP_ADDRESS(&(user_data->ip_dst_rev), &(pinfo->net_dst)) == 0 + && user_data->port_dst_rev == pinfo->destport) { +#ifdef USE_CONVERSATION_GRAPH + vp.time = ((double)pinfo->fd->rel_secs + (double)pinfo->fd->rel_usecs/1000000); + vp.fnumber = pinfo->fd->num; + g_array_append_val(user_data->series_rev.value_pairs, vp); +#endif + iax2_packet_analyse(&(user_data->reversed.statinfo), pinfo, iax2info); + iax2_packet_add_graph(&(user_data->dlg.dialog_graph.graph[GRAPH_REV_JITTER]), &(user_data->reversed.statinfo), pinfo, (guint32)(user_data->reversed.statinfo.jitter*1000000)); + iax2_packet_add_graph(&(user_data->dlg.dialog_graph.graph[GRAPH_REV_DIFF]), &(user_data->reversed.statinfo), pinfo, (guint32)(user_data->reversed.statinfo.diff*1000000)); + iax2_packet_add_info(user_data->dlg.list_rev, user_data, + &(user_data->reversed.statinfo), pinfo, iax2info); + iax2_packet_save_payload(&(user_data->reversed.saveinfo), + &(user_data->reversed.statinfo), pinfo, iax2info); + } + + return 0; +} + +/****************************************************************************/ +/* This comes from tap-rtp-common.c */ +/****************************************************************************/ + +int iax2_packet_analyse(tap_iax2_stat_t *statinfo, + packet_info *pinfo, + const struct _iax2_info_t *iax2info) +{ + double current_time; + double current_jitter; + double current_diff; + + statinfo->flags = 0; + /* check payload type */ + if (iax2info->ftype == AST_FRAME_VOICE) { + if (iax2info->csub != statinfo->pt) + statinfo->flags |= STAT_FLAG_PT_CHANGE; + statinfo->pt = iax2info->csub; + } + + /* store the current time and calculate the current jitter */ + current_time = nstime_to_sec(&pinfo->fd->rel_ts); + current_diff = fabs (current_time - statinfo->time - (((double)iax2info->timestamp - (double)statinfo->timestamp)/1000)); + current_jitter = statinfo->jitter + ( current_diff - statinfo->jitter)/16; + statinfo->delta = current_time-(statinfo->time); + statinfo->jitter = current_jitter; + statinfo->diff = current_diff; + + /* calculate the BW in Kbps adding the IP+IAX2 header to the RTP -> 20bytes(IP)+ 4bytes(Mini) = 24bytes */ + statinfo->bw_history[statinfo->bw_index].bytes = iax2info->payload_len + 24; + statinfo->bw_history[statinfo->bw_index].time = current_time; + /* check if there are more than 1sec in the history buffer to calculate BW in bps. If so, remove those for the calculation */ + while ((statinfo->bw_history[statinfo->bw_start_index].time+1)<current_time){ + statinfo->total_bytes -= statinfo->bw_history[statinfo->bw_start_index].bytes; + statinfo->bw_start_index++; + if (statinfo->bw_start_index == BUFF_BW) statinfo->bw_start_index=0; + }; + statinfo->total_bytes += iax2info->payload_len + 24; + statinfo->bandwidth = (double)(statinfo->total_bytes*8)/1000; + statinfo->bw_index++; + if (statinfo->bw_index == BUFF_BW) statinfo->bw_index = 0; + + + /* is this the first packet we got in this direction? */ + if (statinfo->first_packet) { + statinfo->start_seq_nr = 0; + statinfo->start_time = current_time; + statinfo->delta = 0; + statinfo->jitter = 0; + statinfo->diff = 0; + statinfo->flags |= STAT_FLAG_FIRST; + statinfo->first_packet = FALSE; + } + /* is it a regular packet? */ + if (!(statinfo->flags & STAT_FLAG_FIRST) + && !(statinfo->flags & STAT_FLAG_MARKER) + && !(statinfo->flags & STAT_FLAG_PT_CN) + && !(statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) + && !(statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)) { + /* include it in maximum delta calculation */ + if (statinfo->delta > statinfo->max_delta) { + statinfo->max_delta = statinfo->delta; + statinfo->max_nr = pinfo->fd->num; + } + /* maximum and mean jitter calculation */ + if (statinfo->jitter > statinfo->max_jitter) { + statinfo->max_jitter = statinfo->jitter; + } + statinfo->mean_jitter = (statinfo->mean_jitter*statinfo->total_nr + current_diff) / (statinfo->total_nr+1); + } + /* regular payload change? (CN ignored) */ + if (!(statinfo->flags & STAT_FLAG_FIRST) + && !(statinfo->flags & STAT_FLAG_PT_CN)) { + if ((statinfo->pt != statinfo->reg_pt) + && (statinfo->reg_pt != PT_UNDEFINED)) { + statinfo->flags |= STAT_FLAG_REG_PT_CHANGE; + } + } + + /* set regular payload*/ + if (!(statinfo->flags & STAT_FLAG_PT_CN)) { + statinfo->reg_pt = statinfo->pt; + } + + /* TODO: lost packets / duplicated: we should infer this from timestamp... */ + statinfo->time = current_time; + statinfo->timestamp = iax2info->timestamp; + statinfo->stop_seq_nr = 0; + statinfo->total_nr++; + + return 0; +} + + +static const GdkColor COLOR_DEFAULT = {0, 0xffff, 0xffff, 0xffff}; +static const GdkColor COLOR_ERROR = {0, 0xffff, 0xbfff, 0xbfff}; +static const GdkColor COLOR_WARNING = {0, 0xffff, 0xdfff, 0xbfff}; +static const GdkColor COLOR_CN = {0, 0xbfff, 0xbfff, 0xffff}; +static const GdkColor COLOR_FOREGROUND = {0, 0x0000, 0x0000, 0x0000}; + +/****************************************************************************/ +/* adds statistics information from the packet to the list */ +static int iax2_packet_add_info(GtkWidget *list, user_data_t * user_data, + tap_iax2_stat_t *statinfo, packet_info *pinfo, + const struct _iax2_info_t *iax2info) +{ + guint16 msecs; + gchar timeStr[32]; + struct tm *tm_tmp; + time_t then; + gchar status[40]; + GdkColor color = COLOR_DEFAULT; + gchar color_str[14]; + then = pinfo->fd->abs_ts.secs; + msecs = (guint16)(pinfo->fd->abs_ts.nsecs/1000000); + tm_tmp = localtime(&then); + g_snprintf(timeStr,sizeof(timeStr),"%02d/%02d/%04d %02d:%02d:%02d.%03d", + tm_tmp->tm_mon + 1, + tm_tmp->tm_mday, + tm_tmp->tm_year + 1900, + tm_tmp->tm_hour, + tm_tmp->tm_min, + tm_tmp->tm_sec, + msecs); + + /* Default to using black on white text if nothing below overrides it */ + g_snprintf(color_str,sizeof(color_str),"#ffffffffffff"); + + if (statinfo->flags & STAT_FLAG_WRONG_SEQ) { + g_snprintf(status,sizeof(status),"Wrong sequence nr."); + color = COLOR_ERROR; + g_snprintf(color_str,sizeof(color_str),"#ffffbfffbfff"); + } + else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) { + g_snprintf(status,sizeof(status),"Payload changed to PT=%u", statinfo->pt); + color = COLOR_WARNING; + g_snprintf(color_str,sizeof(color_str),"#ffffdfffbfff"); + } + else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) { + g_snprintf(status,sizeof(status),"Incorrect timestamp"); + color = COLOR_WARNING; + g_snprintf(color_str,sizeof(color_str),"#ffffdfffbfff"); + } + else if ((statinfo->flags & STAT_FLAG_PT_CHANGE) + && !(statinfo->flags & STAT_FLAG_FIRST) + && !(statinfo->flags & STAT_FLAG_PT_CN) + && (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN) + && !(statinfo->flags & STAT_FLAG_MARKER)) { + g_snprintf(status,sizeof(status),"Marker missing?"); + color = COLOR_WARNING; + g_snprintf(color_str,sizeof(color_str),"#ffffdfffbfff"); + } + else { + if (statinfo->flags & STAT_FLAG_MARKER) { + color = COLOR_WARNING; + g_snprintf(color_str,sizeof(color_str),"#ffffdfffbfff"); + } + g_snprintf(status,sizeof(status),OK_TEXT); + } + /* is this the first packet we got in this direction? */ + if (statinfo->flags & STAT_FLAG_FIRST) { + add_to_list(list, user_data, + pinfo->fd->num, + 0, + 0, + statinfo->bandwidth, + status, + timeStr, pinfo->fd->pkt_len, + color_str, + statinfo->flags); + } + else { + add_to_list(list, user_data, + pinfo->fd->num, + statinfo->delta*1000, + statinfo->jitter*1000, + statinfo->bandwidth, + status, + timeStr, pinfo->fd->pkt_len, + color_str, + statinfo->flags); + } + return 0; +} + +#define MAX_SILENCE_TICKS 1000000 +/****************************************************************************/ +static int iax2_packet_save_payload(tap_iax2_save_info_t *saveinfo, + tap_iax2_stat_t *statinfo, + packet_info *pinfo, + const struct _iax2_info_t *iax2info) +{ + const guint8 *data; + size_t nchars; + + /* is this the first packet we got in this direction? */ + if (statinfo->flags & STAT_FLAG_FIRST) { + if (saveinfo->fp == NULL) { + saveinfo->saved = FALSE; + saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR; + } + else + saveinfo->saved = TRUE; + } + + /* save the voice information */ + /* if there was already an error, we quit */ + if (saveinfo->saved == FALSE) + return 0; + + /* if the captured length and packet length aren't equal, we quit */ + if (pinfo->fd->pkt_len != pinfo->fd->cap_len) { + saveinfo->saved = FALSE; + saveinfo->error_type = TAP_RTP_WRONG_LENGTH; + return 0; + } + + if (iax2info->payload_len > 0) { + data = iax2info->payload_data; + nchars=fwrite(data, sizeof(unsigned char), iax2info->payload_len, saveinfo->fp); + saveinfo->count+=iax2info->payload_len; + + fflush(saveinfo->fp); + saveinfo->saved = TRUE; + return 0; + } + + return 0; +} + + +/****************************************************************************/ +/* CALLBACKS */ + +/****************************************************************************/ + +/****************************************************************************/ +/* close the dialog window and remove the tap listener */ +static void on_destroy(GtkWidget *win _U_, user_data_t *user_data _U_) +{ + /* remove tap listener */ + protect_thread_critical_region(); + remove_tap_listener(user_data); + unprotect_thread_critical_region(); + + /* close and remove temporary files */ + if (user_data->forward.saveinfo.fp != NULL) + fclose(user_data->forward.saveinfo.fp); + if (user_data->reversed.saveinfo.fp != NULL) + fclose(user_data->reversed.saveinfo.fp); + /*XXX: test for error **/ + ws_remove(user_data->f_tempname); + ws_remove(user_data->r_tempname); + + /* destroy save_voice_as window if open */ + if (user_data->dlg.save_voice_as_w != NULL) + window_destroy(user_data->dlg.save_voice_as_w); + + /* destroy graph window if open */ + if (user_data->dlg.dialog_graph.window != NULL) + window_destroy(user_data->dlg.dialog_graph.window); + +#ifdef USE_CONVERSATION_GRAPH + /* destroy graph window if open */ + if (user_data->dlg.graph_window != NULL) + window_destroy(user_data->dlg.graph_window); +#endif + + /* disable the "switch_page" signal in the dlg, otherwise will be called when the windows is destroy and cause an exception using GTK1*/ + g_signal_handler_disconnect(user_data->dlg.notebook, user_data->dlg.notebook_signal_id); + + g_free(user_data); +} + + +/****************************************************************************/ +static void on_notebook_switch_page(GtkNotebook *notebook _U_, + GtkNotebookPage *page _U_, + gint page_num _U_, + user_data_t *user_data _U_) +{ + user_data->dlg.selected_list = + (page_num==0) ? user_data->dlg.list_fwd : user_data->dlg.list_rev ; + + user_data->dlg.selected_list_row = 0; +} + +/****************************************************************************/ +static void on_list_select_row(GtkTreeSelection *selection, + user_data_t *user_data _U_/*gpointer data */) +{ + user_data->dlg.selected_list_sel = selection; +} + + +#ifdef USE_CONVERSATION_GRAPH +Note this will not work any more as clist is removed. +/****************************************************************************/ +/* when the graph window gets destroyed */ +static void on_destroy_graph(GtkWidget *win _U_, user_data_t *user_data _U_) +{ + /* note that graph window has been destroyed */ + user_data->dlg.graph_window = NULL; +} + +/****************************************************************************/ +static void graph_selection_callback(value_pair_t vp, user_data_t *user_data) +{ + guint row; + GtkCList *clist = NULL; + if (vp.fnumber != 0) { + clist = GTK_CLIST(user_data->dlg.clist_fwd); + row = gtk_clist_find_row_from_data(clist, + GUINT_TO_POINTER(vp.fnumber)); + if (row==-1) { + clist = GTK_CLIST(user_data->dlg.clist_rev); + row = gtk_clist_find_row_from_data(clist, + GUINT_TO_POINTER(vp.fnumber)); + } + if (row!=-1) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(user_data->dlg.notebook), + (clist == GTK_CLIST(user_data->dlg.clist_fwd)) ? 0 : 1); + gtk_clist_select_row(clist, row, 0); + gtk_clist_moveto(clist, row, 0, 0.5, 0); + } + } +} + + +/****************************************************************************/ +static void on_graph_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + gchar title1[80]; + gchar title2[80]; + GList *list = NULL; + + if (user_data->dlg.graph_window != NULL) { + /* There's already a graph window; reactivate it. */ + reactivate_window(user_data->dlg.graph_window); + return; + } + list = g_list_append(list, &(user_data->series_fwd)); + list = g_list_append(list, &(user_data->series_rev)); + + user_data->series_fwd.color.pixel = 0; + user_data->series_fwd.color.red = 0x80ff; + user_data->series_fwd.color.green = 0xe0ff; + user_data->series_fwd.color.blue = 0xffff; + user_data->series_fwd.yvalue = 0.5; + + user_data->series_rev.color.pixel = 0; + user_data->series_rev.color.red = 0x60ff; + user_data->series_rev.color.green = 0xc0ff; + user_data->series_rev.color.blue = 0xffff; + user_data->series_rev.yvalue = -0.5; + + g_snprintf(title1, 80, "Forward: %s:%u to %s:%u", + get_addr_name(&(user_data->ip_src_fwd)), + user_data->port_src_fwd, + get_addr_name(&(user_data->ip_dst_fwd)), + user_data->port_dst_fwd); + + g_snprintf(title2, 80, "Reverse: %s:%u to %s:%u", + get_addr_name(&(user_data->ip_src_rev)), + user_data->port_src_rev, + get_addr_name(&(user_data->ip_dst_rev)), + user_data->port_dst_rev); + + user_data->dlg.graph_window = show_conversation_graph(list, title1, title2, + &graph_selection_callback, user_data); + g_signal_connect(user_data->dlg.graph_window, "destroy", + G_CALLBACK(on_destroy_graph), user_data); +} +#endif /*USE_CONVERSATION_GRAPH*/ + +/****************************************************************************/ +static void dialog_graph_set_title(user_data_t* user_data) +{ + char *title; + if (!user_data->dlg.dialog_graph.window){ + return; + } + title = g_strdup_printf("IAX2 Graph Analysis Forward: %s:%u to %s:%u Reverse: %s:%u to %s:%u", + get_addr_name(&(user_data->ip_src_fwd)), + user_data->port_src_fwd, + get_addr_name(&(user_data->ip_dst_fwd)), + user_data->port_dst_fwd, + get_addr_name(&(user_data->ip_src_rev)), + user_data->port_src_rev, + get_addr_name(&(user_data->ip_dst_rev)), + user_data->port_dst_rev); + + gtk_window_set_title(GTK_WINDOW(user_data->dlg.dialog_graph.window), title); + g_free(title); + +} + + +/****************************************************************************/ +static void dialog_graph_reset(user_data_t* user_data) +{ + int i, j; + + user_data->dlg.dialog_graph.needs_redraw=TRUE; + for(i=0;i<MAX_GRAPHS;i++){ + for(j=0;j<NUM_GRAPH_ITEMS;j++){ + dialog_graph_graph_item_t *dggi; + dggi=&user_data->dlg.dialog_graph.graph[i].items[j]; + dggi->value=0; + dggi->flags=0; + } + } + user_data->dlg.dialog_graph.last_interval=0xffffffff; + user_data->dlg.dialog_graph.max_interval=0; + user_data->dlg.dialog_graph.num_items=0; + + /* create the color titles near the filter buttons */ + for(i=0;i<MAX_GRAPHS;i++){ + /* it is forward */ + if (i<2){ + g_snprintf(user_data->dlg.dialog_graph.graph[i].title, 100, "%s: %s:%u to %s:%u", + graph_descr[i], + get_addr_name(&(user_data->ip_src_fwd)), + user_data->port_src_fwd, + get_addr_name(&(user_data->ip_dst_fwd)), + user_data->port_dst_fwd); + /* it is reverse */ + } else { + g_snprintf(user_data->dlg.dialog_graph.graph[i].title, 100, "%s: %s:%u to %s:%u", + graph_descr[i], + get_addr_name(&(user_data->ip_src_rev)), + user_data->port_src_rev, + get_addr_name(&(user_data->ip_dst_rev)), + user_data->port_dst_rev); + } + } + + dialog_graph_set_title(user_data); +} + +/****************************************************************************/ +static guint32 get_it_value(dialog_graph_graph_t *dgg, int idx) +{ + dialog_graph_graph_item_t *it; + + it=&dgg->items[idx]; + + return it->value; +} + +/****************************************************************************/ +static void print_time_scale_string(char *buf, int buf_len, guint32 t) +{ + if(t>=10000000){ + g_snprintf(buf, buf_len, "%ds",t/1000000); + } else if(t>=1000000){ + g_snprintf(buf, buf_len, "%d.%03ds",t/1000000,(t%1000000)/1000); + } else if(t>=10000){ + g_snprintf(buf, buf_len, "%dms",t/1000); + } else if(t>=1000){ + g_snprintf(buf, buf_len, "%d.%03dms",t/1000,t%1000); + } else { + g_snprintf(buf, buf_len, "%dus",t); + } +} + +/****************************************************************************/ +static void dialog_graph_draw(user_data_t* user_data) +{ + int i, lwidth; + guint32 last_interval, first_interval, interval_delta, delta_multiplier; + gint32 current_interval; + guint32 left_x_border; + guint32 right_x_border; + guint32 top_y_border; + guint32 bottom_y_border; + PangoLayout *layout; + int label_width, label_height; + guint32 draw_width, draw_height; + char label_string[15]; + + /* new variables */ + guint32 num_time_intervals; + guint32 max_value; /* max value of seen data */ + guint32 max_y; /* max value of the Y scale */ + + if(!user_data->dlg.dialog_graph.needs_redraw){ + return; + } + user_data->dlg.dialog_graph.needs_redraw=FALSE; + + /* + * Find the length of the intervals we have data for + * so we know how large arrays we need to malloc() + */ + num_time_intervals=user_data->dlg.dialog_graph.num_items; + /* if there isnt anything to do, just return */ + if(num_time_intervals==0){ + return; + } + num_time_intervals+=1; + /* XXX move this check to _packet() */ + if(num_time_intervals>NUM_GRAPH_ITEMS){ + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "IAX2 Graph error. There are too many entries, bailing out"); + return; + } + + /* + * find the max value so we can autoscale the y axis + */ + max_value=0; + for(i=0;i<MAX_GRAPHS;i++){ + int idx; + + if(!user_data->dlg.dialog_graph.graph[i].display){ + continue; + } + for(idx=0;(guint32) (idx) < num_time_intervals;idx++){ + guint32 val; + + val=get_it_value(&user_data->dlg.dialog_graph.graph[i], idx); + + /* keep track of the max value we have encountered */ + if(val>max_value){ + max_value=val; + } + } + } + + /* + * Clear out old plot + */ + gdk_draw_rectangle(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->white_gc, + TRUE, + 0, 0, + user_data->dlg.dialog_graph.draw_area->allocation.width, + user_data->dlg.dialog_graph.draw_area->allocation.height); + + + /* + * Calculate the y scale we should use + */ + if(user_data->dlg.dialog_graph.max_y_units==AUTO_MAX_YSCALE){ + max_y=yscale_max[MAX_YSCALE-1]; + for(i=MAX_YSCALE-1;i>0;i--){ + if(max_value<yscale_max[i]){ + max_y=yscale_max[i]; + } + } + } else { + /* the user had specified an explicit y scale to use */ + max_y=user_data->dlg.dialog_graph.max_y_units; + } + + /* + * Calculate size of borders surrounding the plot + * The border on the right side needs to be adjusted depending + * on the width of the text labels. For simplicity we assume that the + * top y scale label will be the widest one + */ + print_time_scale_string(label_string, 15, max_y); + layout = gtk_widget_create_pango_layout(user_data->dlg.dialog_graph.draw_area, label_string); + pango_layout_get_pixel_size(layout, &label_width, &label_height); + left_x_border=10; + right_x_border=label_width+20; + top_y_border=10; + bottom_y_border=label_height+20; + + + /* + * Calculate the size of the drawing area for the actual plot + */ + draw_width=user_data->dlg.dialog_graph.pixmap_width-right_x_border-left_x_border; + draw_height=user_data->dlg.dialog_graph.pixmap_height-top_y_border-bottom_y_border; + + + /* + * Draw the y axis and labels + * (we always draw the y scale with 11 ticks along the axis) + */ + gdk_draw_line(user_data->dlg.dialog_graph.pixmap, user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+1, + top_y_border, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+1, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border); + for(i=0;i<=10;i++){ + int xwidth, lwidth; + + xwidth=5; + if(!(i%5)){ + /* first, middle and last tick are slightly longer */ + xwidth=10; + } + /* draw the tick */ + gdk_draw_line(user_data->dlg.dialog_graph.pixmap, user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+1, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border-draw_height*i/10, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+1+xwidth, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border-draw_height*i/10); + /* draw the labels */ + if(i==0){ + print_time_scale_string(label_string, 15, (max_y*i/10)); + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+15+label_width-lwidth, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border-draw_height*i/10-label_height/2, + layout); + } + if(i==5){ + print_time_scale_string(label_string, 15, (max_y*i/10)); + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+15+label_width-lwidth, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border-draw_height*i/10-label_height/2, + layout); + } + if(i==10){ + print_time_scale_string(label_string, 15, (max_y*i/10)); + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+15+label_width-lwidth, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border-draw_height*i/10-label_height/2, + layout); + } + } + + + + /* + * if we have not specified the last_interval via the gui, + * then just pick the current end of the capture so that is scrolls + * nicely when doing live captures + */ + if(user_data->dlg.dialog_graph.last_interval==0xffffffff){ + last_interval=user_data->dlg.dialog_graph.max_interval; + } else { + last_interval=user_data->dlg.dialog_graph.last_interval; + } + + + + +/*XXX*/ + /* plot the x-scale */ + gdk_draw_line(user_data->dlg.dialog_graph.pixmap, user_data->dlg.dialog_graph.draw_area->style->black_gc, left_x_border, user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+1, user_data->dlg.dialog_graph.pixmap_width-right_x_border+1, user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+1); + + if((last_interval/user_data->dlg.dialog_graph.interval)>draw_width/user_data->dlg.dialog_graph.pixels_per_tick+1){ + first_interval=(last_interval/user_data->dlg.dialog_graph.interval)-draw_width/user_data->dlg.dialog_graph.pixels_per_tick+1; + first_interval*=user_data->dlg.dialog_graph.interval; + } else { + first_interval=0; + } + + interval_delta=1; + delta_multiplier=5; + while(interval_delta<((last_interval-first_interval)/10)){ + interval_delta*=delta_multiplier; + if(delta_multiplier==5){ + delta_multiplier=2; + } else { + delta_multiplier=5; + } + } + + for(current_interval=last_interval;current_interval>(gint32)first_interval;current_interval=current_interval-user_data->dlg.dialog_graph.interval){ + int x, xlen; + + /* if pixels_per_tick is <5, only draw every 10 ticks */ + if((user_data->dlg.dialog_graph.pixels_per_tick<10) && (current_interval%(10*user_data->dlg.dialog_graph.interval))){ + continue; + } + + if(current_interval%interval_delta){ + xlen=5; + } else { + xlen=17; + } + + x=draw_width+left_x_border-((last_interval-current_interval)/user_data->dlg.dialog_graph.interval)*user_data->dlg.dialog_graph.pixels_per_tick; + gdk_draw_line(user_data->dlg.dialog_graph.pixmap, user_data->dlg.dialog_graph.draw_area->style->black_gc, + x-1-user_data->dlg.dialog_graph.pixels_per_tick/2, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+1, + x-1-user_data->dlg.dialog_graph.pixels_per_tick/2, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+xlen+1); + + if(xlen==17){ + int lwidth; + if(user_data->dlg.dialog_graph.interval>=1000){ + g_snprintf(label_string, 15, "%ds", current_interval/1000); + } else if(user_data->dlg.dialog_graph.interval>=100){ + g_snprintf(label_string, 15, "%d.%1ds", current_interval/1000,(current_interval/100)%10) +; + } else if(user_data->dlg.dialog_graph.interval>=10){ + g_snprintf(label_string, 15, "%d.%2ds", current_interval/1000,(current_interval/10)%100) +; + } else { + g_snprintf(label_string, 15, "%d.%3ds", current_interval/1000,current_interval%1000); + } + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + x-1-user_data->dlg.dialog_graph.pixels_per_tick/2-lwidth/2, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+20, + layout); + } + + } + + + + + + + /* + * Draw "x" for Sequence Errors and "m" for Marks + */ + /* Draw the labels Fwd and Rev */ + g_strlcpy(label_string,"<-Fwd",15); + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+33-lwidth, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+3, + layout); + g_strlcpy(label_string,"<-Rev",15); + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + user_data->dlg.dialog_graph.pixmap_width-right_x_border+33-lwidth, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+3+9, + layout); + + /* Draw the marks */ + for(i=MAX_GRAPHS-1;i>=0;i--){ + guint32 interval; + guint32 x_pos, prev_x_pos; + + /* XXX for fwd or rev, the flag info for jitter and diff is the same, and here I loop twice */ + if (!user_data->dlg.dialog_graph.graph[i].display){ + continue; + } + /* initialize prev x/y to the low left corner of the graph */ + prev_x_pos=draw_width-1-user_data->dlg.dialog_graph.pixels_per_tick*((last_interval-first_interval)/user_data->dlg.dialog_graph.interval+1)+left_x_border; + + for(interval=first_interval+user_data->dlg.dialog_graph.interval;interval<=last_interval;interval+=user_data->dlg.dialog_graph.interval){ + x_pos=draw_width-1-user_data->dlg.dialog_graph.pixels_per_tick*((last_interval-interval)/user_data->dlg.dialog_graph.interval+1)+left_x_border; + + if(user_data->dlg.dialog_graph.graph[i].items[interval/user_data->dlg.dialog_graph.interval].flags & (STAT_FLAG_WRONG_SEQ|STAT_FLAG_MARKER)){ + int lwidth; + if (user_data->dlg.dialog_graph.graph[i].items[interval/user_data->dlg.dialog_graph.interval].flags & STAT_FLAG_WRONG_SEQ){ + g_strlcpy(label_string,"x",15); + } else { + g_strlcpy(label_string,"m",15); + } + + pango_layout_set_text(layout, label_string, -1); + pango_layout_get_pixel_size(layout, &lwidth, NULL); + gdk_draw_layout(user_data->dlg.dialog_graph.pixmap, + user_data->dlg.dialog_graph.draw_area->style->black_gc, + x_pos-1-lwidth/2, + user_data->dlg.dialog_graph.pixmap_height-bottom_y_border+3+7*(i/2), + layout); + } + + prev_x_pos=x_pos; + } + } + + g_object_unref(G_OBJECT(layout)); + + /* + * Loop over all graphs and draw them + */ + for(i=MAX_GRAPHS-1;i>=0;i--){ + guint32 interval; + guint32 x_pos, y_pos, prev_x_pos, prev_y_pos; + if (!user_data->dlg.dialog_graph.graph[i].display){ + continue; + } + /* initialize prev x/y to the low left corner of the graph */ + prev_x_pos=draw_width-1-user_data->dlg.dialog_graph.pixels_per_tick*((last_interval-first_interval)/user_data->dlg.dialog_graph.interval+1)+left_x_border; + prev_y_pos=draw_height-1+top_y_border; + + for(interval=first_interval+user_data->dlg.dialog_graph.interval;interval<=last_interval;interval+=user_data->dlg.dialog_graph.interval){ + guint32 val; + x_pos=draw_width-1-user_data->dlg.dialog_graph.pixels_per_tick*((last_interval-interval)/user_data->dlg.dialog_graph.interval+1)+left_x_border; + val=get_it_value(&user_data->dlg.dialog_graph.graph[i], interval/user_data->dlg.dialog_graph.interval); + if(val>max_y){ + y_pos=0; + } else { + y_pos=draw_height-1-(val*draw_height)/max_y+top_y_border; + } + + /* dont need to draw anything if the segment + * is entirely above the top of the graph + */ + if( (prev_y_pos==0) && (y_pos==0) ){ + prev_y_pos=y_pos; + prev_x_pos=x_pos; + continue; + } + + if(val){ + gdk_draw_line(user_data->dlg.dialog_graph.pixmap, user_data->dlg.dialog_graph.graph[i].gc, + x_pos, draw_height-1+top_y_border, + x_pos, y_pos); + } + + prev_y_pos=y_pos; + prev_x_pos=x_pos; + } + } + + + gdk_draw_pixmap(user_data->dlg.dialog_graph.draw_area->window, + user_data->dlg.dialog_graph.draw_area->style->fg_gc[GTK_WIDGET_STATE(user_data->dlg.dialog_graph.draw_area)], + user_data->dlg.dialog_graph.pixmap, + 0, 0, + 0, 0, + user_data->dlg.dialog_graph.pixmap_width, user_data->dlg.dialog_graph.pixmap_height); + + + /* update the scrollbar */ + user_data->dlg.dialog_graph.scrollbar_adjustment->upper=(gfloat) user_data->dlg.dialog_graph.max_interval; + user_data->dlg.dialog_graph.scrollbar_adjustment->step_increment=(gfloat) ((last_interval-first_interval)/10); + user_data->dlg.dialog_graph.scrollbar_adjustment->page_increment=(gfloat) (last_interval-first_interval); + if((last_interval-first_interval)*100 < user_data->dlg.dialog_graph.max_interval){ + user_data->dlg.dialog_graph.scrollbar_adjustment->page_size=(gfloat) (user_data->dlg.dialog_graph.max_interval/100); + } else { + user_data->dlg.dialog_graph.scrollbar_adjustment->page_size=(gfloat) (last_interval-first_interval); + } + user_data->dlg.dialog_graph.scrollbar_adjustment->value=last_interval-user_data->dlg.dialog_graph.scrollbar_adjustment->page_size; + gtk_adjustment_changed(user_data->dlg.dialog_graph.scrollbar_adjustment); + gtk_adjustment_value_changed(user_data->dlg.dialog_graph.scrollbar_adjustment); + +} + +/****************************************************************************/ +static void dialog_graph_redraw(user_data_t* user_data) +{ + user_data->dlg.dialog_graph.needs_redraw=TRUE; + dialog_graph_draw(user_data); +} + +/****************************************************************************/ +static gint quit(GtkWidget *widget, GdkEventExpose *event _U_) +{ + user_data_t *user_data; + + user_data=(user_data_t *)g_object_get_data(G_OBJECT(widget), "user_data_t"); + + user_data->dlg.dialog_graph.window = NULL; + return TRUE; +} + +/****************************************************************************/ +static gint expose_event(GtkWidget *widget, GdkEventExpose *event) +{ + user_data_t *user_data; + + user_data=(user_data_t *)g_object_get_data(G_OBJECT(widget), "user_data_t"); + if(!user_data){ + exit(10); + } + + + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + user_data->dlg.dialog_graph.pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + return FALSE; +} + +/****************************************************************************/ +static gint configure_event(GtkWidget *widget, GdkEventConfigure *event _U_) +{ + user_data_t *user_data; + int i; + + user_data=(user_data_t *)g_object_get_data(G_OBJECT(widget), "user_data_t"); + + if(!user_data){ + exit(10); + } + + if(user_data->dlg.dialog_graph.pixmap){ + gdk_pixmap_unref(user_data->dlg.dialog_graph.pixmap); + user_data->dlg.dialog_graph.pixmap=NULL; + } + + user_data->dlg.dialog_graph.pixmap=gdk_pixmap_new(widget->window, + widget->allocation.width, + widget->allocation.height, + -1); + user_data->dlg.dialog_graph.pixmap_width=widget->allocation.width; + user_data->dlg.dialog_graph.pixmap_height=widget->allocation.height; + + gdk_draw_rectangle(user_data->dlg.dialog_graph.pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height); + + /* set up the colors and the GC structs for this pixmap */ + for(i=0;i<MAX_GRAPHS;i++){ + user_data->dlg.dialog_graph.graph[i].gc=gdk_gc_new(user_data->dlg.dialog_graph.pixmap); + gdk_gc_set_rgb_fg_color(user_data->dlg.dialog_graph.graph[i].gc, &user_data->dlg.dialog_graph.graph[i].color); + } + + dialog_graph_redraw(user_data); + return TRUE; +} + +/****************************************************************************/ +static gint scrollbar_changed(GtkWidget *widget _U_, gpointer data) +{ + user_data_t *user_data=(user_data_t *)data; + guint32 mi; + + mi=(guint32) (user_data->dlg.dialog_graph.scrollbar_adjustment->value+user_data->dlg.dialog_graph.scrollbar_adjustment->page_size); + if(user_data->dlg.dialog_graph.last_interval==mi){ + return TRUE; + } + if( (user_data->dlg.dialog_graph.last_interval==0xffffffff) + && (mi==user_data->dlg.dialog_graph.max_interval) ){ + return TRUE; + } + + user_data->dlg.dialog_graph.last_interval=(mi/user_data->dlg.dialog_graph.interval)*user_data->dlg.dialog_graph.interval; + + dialog_graph_redraw(user_data); + return TRUE; +} + +/****************************************************************************/ +static void create_draw_area(user_data_t* user_data, GtkWidget *box) +{ + user_data->dlg.dialog_graph.draw_area=gtk_drawing_area_new(); + g_signal_connect(user_data->dlg.dialog_graph.draw_area, "destroy", G_CALLBACK(quit), user_data); + g_object_set_data(G_OBJECT(user_data->dlg.dialog_graph.draw_area), "user_data_t", user_data); + + gtk_widget_set_size_request(user_data->dlg.dialog_graph.draw_area, user_data->dlg.dialog_graph.pixmap_width, user_data->dlg.dialog_graph.pixmap_height); + + /* signals needed to handle backing pixmap */ + g_signal_connect(user_data->dlg.dialog_graph.draw_area, "expose_event", G_CALLBACK(expose_event), NULL); + g_signal_connect(user_data->dlg.dialog_graph.draw_area, "configure_event", G_CALLBACK(configure_event), user_data); + + gtk_widget_show(user_data->dlg.dialog_graph.draw_area); + gtk_box_pack_start(GTK_BOX(box), user_data->dlg.dialog_graph.draw_area, TRUE, TRUE, 0); + + /* create the associated scrollbar */ + user_data->dlg.dialog_graph.scrollbar_adjustment=(GtkAdjustment *)gtk_adjustment_new(0,0,0,0,0,0); + user_data->dlg.dialog_graph.scrollbar=gtk_hscrollbar_new(user_data->dlg.dialog_graph.scrollbar_adjustment); + gtk_widget_show(user_data->dlg.dialog_graph.scrollbar); + gtk_box_pack_start(GTK_BOX(box), user_data->dlg.dialog_graph.scrollbar, FALSE, FALSE, 0); + g_signal_connect(user_data->dlg.dialog_graph.scrollbar_adjustment, "value_changed", G_CALLBACK(scrollbar_changed), user_data); +} + +/****************************************************************************/ +static void disable_graph(dialog_graph_graph_t *dgg) +{ + if (dgg->display) { + dgg->display=FALSE; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dgg->display_button), + FALSE); + } +} + +/****************************************************************************/ +static gint filter_callback(GtkWidget *widget _U_, dialog_graph_graph_t *dgg) +{ + /* this graph is not active, just update display and redraw */ + if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dgg->display_button))){ + disable_graph(dgg); + dialog_graph_redraw(dgg->ud); + return 0; + } + + enable_graph(dgg); + cf_retap_packets(&cfile, FALSE); + dialog_graph_redraw(dgg->ud); + + return 0; +} + +/****************************************************************************/ +static void create_filter_box(dialog_graph_graph_t *dgg, GtkWidget *box, int num) +{ + GtkWidget *hbox; + GtkWidget *label; + char str[256]; + + hbox=gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(box), hbox); + gtk_box_set_child_packing(GTK_BOX(box), hbox, FALSE, FALSE, 0, GTK_PACK_START); + gtk_widget_show(hbox); + + g_snprintf(str, 256, "Graph %d", num); + dgg->display_button=gtk_toggle_button_new_with_label(str); + gtk_box_pack_start(GTK_BOX(hbox), dgg->display_button, FALSE, FALSE, 0); + gtk_widget_show(dgg->display_button); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dgg->display_button), dgg->display); + g_signal_connect(dgg->display_button, "toggled", G_CALLBACK(filter_callback), dgg); + + label=gtk_label_new(dgg->title); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &dgg->color); + gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, &dgg->color); + gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, &dgg->color); + gtk_widget_modify_fg(label, GTK_STATE_SELECTED, &dgg->color); + gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, &dgg->color); + + return; +} + +/****************************************************************************/ +static void create_filter_area(user_data_t* user_data, GtkWidget *box) +{ + GtkWidget *frame; + GtkWidget *vbox; + int i; + GtkWidget *label; + + frame=gtk_frame_new("Graphs"); + gtk_container_add(GTK_CONTAINER(box), frame); + gtk_widget_show(frame); + + vbox=gtk_vbox_new(FALSE, 1); + gtk_container_add(GTK_CONTAINER(frame), vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 3); + gtk_box_set_child_packing(GTK_BOX(box), vbox, FALSE, FALSE, 0, GTK_PACK_START); + gtk_widget_show(vbox); + + for(i=0;i<MAX_GRAPHS;i++){ + create_filter_box(&user_data->dlg.dialog_graph.graph[i], vbox, i+1); + } + + label=gtk_label_new("Label: x = Wrong Seq. number m = Mark set"); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + return; +} + +/****************************************************************************/ +static void yscale_select(GtkWidget *item, gpointer key) +{ + int val; + user_data_t *user_data; + + user_data=(user_data_t *)key; + val=(long)g_object_get_data(G_OBJECT(item), "yscale_max"); + + user_data->dlg.dialog_graph.max_y_units=val; + dialog_graph_redraw(user_data); +} + +/****************************************************************************/ +static void pixels_per_tick_select(GtkWidget *item, gpointer key) +{ + int val; + user_data_t *user_data; + + user_data=(user_data_t *)key; + val=(long)g_object_get_data(G_OBJECT(item), "pixels_per_tick"); + user_data->dlg.dialog_graph.pixels_per_tick=val; + dialog_graph_redraw(user_data); +} + +/****************************************************************************/ +static void tick_interval_select(GtkWidget *item, gpointer key) +{ + int val; + user_data_t *user_data; + + user_data=(user_data_t *)key; + val=(long)g_object_get_data(G_OBJECT(item), "tick_interval"); + + user_data->dlg.dialog_graph.interval=val; + cf_retap_packets(&cfile, FALSE); + dialog_graph_redraw(user_data); +} + +/****************************************************************************/ +static void create_yscale_max_menu_items(user_data_t* user_data, GtkWidget *menu) +{ + char str[15]; + GtkWidget *menu_item; + int i; + + for(i=0;i<MAX_YSCALE;i++){ + if(yscale_max[i]==AUTO_MAX_YSCALE){ + g_strlcpy(str,"Auto",15); + } else { + g_snprintf(str, 15, "%u ms", yscale_max[i]/1000); + } + menu_item=gtk_menu_item_new_with_label(str); + g_object_set_data(G_OBJECT(menu_item), "yscale_max", + GUINT_TO_POINTER(yscale_max[i])); + g_signal_connect(menu_item, "activate", G_CALLBACK(yscale_select), user_data); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + return; +} + +/****************************************************************************/ +static void create_pixels_per_tick_menu_items(user_data_t* user_data, GtkWidget *menu) +{ + char str[5]; + GtkWidget *menu_item; + int i; + + for(i=0;i<MAX_PIXELS_PER_TICK;i++){ + g_snprintf(str, 5, "%u", pixels_per_tick[i]); + menu_item=gtk_menu_item_new_with_label(str); + + g_object_set_data(G_OBJECT(menu_item), "pixels_per_tick", + GUINT_TO_POINTER(pixels_per_tick[i])); + g_signal_connect(menu_item, "activate", G_CALLBACK(pixels_per_tick_select), user_data); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + gtk_menu_set_active(GTK_MENU(menu), DEFAULT_PIXELS_PER_TICK); + return; +} + + +/****************************************************************************/ +static void create_tick_interval_menu_items(user_data_t* user_data, GtkWidget *menu) +{ + char str[15]; + GtkWidget *menu_item; + int i; + + for(i=0;i<MAX_TICK_VALUES;i++){ + if(tick_interval_values[i]>=1000){ + g_snprintf(str, 15, "%u sec", tick_interval_values[i]/1000); + } else if(tick_interval_values[i]>=100){ + g_snprintf(str, 15, "0.%1u sec", (tick_interval_values[i]/100)%10); + } else if(tick_interval_values[i]>=10){ + g_snprintf(str, 15, "0.%02u sec", (tick_interval_values[i]/10)%10); + } else { + g_snprintf(str, 15, "0.%03u sec", (tick_interval_values[i])%10); + } + + menu_item=gtk_menu_item_new_with_label(str); + g_object_set_data(G_OBJECT(menu_item), "tick_interval", + GUINT_TO_POINTER(tick_interval_values[i])); + g_signal_connect(menu_item, "activate", G_CALLBACK(tick_interval_select), (gpointer)user_data); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + gtk_menu_set_active(GTK_MENU(menu), DEFAULT_TICK_VALUE); + return; +} + +/****************************************************************************/ +static void create_ctrl_menu(user_data_t* user_data, GtkWidget *box, const char *name, void (*func)(user_data_t* user_data, GtkWidget *menu)) +{ + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *option_menu; + GtkWidget *menu; + + hbox=gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(box), hbox); + gtk_box_set_child_packing(GTK_BOX(box), hbox, FALSE, FALSE, 0, GTK_PACK_START); + gtk_widget_show(hbox); + + label=gtk_label_new(name); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + option_menu=gtk_option_menu_new(); + menu=gtk_menu_new(); + (*func)(user_data, menu); + gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), menu); + gtk_box_pack_end(GTK_BOX(hbox), option_menu, FALSE, FALSE, 0); + gtk_widget_show(option_menu); +} + +/****************************************************************************/ +static void create_ctrl_area(user_data_t* user_data, GtkWidget *box) +{ + GtkWidget *frame_vbox; + GtkWidget *frame; + GtkWidget *vbox; + + frame_vbox=gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(box), frame_vbox); + gtk_widget_show(frame_vbox); + + frame = gtk_frame_new("X Axis"); + gtk_container_add(GTK_CONTAINER(frame_vbox), frame); + gtk_widget_show(frame); + + vbox=gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(frame), vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 3); + gtk_box_set_child_packing(GTK_BOX(box), vbox, FALSE, FALSE, 0, GTK_PACK_END); + gtk_widget_show(vbox); + + create_ctrl_menu(user_data, vbox, "Tick interval:", create_tick_interval_menu_items); + create_ctrl_menu(user_data, vbox, "Pixels per tick:", create_pixels_per_tick_menu_items); + + frame = gtk_frame_new("Y Axis"); + gtk_container_add(GTK_CONTAINER(frame_vbox), frame); + gtk_widget_show(frame); + + vbox=gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(frame), vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 3); + gtk_box_set_child_packing(GTK_BOX(box), vbox, FALSE, FALSE, 0, GTK_PACK_END); + gtk_widget_show(vbox); + + create_ctrl_menu(user_data, vbox, "Scale:", create_yscale_max_menu_items); + + return; +} + +/****************************************************************************/ +static void dialog_graph_init_window(user_data_t* user_data) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *bt_close; + + /* create the main window */ + user_data->dlg.dialog_graph.window=window_new(GTK_WINDOW_TOPLEVEL, "I/O Graphs"); + + vbox=gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(user_data->dlg.dialog_graph.window), vbox); + gtk_widget_show(vbox); + + create_draw_area(user_data, vbox); + + hbox=gtk_hbox_new(FALSE, 3); + gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 3); + gtk_box_set_child_packing(GTK_BOX(vbox), hbox, FALSE, FALSE, 0, GTK_PACK_START); + gtk_widget_show(hbox); + + create_filter_area(user_data, hbox); + create_ctrl_area(user_data, hbox); + + dialog_graph_set_title(user_data); + + hbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show(hbox); + + bt_close = g_object_get_data(G_OBJECT(hbox), GTK_STOCK_CLOSE); + window_set_cancel_button(user_data->dlg.dialog_graph.window, bt_close, window_cancel_button_cb); + + g_signal_connect(user_data->dlg.dialog_graph.window, "delete_event", G_CALLBACK(window_delete_event_cb), NULL); + + gtk_widget_show(user_data->dlg.dialog_graph.window); + window_present(user_data->dlg.dialog_graph.window); + +} + + +/****************************************************************************/ +static void on_graph_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + if (user_data->dlg.dialog_graph.window != NULL) { + /* There's already a graph window; reactivate it. */ + reactivate_window(user_data->dlg.dialog_graph.window); + return; + } + + dialog_graph_init_window(user_data); + +} + +/****************************************************************************/ +static void on_goto_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeSelection *selection; + guint fnumber; + + selection = user_data->dlg.selected_list_sel; + + if (selection==NULL) + return; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)){ + gtk_tree_model_get (model, &iter, PACKET_COLUMN, &fnumber, -1); + cf_goto_frame(&cfile, fnumber); + } +} + + +static void draw_stat(user_data_t *user_data); + +/****************************************************************************/ +/* re-dissects all packets */ +static void on_refresh_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + GString *error_string; + + /* remove tap listener */ + protect_thread_critical_region(); + remove_tap_listener(user_data); + unprotect_thread_critical_region(); + + /* register tap listener */ + error_string = register_tap_listener("IAX2", user_data, NULL, + iax2_reset, iax2_packet, iax2_draw); + if (error_string != NULL) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, error_string->str); + g_string_free(error_string, TRUE); + return; + } + + /* retap all packets */ + cf_retap_packets(&cfile, FALSE); + + /* draw statistics info */ + draw_stat(user_data); + +} + +/****************************************************************************/ +static void on_next_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gchar *text; + GtkTreeSelection *selection; + GtkTreePath *path; + + selection = user_data->dlg.selected_list_sel; + + if (selection==NULL) + return; + +try_again: + if (gtk_tree_selection_get_selected (selection, &model, &iter)){ + while (gtk_tree_model_iter_next (model,&iter)) { + gtk_tree_model_get (model, &iter, STATUS_COLUMN, &text, -1); + if (strcmp(text, OK_TEXT) != 0) { + gtk_tree_selection_select_iter (selection, &iter); + path = gtk_tree_model_get_path(model, &iter); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW( user_data->dlg.selected_list), + path, + NULL, FALSE, 0, 0); + gtk_tree_path_free(path); + g_free (text); + return; + } + g_free (text); + } + /* wrap around */ + if (user_data->dlg.number_of_nok>1){ + /* Get the first iter and select it before starting over */ + gtk_tree_model_get_iter_first(model, &iter); + gtk_tree_selection_select_iter (selection, &iter); + goto try_again; + } + } +} + +/****************************************************************************/ +/* when we want to save the information */ +static void save_csv_as_ok_cb(GtkWidget *bt _U_, gpointer fs /*user_data_t *user_data*/ _U_) +{ + gchar *g_dest; + GtkWidget *rev, *forw, *both; + user_data_t *user_data; + + GtkListStore *store; + GtkTreeIter iter; + GtkTreeSelection *selection; + GtkTreeModel *model; + + /* To Hold data from the list row */ + guint packet; /* Packet */ + gfloat delta; /* Delta(ms) */ + gfloat jitter; /* Jitter(ms) */ + gfloat ipbw; /* IP BW(kbps) */ + char * status_str; /* Status */ + char * date_str; /* Date */ + guint length; /* Length */ + + + FILE *fp; + int j; + + g_dest = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs))); + + /* Perhaps the user specified a directory instead of a file. + * Check whether they did. + */ + if (test_for_directory(g_dest) == EISDIR) { + /* It's a directory - set the file selection box to display it. */ + set_last_open_dir(g_dest); + g_free(g_dest); + file_selection_set_current_folder(fs, get_last_open_dir()); + return; + } + + rev = (GtkWidget*)g_object_get_data(G_OBJECT(bt), "reversed_rb"); + forw = (GtkWidget*)g_object_get_data(G_OBJECT(bt), "forward_rb"); + both = (GtkWidget*)g_object_get_data(G_OBJECT(bt), "both_rb"); + user_data = (user_data_t*)g_object_get_data(G_OBJECT(bt), "user_data"); + + if (GTK_TOGGLE_BUTTON(forw)->active || GTK_TOGGLE_BUTTON(both)->active) { + fp = ws_fopen(g_dest, "w"); + if (fp == NULL) { + open_failure_alert_box(g_dest, errno, TRUE); + return; + } + + if (GTK_TOGGLE_BUTTON(both)->active) { + fprintf(fp, "Forward\n"); + if (ferror(fp)) { + write_failure_alert_box(g_dest, errno); + fclose(fp); + return; + } + } + + for(j = 0; j < NUM_COLS; j++) { + if (j == 0) { + fprintf(fp,"%s",titles[j]); + } else { + fprintf(fp,",%s",titles[j]); + } + } + fprintf(fp,"\n"); + if (ferror(fp)) { + write_failure_alert_box(g_dest, errno); + fclose(fp); + return; + } + model = gtk_tree_view_get_model(GTK_TREE_VIEW(user_data->dlg.list_fwd)); + store = GTK_LIST_STORE(model); + if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter) ) { + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(user_data->dlg.list_fwd)); + + while (gtk_tree_model_iter_next (model,&iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, + 0, &packet, + 1, &delta, + 2, &jitter, + 3, &ipbw, + 4, &status_str, + 5, &date_str, + 6, &length, + -1); + fprintf(fp, "%u",packet); + fprintf(fp, ",%.2f", delta); + fprintf(fp, ",%.2f", jitter); + fprintf(fp, ",%.2f", ipbw); + fprintf(fp, ",%s", status_str); + fprintf(fp, ",%s", date_str); + fprintf(fp, ",%u", length); + fprintf(fp,"\n"); + } + if (ferror(fp)) { + write_failure_alert_box(g_dest, errno); + fclose(fp); + return; + } + } + + if (fclose(fp) == EOF) { + write_failure_alert_box(g_dest, errno); + return; + } + } + + if (GTK_TOGGLE_BUTTON(rev)->active || GTK_TOGGLE_BUTTON(both)->active) { + + if (GTK_TOGGLE_BUTTON(both)->active) { + fp = ws_fopen(g_dest, "a"); + if (fp == NULL) { + open_failure_alert_box(g_dest, errno, TRUE); + return; + } + fprintf(fp, "\nReverse\n"); + if (ferror(fp)) { + write_failure_alert_box(g_dest, errno); + fclose(fp); + return; + } + } else { + fp = ws_fopen(g_dest, "w"); + if (fp == NULL) { + open_failure_alert_box(g_dest, errno, TRUE); + return; + } + } + for(j = 0; j < NUM_COLS; j++) { + if (j == 0) { + fprintf(fp,"%s",titles[j]); + } else { + fprintf(fp,",%s",titles[j]); + } + } + fprintf(fp,"\n"); + if (ferror(fp)) { + write_failure_alert_box(g_dest, errno); + fclose(fp); + return; + } + model = gtk_tree_view_get_model(GTK_TREE_VIEW(user_data->dlg.list_rev)); + store = GTK_LIST_STORE(model); + if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter) ) { + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(user_data->dlg.list_rev)); + + while (gtk_tree_model_iter_next (model,&iter)) { + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, + 0, &packet, + 1, &delta, + 2, &jitter, + 3, &ipbw, + 4, &status_str, + 5, &date_str, + 6, &length, + -1); + fprintf(fp, "%u",packet); + fprintf(fp, ",%.2f", delta); + fprintf(fp, ",%.2f", jitter); + fprintf(fp, ",%.2f", ipbw); + fprintf(fp, ",%s", status_str); + fprintf(fp, ",%s", date_str); + fprintf(fp, ",%u", length); + fprintf(fp,"\n"); + } + if (ferror(fp)) { + write_failure_alert_box(g_dest, errno); + fclose(fp); + return; + } + } + if (fclose(fp) == EOF) { + write_failure_alert_box(g_dest, errno); + return; + } + } + + window_destroy(GTK_WIDGET(user_data->dlg.save_csv_as_w)); +} + +static void save_csv_as_destroy_cb(GtkWidget *win _U_, user_data_t *user_data _U_) +{ + user_data->dlg.save_csv_as_w = NULL; +} + +/* when the user wants to save the csv information in a file */ +static void save_csv_as_cb(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + GtkWidget *vertb; + GtkWidget *table1; + GtkWidget *label_format; + GtkWidget *channels_label; + GSList *channels_group = NULL; + GtkWidget *forward_rb; + GtkWidget *reversed_rb; + GtkWidget *both_rb; + GtkWidget *ok_bt; + + if (user_data->dlg.save_csv_as_w != NULL) { + /* There's already a Save CSV info dialog box; reactivate it. */ + reactivate_window(user_data->dlg.save_csv_as_w); + return; + } + + user_data->dlg.save_csv_as_w = gtk_file_selection_new("Wireshark: Save Data As CSV"); + + /* Container for each row of widgets */ + vertb = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vertb), 5); + gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(user_data->dlg.save_csv_as_w)->action_area), + vertb, FALSE, FALSE, 0); + gtk_widget_show (vertb); + + table1 = gtk_table_new (2, 4, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vertb), table1, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 10); + gtk_table_set_row_spacings (GTK_TABLE (table1), 20); + + label_format = gtk_label_new ("Format: Comma Separated Values"); + gtk_widget_show (label_format); + gtk_table_attach (GTK_TABLE (table1), label_format, 0, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + + channels_label = gtk_label_new ("Channels:"); + gtk_widget_show (channels_label); + gtk_table_attach (GTK_TABLE (table1), channels_label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (channels_label), 0, 0.5); + + forward_rb = gtk_radio_button_new_with_label (channels_group, "forward "); + channels_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (forward_rb)); + gtk_widget_show (forward_rb); + gtk_table_attach (GTK_TABLE (table1), forward_rb, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + reversed_rb = gtk_radio_button_new_with_label (channels_group, "reversed"); + channels_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (reversed_rb)); + gtk_widget_show (reversed_rb); + gtk_table_attach (GTK_TABLE (table1), reversed_rb, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + both_rb = gtk_radio_button_new_with_label (channels_group, "both"); + channels_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (both_rb)); + gtk_widget_show (both_rb); + gtk_table_attach (GTK_TABLE (table1), both_rb, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(both_rb), TRUE); + + ok_bt = GTK_FILE_SELECTION(user_data->dlg.save_csv_as_w)->ok_button; + g_object_set_data(G_OBJECT(ok_bt), "forward_rb", forward_rb); + g_object_set_data(G_OBJECT(ok_bt), "reversed_rb", reversed_rb); + g_object_set_data(G_OBJECT(ok_bt), "both_rb", both_rb); + g_object_set_data(G_OBJECT(ok_bt), "user_data", user_data); + g_signal_connect(ok_bt, "clicked", G_CALLBACK(save_csv_as_ok_cb), + user_data->dlg.save_csv_as_w); + + window_set_cancel_button(user_data->dlg.save_csv_as_w, + GTK_FILE_SELECTION(user_data->dlg.save_csv_as_w)->cancel_button, window_cancel_button_cb); + + g_signal_connect(user_data->dlg.save_csv_as_w, "delete_event", G_CALLBACK(window_delete_event_cb), NULL); + g_signal_connect(user_data->dlg.save_csv_as_w, "destroy", + G_CALLBACK(save_csv_as_destroy_cb), user_data); + + gtk_widget_show(user_data->dlg.save_csv_as_w); + window_present(user_data->dlg.save_csv_as_w); +} + + +/****************************************************************************/ +static void save_voice_as_destroy_cb(GtkWidget *win _U_, user_data_t *user_data _U_) +{ + /* Note that we no longer have a Save voice info dialog box. */ + user_data->dlg.save_voice_as_w = NULL; +} + +/****************************************************************************/ +/* here we save it into a file that user specified */ +/* XXX what about endians here? could go something wrong? */ +static gboolean copy_file(gchar *dest, gint channels, gint format, user_data_t *user_data) +{ + int to_fd, forw_fd, rev_fd, fread = 0, rread = 0, fwritten, rwritten; + gchar f_pd[1] = {0}; + gchar r_pd[1] = {0}; + gint16 sample; + gchar pd[4]; + guint32 f_write_silence = 0; + guint32 r_write_silence = 0; + progdlg_t *progbar; + guint32 progbar_count, progbar_quantum, progbar_nextstep = 0, count = 0; + gboolean stop_flag = FALSE; + size_t nchars; + + forw_fd = ws_open(user_data->f_tempname, O_RDONLY | O_BINARY, 0000 /* no creation so don't matter */); + if (forw_fd < 0) + return FALSE; + rev_fd = ws_open(user_data->r_tempname, O_RDONLY | O_BINARY, 0000 /* no creation so don't matter */); + if (rev_fd < 0) { + ws_close(forw_fd); + return FALSE; + } + + /* open file for saving */ + to_fd = ws_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (to_fd < 0) { + ws_close(forw_fd); + ws_close(rev_fd); + return FALSE; + } + + progbar = create_progress_dlg("Saving voice in a file", dest, TRUE, &stop_flag); + + if (format == SAVE_AU_FORMAT) /* au format */ + { + /* First we write the .au header. XXX Hope this is endian independant */ + /* the magic word 0x2e736e64 == .snd */ + phtonl(pd, 0x2e736e64); + nchars=ws_write(to_fd, pd, 4); + /* header offset == 24 bytes */ + phtonl(pd, 24); + nchars=ws_write(to_fd, pd, 4); + /* total length, it is permited to set this to 0xffffffff */ + phtonl(pd, -1); + nchars=ws_write(to_fd, pd, 4); + /* encoding format == 16-bit linear PCM */ + phtonl(pd, 3); + nchars=ws_write(to_fd, pd, 4); + /* sample rate == 8000 Hz */ + phtonl(pd, 8000); + nchars=ws_write(to_fd, pd, 4); + /* channels == 1 */ + phtonl(pd, 1); + nchars=ws_write(to_fd, pd, 4); + + + switch (channels) { + /* only forward direction */ + case SAVE_FORWARD_DIRECTION_MASK: { + progbar_count = user_data->forward.saveinfo.count; + progbar_quantum = user_data->forward.saveinfo.count/100; + while ((fread = read(forw_fd, f_pd, 1)) > 0) { + if(stop_flag) + break; + if((count > progbar_nextstep) && (count <= progbar_count)) { + update_progress_dlg(progbar, + (gfloat) count/progbar_count, "Saving"); + progbar_nextstep = progbar_nextstep + progbar_quantum; + } + count++; + + if (user_data->forward.statinfo.pt == AST_FORMAT_ULAW){ + sample = ulaw2linear(*f_pd); + phtons(pd, sample); + } + else if(user_data->forward.statinfo.pt == AST_FORMAT_ALAW){ + sample = alaw2linear(*f_pd); + phtons(pd, sample); + } + else{ + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + + fwritten = ws_write(to_fd, pd, 2); + if ((fwritten < 2) || (fwritten < 0) || (fread < 0)) { + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + } + break; + } + /* only reversed direction */ + case SAVE_REVERSE_DIRECTION_MASK: { + progbar_count = user_data->reversed.saveinfo.count; + progbar_quantum = user_data->reversed.saveinfo.count/100; + while ((rread = read(rev_fd, r_pd, 1)) > 0) { + if(stop_flag) + break; + if((count > progbar_nextstep) && (count <= progbar_count)) { + update_progress_dlg(progbar, + (gfloat) count/progbar_count, "Saving"); + progbar_nextstep = progbar_nextstep + progbar_quantum; + } + count++; + + if (user_data->reversed.statinfo.pt == AST_FORMAT_ULAW){ + sample = ulaw2linear(*r_pd); + phtons(pd, sample); + } + else if(user_data->reversed.statinfo.pt == AST_FORMAT_ALAW){ + sample = alaw2linear(*r_pd); + phtons(pd, sample); + } + else{ + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + + rwritten = ws_write(to_fd, pd, 2); + if ((rwritten < 2) || (rwritten < 0) || (rread < 0)) { + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + } + break; + } + /* both directions */ + case SAVE_BOTH_DIRECTION_MASK: { + (user_data->forward.saveinfo.count > user_data->reversed.saveinfo.count) ? + (progbar_count = user_data->forward.saveinfo.count) : + (progbar_count = user_data->reversed.saveinfo.count); + progbar_quantum = progbar_count/100; + /* since conversation in one way can start later than in the other one, + * we have to write some silence information for one channel */ + if (user_data->forward.statinfo.start_time > user_data->reversed.statinfo.start_time) { + f_write_silence = (guint32) + ((user_data->forward.statinfo.start_time-user_data->reversed.statinfo.start_time)*8000); + } + else if (user_data->forward.statinfo.start_time < user_data->reversed.statinfo.start_time) { + r_write_silence = (guint32) + ((user_data->reversed.statinfo.start_time-user_data->forward.statinfo.start_time)*8000); + } + for(;;) { + if(stop_flag) + break; + if((count > progbar_nextstep) && (count <= progbar_count)) { + update_progress_dlg(progbar, + (gfloat) count/progbar_count, "Saving"); + progbar_nextstep = progbar_nextstep + progbar_quantum; + } + count++; + if(f_write_silence > 0) { + rread = read(rev_fd, r_pd, 1); + switch (user_data->forward.statinfo.reg_pt) { + case AST_FORMAT_ULAW: + *f_pd = SILENCE_PCMU; + break; + case AST_FORMAT_ALAW: + *f_pd = SILENCE_PCMA; + break; + } + fread = 1; + f_write_silence--; + } + else if(r_write_silence > 0) { + fread = read(forw_fd, f_pd, 1); + switch (user_data->reversed.statinfo.reg_pt) { + case AST_FORMAT_ULAW: + *r_pd = SILENCE_PCMU; + break; + case AST_FORMAT_ALAW: + *r_pd = SILENCE_PCMA; + break; + } + rread = 1; + r_write_silence--; + } + else { + fread = read(forw_fd, f_pd, 1); + rread = read(rev_fd, r_pd, 1); + } + if ((rread == 0) && (fread == 0)) + break; + if ((user_data->forward.statinfo.pt == AST_FORMAT_ULAW) && (user_data->reversed.statinfo.pt == AST_FORMAT_ULAW)){ + sample = (ulaw2linear(*r_pd) + ulaw2linear(*f_pd)) / 2; + phtons(pd, sample); + } + else if((user_data->forward.statinfo.pt == AST_FORMAT_ALAW) && (user_data->reversed.statinfo.pt == AST_FORMAT_ALAW)){ + sample = (alaw2linear(*r_pd) + alaw2linear(*f_pd)) / 2; + phtons(pd, sample); + } + else + { + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + + + rwritten = ws_write(to_fd, pd, 2); + if ((rwritten < 2) || (rread < 0) || (fread < 0)) { + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + } + } + } + } + else if (format == SAVE_RAW_FORMAT) /* raw format */ + { + int fd; + switch (channels) { + /* only forward direction */ + case SAVE_FORWARD_DIRECTION_MASK: { + progbar_count = user_data->forward.saveinfo.count; + progbar_quantum = user_data->forward.saveinfo.count/100; + fd = forw_fd; + break; + } + /* only reversed direction */ + case SAVE_REVERSE_DIRECTION_MASK: { + progbar_count = user_data->reversed.saveinfo.count; + progbar_quantum = user_data->reversed.saveinfo.count/100; + fd = rev_fd; + break; + } + default: { + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + } + + + + /* XXX how do you just copy the file? */ + while ((rread = read(fd, pd, 1)) > 0) { + if(stop_flag) + break; + if((count > progbar_nextstep) && (count <= progbar_count)) { + update_progress_dlg(progbar, + (gfloat) count/progbar_count, "Saving"); + progbar_nextstep = progbar_nextstep + progbar_quantum; + } + count++; + + rwritten = ws_write(to_fd, pd, 1); + + if ((rwritten < rread) || (rwritten < 0) || (rread < 0)) { + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + destroy_progress_dlg(progbar); + return FALSE; + } + } + } + + destroy_progress_dlg(progbar); + ws_close(forw_fd); + ws_close(rev_fd); + ws_close(to_fd); + return TRUE; +} + + +/****************************************************************************/ +/* the user wants to save in a file */ +/* XXX support for different formats is currently commented out */ +static void save_voice_as_ok_cb(GtkWidget *ok_bt _U_, gpointer fs _U_) +{ + gchar *g_dest; + /*GtkWidget *wav, *sw;*/ + GtkWidget *au, *raw; + GtkWidget *rev, *forw, *both; + user_data_t *user_data; + gint channels , format; + + g_dest = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (fs))); + + /* Perhaps the user specified a directory instead of a file. + Check whether they did. */ + if (test_for_directory(g_dest) == EISDIR) { + /* It's a directory - set the file selection box to display it. */ + set_last_open_dir(g_dest); + g_free(g_dest); + file_selection_set_current_folder(fs, get_last_open_dir()); + return; + } + + /*wav = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "wav_rb"); + sw = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "sw_rb");*/ + au = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "au_rb"); + raw = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "raw_rb"); + rev = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "reversed_rb"); + forw = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "forward_rb"); + both = (GtkWidget *)g_object_get_data(G_OBJECT(ok_bt), "both_rb"); + user_data = (user_data_t *)g_object_get_data(G_OBJECT(ok_bt), "user_data"); + + /* XXX user clicks the ok button, but we know we can't save the voice info because f.e. + * we don't support that codec. So we pop up a warning. Maybe it would be better to + * disable the ok button or disable the buttons for direction if only one is not ok. The + * problem is if we open the save voice dialog and then click the refresh button and maybe + * the state changes, so we can't save anymore. In this case we should be able to update + * the buttons. For now it is easier if we put the warning when the ok button is pressed. + */ + + /* we can not save in both directions */ + if ((user_data->forward.saveinfo.saved == FALSE) && (user_data->reversed.saveinfo.saved == FALSE) && (GTK_TOGGLE_BUTTON (both)->active)) { + /* there are many combinations here, we just exit when first matches */ + if ((user_data->forward.saveinfo.error_type == TAP_RTP_WRONG_CODEC) || + (user_data->reversed.saveinfo.error_type == TAP_RTP_WRONG_CODEC)) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: Unsupported codec!"); + else if ((user_data->forward.saveinfo.error_type == TAP_RTP_WRONG_LENGTH) || + (user_data->reversed.saveinfo.error_type == TAP_RTP_WRONG_LENGTH)) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: Wrong length of captured packets!"); + else if ((user_data->forward.saveinfo.error_type == TAP_RTP_SHORT_FRAME) || + (user_data->reversed.saveinfo.error_type == TAP_RTP_SHORT_FRAME)) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: Not all data in all packets was captured!"); + else + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: File I/O problem!"); + return; + } + /* we can not save forward direction */ + else if ((user_data->forward.saveinfo.saved == FALSE) && ((GTK_TOGGLE_BUTTON (forw)->active) || + (GTK_TOGGLE_BUTTON (both)->active))) { + if (user_data->forward.saveinfo.error_type == TAP_RTP_WRONG_CODEC) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save forward direction in a file: Unsupported codec!"); + else if (user_data->forward.saveinfo.error_type == TAP_RTP_WRONG_LENGTH) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save forward direction in a file: Wrong length of captured packets!"); + else if (user_data->forward.saveinfo.error_type == TAP_RTP_SHORT_FRAME) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save forward direction in a file: Not all data in all packets was captured!"); + else + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save forward direction in a file: File I/O problem!"); + return; + } + /* we can not save reversed direction */ + else if ((user_data->reversed.saveinfo.saved == FALSE) && ((GTK_TOGGLE_BUTTON (rev)->active) || + (GTK_TOGGLE_BUTTON (both)->active))) { + if (user_data->reversed.saveinfo.error_type == TAP_RTP_WRONG_CODEC) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save reversed direction in a file: Unsupported codec!"); + else if (user_data->reversed.saveinfo.error_type == TAP_RTP_WRONG_LENGTH) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save reversed direction in a file: Wrong length of captured packets!"); + else if (user_data->reversed.saveinfo.error_type == TAP_RTP_SHORT_FRAME) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save reversed direction in a file: Not all data in all packets was captured!"); + else if (user_data->reversed.saveinfo.error_type == TAP_RTP_NO_DATA) + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save reversed direction in a file: No IAX2 data!"); + else + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save reversed direction in a file: File I/O problem!"); + return; + } + + /*if (GTK_TOGGLE_BUTTON (wav)->active) + format = SAVE_WAV_FORMAT; + else */if (GTK_TOGGLE_BUTTON (au)->active) + format = SAVE_AU_FORMAT; + /*else if (GTK_TOGGLE_BUTTON (sw)->active) + format = SAVE_SW_FORMAT;*/ + else if (GTK_TOGGLE_BUTTON (raw)->active) + format = SAVE_RAW_FORMAT; + else + format = SAVE_NONE_FORMAT; + + if (GTK_TOGGLE_BUTTON (rev)->active) + channels = SAVE_REVERSE_DIRECTION_MASK; + else if (GTK_TOGGLE_BUTTON (both)->active) + channels = SAVE_BOTH_DIRECTION_MASK; + else + channels = SAVE_FORWARD_DIRECTION_MASK; + + /* direction/format validity*/ + if (format == SAVE_AU_FORMAT) + { + /* make sure streams are alaw/ulaw */ + if ((channels & SAVE_FORWARD_DIRECTION_MASK) && (user_data->forward.statinfo.pt != AST_FORMAT_ALAW) && (user_data->forward.statinfo.pt != AST_FORMAT_ULAW)){ + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: saving in au format supported only for alaw/ulaw streams"); + return; + } + if ((channels & SAVE_REVERSE_DIRECTION_MASK) && (user_data->reversed.statinfo.pt != AST_FORMAT_ALAW) && (user_data->reversed.statinfo.pt != AST_FORMAT_ULAW)){ + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: saving in au format supported only for alaw/ulaw streams"); + return; + } + /* make sure pt's don't differ */ + if ((channels == SAVE_BOTH_DIRECTION_MASK) && (user_data->forward.statinfo.pt != user_data->reversed.statinfo.pt)){ + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: Forward and reverse direction differ in type"); + return; + } + } + else if (format == SAVE_RAW_FORMAT) + { + /* can't save raw in both directions */ + if (channels == SAVE_BOTH_DIRECTION_MASK){ + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: Unable to save raw data in both directions"); + return; + } + } + else + { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Can't save in a file: Invalid save format"); + return; + } + + if(!copy_file(g_dest, channels, format, user_data)) { + /* XXX - report the error type! */ + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "An error occurred while saving voice in a file!"); + return; + } + + window_destroy(GTK_WIDGET(user_data->dlg.save_voice_as_w)); +} + +/****************************************************************************/ +/* when the user wants to save the voice information in a file */ +/* XXX support for different formats is currently commented out */ +static void on_save_bt_clicked(GtkWidget *bt _U_, user_data_t *user_data _U_) +{ + GtkWidget *vertb; + GtkWidget *table1; + GtkWidget *label_format; + GtkWidget *channels_label; + GSList *format_group = NULL; + GSList *channels_group = NULL; + GtkWidget *forward_rb; + GtkWidget *reversed_rb; + GtkWidget *both_rb; + /*GtkWidget *wav_rb; GtkWidget *sw_rb;*/ + GtkWidget *au_rb; + GtkWidget *raw_rb; + GtkWidget *ok_bt; + + /* if we can't save in a file: wrong codec, cut packets or other errors */ + /* shold the error arise here or later when you click ok button ? + * if we do it here, then we must disable the refresh button, so we don't do it here */ + + if (user_data->dlg.save_voice_as_w != NULL) { + /* There's already a Save voice info dialog box; reactivate it. */ + reactivate_window(user_data->dlg.save_voice_as_w); + return; + } + + /* XXX - use file_selection from dlg_utils instead! */ + user_data->dlg.save_voice_as_w = gtk_file_selection_new("Wireshark: Save Payload As ..."); + + /* Container for each row of widgets */ + vertb = gtk_vbox_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vertb), 5); + gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(user_data->dlg.save_voice_as_w)->action_area), + vertb, FALSE, FALSE, 0); + gtk_widget_show (vertb); + + table1 = gtk_table_new (2, 4, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vertb), table1, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 10); + gtk_table_set_row_spacings (GTK_TABLE (table1), 20); + + /*label_format = gtk_label_new ("Format: .au (ulaw, 8 bit, 8000 Hz, mono) "); + gtk_widget_show (label_format); + gtk_table_attach (GTK_TABLE (table1), label_format, 0, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0);*/ + + label_format = gtk_label_new ("Format: "); + gtk_widget_show (label_format); + gtk_table_attach (GTK_TABLE (table1), label_format, 0, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_misc_set_alignment (GTK_MISC (label_format), 0, 0.5); + + raw_rb = gtk_radio_button_new_with_label (format_group, ".raw"); + format_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (raw_rb)); + gtk_widget_show (raw_rb); + gtk_table_attach (GTK_TABLE (table1), raw_rb, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + + au_rb = gtk_radio_button_new_with_label (format_group, ".au"); + format_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (au_rb)); + gtk_widget_show (au_rb); + gtk_table_attach (GTK_TABLE (table1), au_rb, 3, 4, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + /* we support .au - ulaw*/ + /* wav_rb = gtk_radio_button_new_with_label (format_group, ".wav"); + format_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (wav_rb)); + gtk_widget_show (wav_rb); + gtk_table_attach (GTK_TABLE (table1), wav_rb, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + sw_rb = gtk_radio_button_new_with_label (format_group, "8 kHz, 16 bit "); + format_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (sw_rb)); + gtk_widget_show (sw_rb); + gtk_table_attach (GTK_TABLE (table1), sw_rb, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + au_rb = gtk_radio_button_new_with_label (format_group, ".au"); + format_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (au_rb)); + gtk_widget_show (au_rb); + gtk_table_attach (GTK_TABLE (table1), au_rb, 3, 4, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + */ + + + channels_label = gtk_label_new ("Channels:"); + gtk_widget_show (channels_label); + gtk_table_attach (GTK_TABLE (table1), channels_label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (channels_label), 0, 0.5); + + forward_rb = gtk_radio_button_new_with_label (channels_group, "forward "); + channels_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (forward_rb)); + gtk_widget_show (forward_rb); + gtk_table_attach (GTK_TABLE (table1), forward_rb, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + reversed_rb = gtk_radio_button_new_with_label (channels_group, "reversed"); + channels_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (reversed_rb)); + gtk_widget_show (reversed_rb); + gtk_table_attach (GTK_TABLE (table1), reversed_rb, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + both_rb = gtk_radio_button_new_with_label (channels_group, "both"); + channels_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (both_rb)); + gtk_widget_show (both_rb); + gtk_table_attach (GTK_TABLE (table1), both_rb, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(both_rb), TRUE); + + /* if one direction is nok we don't allow saving + XXX this is not ok since the user can click the refresh button and cause changes + but we can not update this window. So we move all the decision on the time the ok + button is clicked + if (user_data->forward.saved == FALSE) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(reversed_rb), TRUE); + gtk_widget_set_sensitive(forward_rb, FALSE); + gtk_widget_set_sensitive(both_rb, FALSE); + } + else if (user_data->reversed.saved == FALSE) { + gtk_widget_set_sensitive(reversed_rb, FALSE); + gtk_widget_set_sensitive(both_rb, FALSE); + } + */ + + ok_bt = GTK_FILE_SELECTION(user_data->dlg.save_voice_as_w)->ok_button; + /*g_object_set_data(G_OBJECT(ok_bt), "wav_rb", wav_rb);*/ + g_object_set_data(G_OBJECT(ok_bt), "au_rb", au_rb); + /*g_object_set_data(G_OBJECT(ok_bt), "sw_rb", sw_rb);*/ + g_object_set_data(G_OBJECT(ok_bt), "raw_rb", raw_rb); + g_object_set_data(G_OBJECT(ok_bt), "forward_rb", forward_rb); + g_object_set_data(G_OBJECT(ok_bt), "reversed_rb", reversed_rb); + g_object_set_data(G_OBJECT(ok_bt), "both_rb", both_rb); + g_object_set_data(G_OBJECT(ok_bt), "user_data", user_data); + g_signal_connect(ok_bt, "clicked", G_CALLBACK(save_voice_as_ok_cb), + user_data->dlg.save_voice_as_w); + + window_set_cancel_button(user_data->dlg.save_voice_as_w, + GTK_FILE_SELECTION(user_data->dlg.save_voice_as_w)->cancel_button, window_cancel_button_cb); + + g_signal_connect(user_data->dlg.save_voice_as_w, "delete_event", + G_CALLBACK(window_delete_event_cb), NULL); + g_signal_connect(user_data->dlg.save_voice_as_w, "destroy", + G_CALLBACK(save_voice_as_destroy_cb), user_data); + + gtk_widget_show(user_data->dlg.save_voice_as_w); + window_present(user_data->dlg.save_voice_as_w); +} + + +/****************************************************************************/ +/* when we are finished with redisection, we add the label for the statistic */ +static void draw_stat(user_data_t *user_data) +{ + gchar label_max[200]; + + g_snprintf(label_max, 199, "Total IAX2 packets = %u Max delta = %f sec at packet no. %u", + user_data->forward.statinfo.total_nr, + user_data->forward.statinfo.max_delta, user_data->forward.statinfo.max_nr); + + gtk_label_set_text(GTK_LABEL(user_data->dlg.label_stats_fwd), label_max); + + g_snprintf(label_max, 199, "Total IAX2 packets = %u Max delta = %f sec at packet no. %u", + user_data->reversed.statinfo.total_nr, + user_data->reversed.statinfo.max_delta, user_data->reversed.statinfo.max_nr); + + gtk_label_set_text(GTK_LABEL(user_data->dlg.label_stats_rev), label_max); + + return ; +} + + + +/****************************************************************************/ +/* append a line to list */ +static void add_to_list(GtkWidget *list, user_data_t * user_data, guint32 number, + double delta, double jitter, double bandwidth, gchar *status, + gchar *timeStr, guint32 pkt_len, gchar *color_str, guint32 flags) +{ + GtkListStore *list_store; + + if (strcmp(status, OK_TEXT) != 0) { + user_data->dlg.number_of_nok++; + } + + list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW (list))); /* Get store */ + + /* Creates a new row at position. iter will be changed to point to this new row. + * If position is larger than the number of rows on the list, then the new row will be appended to the list. + * The row will be filled with the values given to this function. + * : + * should generally be preferred when inserting rows in a sorted list store. + */ +#if GTK_CHECK_VERSION(2,6,0) + gtk_list_store_insert_with_values( list_store , &user_data->dlg.iter, G_MAXINT, +#else + gtk_list_store_append (list_store, &user_data->dlg.iter); + gtk_list_store_set (list_store, &user_data->dlg.iter, +#endif + PACKET_COLUMN, number, + DELTA_COLUMN, delta, + JITTER_COLUMN, jitter, + IPBW_COLUMN, bandwidth, + STATUS_COLUMN, (char *)status, + DATE_COLUMN, (char *)timeStr, + LENGTH_COLUMN, pkt_len, + FOREGROUND_COLOR_COL, NULL, + BACKGROUND_COLOR_COL, (char *)color_str, + -1); + + if(flags & STAT_FLAG_FIRST){ + /* Set first row as active */ + gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(list)), &user_data->dlg.iter); + } +} + +/**************************************************************************** +* Functions needed to present values from the list +*/ + +/* Present floats with two decimals */ +void +iax2_float_data_func (GtkTreeViewColumn *column _U_, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) + { + gfloat float_val; + gchar buf[20]; + char *savelocale; + + /* the col to get data from is in userdata */ + gint float_col = GPOINTER_TO_INT(user_data); + + gtk_tree_model_get(model, iter, float_col, &float_val, -1); + + /* save the current locale */ + savelocale = setlocale(LC_NUMERIC, NULL); + /* switch to "C" locale to avoid problems with localized decimal separators + * in g_snprintf("%f") functions + */ + setlocale(LC_NUMERIC, "C"); + + g_snprintf(buf, sizeof(buf), "%.2f", float_val); + /* restore previous locale setting */ + setlocale(LC_NUMERIC, savelocale); + + g_object_set(renderer, "text", buf, NULL); + } + + +/* Create list */ +static +GtkWidget* create_list(user_data_t* user_data) +{ + + GtkListStore *list_store; + GtkWidget *list; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeSortable *sortable; + GtkTreeView *list_view; + GtkTreeSelection *selection; + + /* Create the store */ + list_store = gtk_list_store_new(N_COLUMN, /* Total number of columns XXX*/ + G_TYPE_UINT, /* Packet */ + G_TYPE_FLOAT, /* Delta(ms) */ + G_TYPE_FLOAT, /* Jitter(ms) */ + G_TYPE_FLOAT, /* IP BW(kbps) */ + G_TYPE_STRING, /* Status */ + G_TYPE_STRING, /* Date */ + G_TYPE_UINT, /* Length */ + G_TYPE_STRING, /* Foreground color */ + G_TYPE_STRING); /* Background color */ + + /* Create a view */ + list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store)); + + list_view = GTK_TREE_VIEW(list); + sortable = GTK_TREE_SORTABLE(list_store); + +#if GTK_CHECK_VERSION(2,6,0) + /* Speed up the list display */ + gtk_tree_view_set_fixed_height_mode(list_view, TRUE); +#endif + + /* Setup the sortable columns */ + gtk_tree_sortable_set_sort_column_id(sortable, PACKET_COLUMN, GTK_SORT_ASCENDING); + gtk_tree_view_set_headers_clickable(list_view, FALSE); + + /* The view now holds a reference. We can get rid of our own reference */ + g_object_unref (G_OBJECT (list_store)); + + /* + * Create the first column packet, associating the "text" attribute of the + * cell_renderer to the first column of the model + */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Packet", renderer, + "text", PACKET_COLUMN, + "foreground", FOREGROUND_COLOR_COL, + "background", BACKGROUND_COLOR_COL, + NULL); + gtk_tree_view_column_set_sort_column_id(column, PACKET_COLUMN); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(column, 100); + + /* Add the column to the view. */ + gtk_tree_view_append_column (list_view, column); + + /* Second column.. Delta(ms). */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Delta(ms)", renderer, + "text", DELTA_COLUMN, + "foreground", FOREGROUND_COLOR_COL, + "background", BACKGROUND_COLOR_COL, + NULL); + + gtk_tree_view_column_set_cell_data_func(column, renderer, iax2_float_data_func, + GINT_TO_POINTER(DELTA_COLUMN), NULL); + + gtk_tree_view_column_set_sort_column_id(column, DELTA_COLUMN); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(column, 100); + gtk_tree_view_append_column (list_view, column); + + /* Third column.. Jitter(ms). */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Jitter(ms)", renderer, + "text", JITTER_COLUMN, + "foreground", FOREGROUND_COLOR_COL, + "background", BACKGROUND_COLOR_COL, + NULL); + + gtk_tree_view_column_set_cell_data_func(column, renderer, iax2_float_data_func, + GINT_TO_POINTER(JITTER_COLUMN), NULL); + + gtk_tree_view_column_set_sort_column_id(column, JITTER_COLUMN); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(column, 100); + gtk_tree_view_append_column (list_view, column); + + /* Fourth column.. IP BW(kbps). */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("IP BW(kbps)", renderer, + "text", IPBW_COLUMN, + "foreground", FOREGROUND_COLOR_COL, + "background", BACKGROUND_COLOR_COL, + NULL); + + gtk_tree_view_column_set_cell_data_func(column, renderer, iax2_float_data_func, + GINT_TO_POINTER(IPBW_COLUMN), NULL); + + gtk_tree_view_column_set_sort_column_id(column, IPBW_COLUMN); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(column, 100); + gtk_tree_view_append_column (list_view, column); + + /* Fifth column.. Status. */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ( "Status", renderer, + "text", STATUS_COLUMN, + "foreground", FOREGROUND_COLOR_COL, + "background", BACKGROUND_COLOR_COL, + NULL); + gtk_tree_view_column_set_sort_column_id(column, STATUS_COLUMN); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(column, 100); + gtk_tree_view_append_column (list_view, column); + + /* Sixth column.. Length. */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Length", renderer, + "text", LENGTH_COLUMN, + "foreground", FOREGROUND_COLOR_COL, + "background", BACKGROUND_COLOR_COL, + NULL); + + + gtk_tree_view_column_set_sort_column_id(column, LENGTH_COLUMN); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(column, 100); + gtk_tree_view_append_column (list_view, column); + + /* Now enable the sorting of each column */ + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(list_view), TRUE); + gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(list_view), TRUE); + + /* Setup the selection handler */ + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); + + g_signal_connect (G_OBJECT (selection), "changed", /* select_row */ + G_CALLBACK (on_list_select_row), + user_data); + return list; +} + + + +/****************************************************************************/ +/* Create the dialog box with all widgets */ +static void create_iax2_dialog(user_data_t* user_data) +{ + GtkWidget *window = NULL; + GtkWidget *list_fwd; + GtkWidget *list_rev; + GtkWidget *label_stats_fwd; + GtkWidget *label_stats_rev; + GtkWidget *notebook; + + GtkWidget *main_vb, *page, *page_r; + GtkWidget *label; + GtkWidget *scrolled_window, *scrolled_window_r/*, *frame, *text, *label4, *page_help*/; + GtkWidget *box4, *voice_bt, *refresh_bt, *goto_bt, *close_bt, *csv_bt, *next_bt; +#ifdef USE_CONVERSATION_GRAPH + GtkWidget *graph_bt; +#endif + GtkWidget *graph_bt; + gchar label_forward[150]; + gchar label_reverse[150]; + + gchar str_ip_src[16]; + gchar str_ip_dst[16]; + + window = window_new(GTK_WINDOW_TOPLEVEL, "Wireshark: IAX2 Stream Analysis"); + gtk_window_set_default_size(GTK_WINDOW(window), 700, 400); + + /* Container for each row of widgets */ + main_vb = gtk_vbox_new(FALSE, 2); + gtk_container_set_border_width(GTK_CONTAINER(main_vb), 2); + gtk_container_add(GTK_CONTAINER(window), main_vb); + gtk_widget_show(main_vb); + + /* Notebooks... */ + g_strlcpy(str_ip_src, get_addr_name(&(user_data->ip_src_fwd)), 16); + g_strlcpy(str_ip_dst, get_addr_name(&(user_data->ip_dst_fwd)), 16); + + g_snprintf(label_forward, 149, + "Analysing stream from %s port %u to %s port %u ", + str_ip_src, user_data->port_src_fwd, str_ip_dst, user_data->port_dst_fwd); + + + g_strlcpy(str_ip_src, get_addr_name(&(user_data->ip_src_rev)), 16); + g_strlcpy(str_ip_dst, get_addr_name(&(user_data->ip_dst_rev)), 16); + + g_snprintf(label_reverse, 149, + "Analysing stream from %s port %u to %s port %u ", + str_ip_src, user_data->port_src_rev, str_ip_dst, user_data->port_dst_rev); + + /* Start a notebook for flipping between sets of changes */ + notebook = gtk_notebook_new(); + gtk_container_add(GTK_CONTAINER(main_vb), notebook); + g_object_set_data(G_OBJECT(window), "notebook", notebook); + + user_data->dlg.notebook_signal_id = + g_signal_connect(notebook, "switch_page", G_CALLBACK(on_notebook_switch_page), user_data); + + /* page for forward connection */ + page = gtk_vbox_new(FALSE, 8); + gtk_container_set_border_width(GTK_CONTAINER(page), 8); + + /* direction label */ + label = gtk_label_new(label_forward); + gtk_box_pack_start(GTK_BOX(page), label, FALSE, FALSE, 0); + + /* place for some statistics */ + label_stats_fwd = gtk_label_new("\n"); + gtk_box_pack_end(GTK_BOX(page), label_stats_fwd, FALSE, FALSE, 0); + + /* scrolled window */ + scrolled_window = scrolled_window_new(NULL, NULL); + + /* packet list */ + list_fwd = create_list(user_data); + gtk_widget_show(list_fwd); + gtk_container_add(GTK_CONTAINER(scrolled_window), list_fwd); + gtk_box_pack_start(GTK_BOX(page), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show(scrolled_window); + + /* tab */ + label = gtk_label_new(" Forward Direction "); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label); + + /* same page for reversed connection */ + page_r = gtk_vbox_new(FALSE, 8); + gtk_container_set_border_width(GTK_CONTAINER(page_r), 8); + label = gtk_label_new(label_reverse); + gtk_box_pack_start(GTK_BOX(page_r), label, FALSE, FALSE, 0); + label_stats_rev = gtk_label_new("\n"); + gtk_box_pack_end(GTK_BOX(page_r), label_stats_rev, FALSE, FALSE, 0); + + scrolled_window_r = scrolled_window_new(NULL, NULL); + + list_rev = create_list(user_data); + gtk_widget_show(list_rev); + gtk_container_add(GTK_CONTAINER(scrolled_window_r), list_rev); + gtk_box_pack_start(GTK_BOX(page_r), scrolled_window_r, TRUE, TRUE, 0); + gtk_widget_show(scrolled_window_r); + + label = gtk_label_new(" Reversed Direction "); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page_r, label); + + /* page for help&about or future + page_help = gtk_hbox_new(FALSE, 5); + label = gtk_label_new(" Future "); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page_help, label); + frame = gtk_frame_new(""); + text = gtk_label_new("\n\nMaybe some more statistics: delta and jitter distribution,..."); + gtk_label_set_justify(GTK_LABEL(text), GTK_JUSTIFY_LEFT); + gtk_container_add(GTK_CONTAINER(frame), text); + gtk_container_set_border_width(GTK_CONTAINER(frame), 20); + gtk_box_pack_start(GTK_BOX(page_help), frame, TRUE, TRUE, 0); + */ + + /* show all notebooks */ + gtk_widget_show_all(notebook); + + /* buttons */ + box4 = gtk_hbutton_box_new(); + gtk_box_pack_start(GTK_BOX(main_vb), box4, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(box4), 10); + gtk_button_box_set_layout(GTK_BUTTON_BOX (box4), GTK_BUTTONBOX_EDGE); + gtk_box_set_spacing(GTK_BOX (box4), 0); + gtk_button_box_set_child_ipadding(GTK_BUTTON_BOX (box4), 4, 0); + gtk_widget_show(box4); + + voice_bt = gtk_button_new_with_label("Save payload..."); + gtk_container_add(GTK_CONTAINER(box4), voice_bt); + gtk_widget_show(voice_bt); + g_signal_connect(voice_bt, "clicked", G_CALLBACK(on_save_bt_clicked), user_data); + + csv_bt = gtk_button_new_with_label("Save as CSV..."); + gtk_container_add(GTK_CONTAINER(box4), csv_bt); + gtk_widget_show(csv_bt); + g_signal_connect(csv_bt, "clicked", G_CALLBACK(save_csv_as_cb), user_data); + + refresh_bt = gtk_button_new_from_stock(GTK_STOCK_REFRESH); + gtk_container_add(GTK_CONTAINER(box4), refresh_bt); + gtk_widget_show(refresh_bt); + g_signal_connect(refresh_bt, "clicked", G_CALLBACK(on_refresh_bt_clicked), user_data); + + goto_bt = gtk_button_new_from_stock(GTK_STOCK_JUMP_TO); + gtk_container_add(GTK_CONTAINER(box4), goto_bt); + gtk_widget_show(goto_bt); + g_signal_connect(goto_bt, "clicked", G_CALLBACK(on_goto_bt_clicked), user_data); + + graph_bt = gtk_button_new_with_label("Graph"); + gtk_container_add(GTK_CONTAINER(box4), graph_bt); + gtk_widget_show(graph_bt); + g_signal_connect(graph_bt, "clicked", G_CALLBACK(on_graph_bt_clicked), user_data); + + +#ifdef USE_CONVERSATION_GRAPH + graph_bt = gtk_button_new_with_label("Graph"); + gtk_container_add(GTK_CONTAINER(box4), graph_bt); + gtk_widget_show(graph_bt); + g_signal_connect(graph_bt, "clicked", G_CALLBACK(on_graph_bt_clicked), user_data); +#endif + + next_bt = gtk_button_new_with_label("Next non-Ok"); + gtk_container_add(GTK_CONTAINER(box4), next_bt); + gtk_widget_show(next_bt); + g_signal_connect(next_bt, "clicked", G_CALLBACK(on_next_bt_clicked), user_data); + + close_bt = gtk_button_new_from_stock(GTK_STOCK_CLOSE); + gtk_container_add(GTK_CONTAINER(box4), close_bt); + GTK_WIDGET_SET_FLAGS(close_bt, GTK_CAN_DEFAULT); + gtk_widget_show(close_bt); + window_set_cancel_button(window, close_bt, window_cancel_button_cb); + + g_signal_connect(window, "delete_event", G_CALLBACK(window_delete_event_cb), NULL); + g_signal_connect(window, "destroy", G_CALLBACK(on_destroy), user_data); + + gtk_widget_show(window); + window_present(window); + + /* some widget references need to be saved for outside use */ + user_data->dlg.window = window; + user_data->dlg.list_fwd = list_fwd; + user_data->dlg.list_rev = list_rev; + user_data->dlg.label_stats_fwd = label_stats_fwd; + user_data->dlg.label_stats_rev = label_stats_rev; + user_data->dlg.notebook = notebook; + user_data->dlg.selected_list = list_fwd; + user_data->dlg.number_of_nok = 0; + + /* + * select the initial row + */ + gtk_widget_grab_focus(list_fwd); +} + + +/****************************************************************************/ +static gboolean process_node(proto_node *ptree_node, header_field_info *hfinformation, + const gchar* proto_field, guint32* p_result) +{ + field_info *finfo; + proto_node *proto_sibling_node; + header_field_info *hfssrc; + ipv4_addr *ipv4; + + finfo = PITEM_FINFO(ptree_node); + + if (hfinformation==(finfo->hfinfo)) { + hfssrc = proto_registrar_get_byname(proto_field); + if (hfssrc == NULL) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Bad field name!"); + return FALSE; + } + for(ptree_node=ptree_node->first_child; ptree_node!=NULL; + ptree_node=ptree_node->next) { + finfo=PITEM_FINFO(ptree_node); + if (hfssrc==finfo->hfinfo) { + if (hfinformation->type==FT_IPv4) { + ipv4 = fvalue_get(&finfo->value); + *p_result = ipv4_get_net_order_addr(ipv4); + } + else { + *p_result = fvalue_get_uinteger(&finfo->value); + } + return TRUE; + } + } + if(!ptree_node) + return FALSE; + } + + proto_sibling_node = ptree_node->next; + + if (proto_sibling_node) { + return process_node(proto_sibling_node, hfinformation, proto_field, p_result); + } + else + return FALSE; +} + +/****************************************************************************/ +static gboolean get_int_value_from_proto_tree(proto_tree *protocol_tree, + const gchar* proto_name, + const gchar* proto_field, + guint32* p_result) +{ + proto_node *ptree_node; + header_field_info *hfinformation; + + hfinformation = proto_registrar_get_byname(proto_name); + if (hfinformation == NULL) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Bad proto!"); + return FALSE; + } + + ptree_node = ((proto_node *)protocol_tree)->first_child; + if (!ptree_node) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "No info!"); + return FALSE; + } + return process_node(ptree_node, hfinformation, proto_field, p_result); +} + + +/****************************************************************************/ +void iax2_analysis( + address *ip_src_fwd, + guint16 port_src_fwd, + address *ip_dst_fwd, + guint16 port_dst_fwd, + address *ip_src_rev, + guint16 port_src_rev, + address *ip_dst_rev, + guint16 port_dst_rev + ) +{ + user_data_t *user_data; + int fd; + int i; + static color_t col[MAX_GRAPHS] = { + {0, 0x0000, 0x0000, 0x0000}, + {0, 0xffff, 0x0000, 0x0000}, + {0, 0x0000, 0xffff, 0x0000}, + {0, 0x0000, 0x0000, 0xffff} + }; + + /* init */ + user_data = g_malloc(sizeof(user_data_t)); + + COPY_ADDRESS(&(user_data->ip_src_fwd), ip_src_fwd); + user_data->port_src_fwd = port_src_fwd; + COPY_ADDRESS(&(user_data->ip_dst_fwd), ip_dst_fwd); + user_data->port_dst_fwd = port_dst_fwd; + COPY_ADDRESS(&(user_data->ip_src_rev), ip_src_rev); + user_data->port_src_rev = port_src_rev; + COPY_ADDRESS(&(user_data->ip_dst_rev), ip_dst_rev); + user_data->port_dst_rev = port_dst_rev; + + + /* file names for storing sound data */ + /*XXX: check for errors*/ + fd = create_tempfile(user_data->f_tempname, sizeof(user_data->f_tempname), + "ether_iax2_f"); + ws_close(fd); + fd = create_tempfile(user_data->r_tempname, sizeof(user_data->r_tempname), + "ether_iax2_r"); + ws_close(fd); + user_data->forward.saveinfo.fp = NULL; + user_data->reversed.saveinfo.fp = NULL; + user_data->dlg.save_voice_as_w = NULL; + user_data->dlg.save_csv_as_w = NULL; + user_data->dlg.dialog_graph.window = NULL; + +#ifdef USE_CONVERSATION_GRAPH + user_data->dlg.graph_window = NULL; + user_data->series_fwd.value_pairs = NULL; + user_data->series_rev.value_pairs = NULL; +#endif + + /* init dialog_graph */ + user_data->dlg.dialog_graph.needs_redraw=TRUE; + user_data->dlg.dialog_graph.interval=tick_interval_values[DEFAULT_TICK_VALUE]; + user_data->dlg.dialog_graph.draw_area=NULL; + user_data->dlg.dialog_graph.pixmap=NULL; + user_data->dlg.dialog_graph.scrollbar=NULL; + user_data->dlg.dialog_graph.scrollbar_adjustment=NULL; + user_data->dlg.dialog_graph.pixmap_width=500; + user_data->dlg.dialog_graph.pixmap_height=200; + user_data->dlg.dialog_graph.pixels_per_tick=pixels_per_tick[DEFAULT_PIXELS_PER_TICK]; + user_data->dlg.dialog_graph.max_y_units=AUTO_MAX_YSCALE; + user_data->dlg.dialog_graph.last_interval=0xffffffff; + user_data->dlg.dialog_graph.max_interval=0; + user_data->dlg.dialog_graph.num_items=0; + user_data->dlg.dialog_graph.start_time = -1; + + for(i=0;i<MAX_GRAPHS;i++){ + user_data->dlg.dialog_graph.graph[i].gc=NULL; + user_data->dlg.dialog_graph.graph[i].color.pixel=0; + user_data->dlg.dialog_graph.graph[i].color.red=col[i].red; + user_data->dlg.dialog_graph.graph[i].color.green=col[i].green; + user_data->dlg.dialog_graph.graph[i].color.blue=col[i].blue; + user_data->dlg.dialog_graph.graph[i].display=TRUE; + user_data->dlg.dialog_graph.graph[i].display_button=NULL; + user_data->dlg.dialog_graph.graph[i].ud=user_data; + } + + /* create the dialog box */ + create_iax2_dialog(user_data); + + /* proceed as if the Refresh button would have been pressed */ + on_refresh_bt_clicked(NULL, user_data); +} + +/****************************************************************************/ +/* entry point from main menu */ +static void iax2_analysis_cb(GtkWidget *w _U_, gpointer data _U_) +{ + address ip_src_fwd; + guint16 port_src_fwd; + address ip_dst_fwd; + guint16 port_dst_fwd; + address ip_src_rev; + guint16 port_src_rev; + address ip_dst_rev; + guint16 port_dst_rev; + /* unsigned int ptype; */ + + gchar filter_text[256]; + dfilter_t *sfcode; + capture_file *cf; + epan_dissect_t *edt; + gint err; + gchar *err_info; + gboolean frame_matched; + frame_data *fdata; + GList *strinfo_list; + GList *filtered_list = NULL; + rtp_stream_info_t *strinfo; + guint nfound; + + /* Try to compile the filter. */ + g_strlcpy(filter_text,"iax2 && (ip || ipv6)",256); + if (!dfilter_compile(filter_text, &sfcode)) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, dfilter_error_msg); + return; + } + /* we load the current file into cf variable */ + cf = &cfile; + fdata = cf->current_frame; + + /* we are on the selected frame now */ + if (fdata == NULL) + return; /* if we exit here it's an error */ + + /* dissect the current frame */ + if (!wtap_seek_read(cf->wth, fdata->file_off, &cf->pseudo_header, + cf->pd, fdata->cap_len, &err, &err_info)) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + cf_read_error_message(err, err_info), cf->filename); + return; + } + edt = epan_dissect_new(TRUE, FALSE); + epan_dissect_prime_dfilter(edt, sfcode); + epan_dissect_run(edt, &cf->pseudo_header, cf->pd, fdata, NULL); + frame_matched = dfilter_apply_edt(sfcode, edt); + + /* if it is not an iax2 frame, show an error dialog */ + frame_matched = dfilter_apply_edt(sfcode, edt); + if (frame_matched != 1) { + epan_dissect_free(edt); + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "You didn't choose a IAX2 packet!"); + return; + } + /* check if it is Voice or MiniPacket + if (!get_int_value_from_proto_tree(edt->tree, "iax2", "iax2.call", &ptype)) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Please select a Voice packet!"); + return; + } */ + + /* check if it is part of a Call */ + if (edt->pi.circuit_id == 0) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "Please select a Call packet!"); + return; + } + + /* ok, it is a IAX2 frame, so let's get the ip and port values */ + COPY_ADDRESS(&(ip_src_fwd), &(edt->pi.src)) + COPY_ADDRESS(&(ip_dst_fwd), &(edt->pi.dst)) + port_src_fwd = edt->pi.srcport; + port_dst_fwd = edt->pi.destport; + + /* assume the inverse ip/port combination for the reverse direction */ + COPY_ADDRESS(&(ip_src_rev), &(edt->pi.dst)) + COPY_ADDRESS(&(ip_dst_rev), &(edt->pi.src)) + port_src_rev = edt->pi.destport; + port_dst_rev = edt->pi.srcport; + + /* Scan for rtpstream */ + rtpstream_scan(); + /* search for reversed direction in the global rtp streams list */ + nfound = 0; + strinfo_list = g_list_first(rtpstream_get_info()->strinfo_list); + while (strinfo_list) + { + strinfo = (rtp_stream_info_t*)(strinfo_list->data); + if (ADDRESSES_EQUAL(&(strinfo->src_addr),&(ip_src_fwd)) + && strinfo->src_port==port_src_fwd + && ADDRESSES_EQUAL(&(strinfo->dest_addr),&(ip_dst_fwd)) + && strinfo->dest_port==port_dst_fwd) + { + filtered_list = g_list_prepend(filtered_list, strinfo); + } + + if (ADDRESSES_EQUAL(&(strinfo->src_addr),&(ip_src_rev)) + && strinfo->src_port==port_src_rev + && ADDRESSES_EQUAL(&(strinfo->dest_addr),&(ip_dst_rev)) + && strinfo->dest_port==port_dst_rev) + { + ++nfound; + filtered_list = g_list_append(filtered_list, strinfo); + } + + strinfo_list = g_list_next(strinfo_list); + } + + /* if more than one reverse streams found, we let the user choose the right one */ + if (nfound>1) { + rtpstream_dlg_show(filtered_list); + return; + } + else { + iax2_analysis( + &ip_src_fwd, + port_src_fwd, + &ip_dst_fwd, + port_dst_fwd, + &ip_src_rev, + port_src_rev, + &ip_dst_rev, + port_dst_rev + ); + } +} + +/****************************************************************************/ +static void +iax2_analysis_init(const char *dummy _U_,void* userdata _U_) +{ + iax2_analysis_cb(NULL, NULL); +} + +/****************************************************************************/ +void +register_tap_listener_iax2_analysis(void) +{ + register_stat_cmd_arg("IAX2", iax2_analysis_init,NULL); + + register_stat_menu_item("IAX2/Stream Analysis...", REGISTER_STAT_GROUP_TELEPHONY, + iax2_analysis_cb, NULL, NULL, NULL); +} diff --git a/gtk/iax2_analysis.h b/gtk/iax2_analysis.h new file mode 100644 index 0000000000..1b83df7c9b --- /dev/null +++ b/gtk/iax2_analysis.h @@ -0,0 +1,119 @@ +/* iax2_analysis.h + * IAX2 analysis addition for Wireshark + * + * $Id$ + * + * based on rtp_analysis.c + * Copyright 2003, Alcatel Business Systems + * By Lars Ruoff <lars.ruoff@gmx.net> + * + * based on tap_rtp.c + * Copyright 2003, Iskratel, Ltd, Kranj + * By Miha Jemec <m.jemec@iskratel.si> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * 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. + */ + +#ifndef IAX2_ANALYSIS_H_INCLUDED +#define IAX2_ANALYSIS_H_INCLUDED + +#include <glib.h> +#include <epan/address.h> +#include <epan/packet_info.h> + +/** @file + * ??? + * @todo what's this? + */ + +void iax2_analysis( + address *ip_src_fwd, + guint16 port_src_fwd, + address *ip_dst_fwd, + guint16 port_dst_fwd, + address *ip_src_rev, + guint16 port_src_rev, + address *ip_dst_rev, + guint16 port_dst_rev + ); + +/****************************************************************************/ +/* structure that holds the information about the forward and reversed direction */ +typedef struct _iax2_bw_history_item { + double time; + guint32 bytes; +} iax2_bw_history_item; + +#define BUFF_BW 300 + +typedef struct _tap_iax2_stat_t { + gboolean first_packet; /* do not use in code that is called after rtp_packet_analyse */ + /* use (flags & STAT_FLAG_FIRST) instead */ + /* all of the following fields will be initialized after + rtp_packet_analyse has been called */ + guint32 flags; /* see STAT_FLAG-defines below */ + guint16 seq_num; + guint32 timestamp; + guint32 delta_timestamp; + double bandwidth; + iax2_bw_history_item bw_history[BUFF_BW]; + guint16 bw_start_index; + guint16 bw_index; + guint32 total_bytes; + double delta; + double jitter; + double diff; + double time; + double start_time; + double max_delta; + double max_jitter; + double mean_jitter; + guint32 max_nr; + guint16 start_seq_nr; + guint16 stop_seq_nr; + guint32 total_nr; + guint32 sequence; + gboolean under; + gint cycles; + guint16 pt; + int reg_pt; +} tap_iax2_stat_t; + +#define PT_UNDEFINED -1 + +/* status flags for the flags parameter in tap_iax2_stat_t */ +#define STAT_FLAG_FIRST 0x01 +#define STAT_FLAG_MARKER 0x02 +#define STAT_FLAG_WRONG_SEQ 0x04 +#define STAT_FLAG_PT_CHANGE 0x08 +#define STAT_FLAG_PT_CN 0x10 +#define STAT_FLAG_FOLLOW_PT_CN 0x20 +#define STAT_FLAG_REG_PT_CHANGE 0x40 +#define STAT_FLAG_WRONG_TIMESTAMP 0x80 + +/* forward */ +struct _rtp_info; + +/* function for analysing an RTP packet. Called from rtp_analysis and rtp_streams */ +extern int iax2_packet_analyse(tap_iax2_stat_t *statinfo, + packet_info *pinfo, + const struct _iax2_info_t *iax2info); + + +#endif /*IAX2_ANALYSIS_H_INCLUDED*/ diff --git a/gtk/voip_calls.c b/gtk/voip_calls.c index 5471083097..e89ab2ee85 100644 --- a/gtk/voip_calls.c +++ b/gtk/voip_calls.c @@ -59,6 +59,7 @@ #include <epan/dissectors/packet-sccp.h> #include <plugins/unistim/packet-unistim.h> #include <epan/dissectors/packet-skinny.h> +#include <epan/dissectors/packet-iax2.h> #include <epan/rtp_pt.h> #include "../globals.h" @@ -101,6 +102,7 @@ const char *voip_protocol_name[]={ "RANAP", "UNISTIM", "SKINNY", + "IAX2", "VoIP" }; @@ -3728,6 +3730,185 @@ remove_tap_listener_skinny_calls(void) } /****************************************************************************/ +/* ***************************TAP for IAX2 **********************************/ +/****************************************************************************/ + +/* IAX2 to tap-voip call state mapping */ +static const voip_call_state tap_iax_voip_state[] = { + VOIP_NO_STATE, + VOIP_CALL_SETUP, /*NEW*/ + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_COMPLETED, /*HANGUP*/ + VOIP_REJECTED, /*REJECT*/ + VOIP_RINGING, /*ACCEPT*/ + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_CALL_SETUP, /*DIAL*/ + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE, + VOIP_NO_STATE +}; + +static void free_iax2_info(gpointer p) { + iax2_info_t *ii = p; + + g_free(ii); +} + + +/****************************************************************************/ +/* whenever a IAX2 packet is seen by the tap listener */ +static int +iax2_calls_packet( void *ptr _U_, packet_info *pinfo, epan_dissect_t *edt _U_, const void *iax2_info) +{ + voip_calls_tapinfo_t *tapinfo = &the_tapinfo_struct; + GList* list; + voip_calls_info_t *callsinfo = NULL; + address* phone; + const iax2_info_t *ii = iax2_info; + iax2_info_t *tmp_iax2info; + gchar * comment; + + if (ii == NULL || ii->ptype != IAX2_FULL_PACKET || (ii->scallno == 0 && ii->dcallno == 0)) + return 0; + /* check whether we already have this context in the list */ + list = g_list_first(tapinfo->callsinfo_list); + while (list) + { + voip_calls_info_t* tmp_listinfo = list->data; + if (tmp_listinfo->protocol == VOIP_IAX2){ + tmp_iax2info = tmp_listinfo->prot_info; + if (tmp_iax2info->scallno == ii->scallno || + tmp_iax2info->scallno == ii->dcallno){ + callsinfo = (voip_calls_info_t*)(list->data); + break; + } + } + list = g_list_next (list); + } + phone = &(pinfo->src); + + + if (callsinfo==NULL){ + /* We only care about real calls, i.e., no registration stuff */ + if (ii->ftype != AST_FRAME_IAX || ii->csub != IAX_COMMAND_NEW) + return 0; + callsinfo = g_malloc0(sizeof(voip_calls_info_t)); + callsinfo->call_state = VOIP_NO_STATE; + callsinfo->call_active_state = VOIP_ACTIVE; + callsinfo->prot_info=g_malloc(sizeof(iax2_info_t)); + callsinfo->free_prot_info = free_iax2_info; + tmp_iax2info = callsinfo->prot_info; + + tmp_iax2info->scallno = ii->scallno; + if (tmp_iax2info->scallno == 0) tmp_iax2info->scallno = ii->dcallno; + tmp_iax2info->callState = tap_iax_voip_state[ii->callState]; + + callsinfo->npackets = 1; + callsinfo->first_frame_num=pinfo->fd->num; + callsinfo->last_frame_num=pinfo->fd->num; + + COPY_ADDRESS(&(callsinfo->initial_speaker), phone); + callsinfo->from_identity = g_strdup(ii->callingParty); + callsinfo->to_identity = g_strdup(ii->calledParty); + + callsinfo->protocol = VOIP_IAX2; + callsinfo->call_num = tapinfo->ncalls++; + callsinfo->start_sec=(gint32) (pinfo->fd->rel_ts.secs); + callsinfo->start_usec=pinfo->fd->rel_ts.nsecs; + callsinfo->stop_sec=(gint32) (pinfo->fd->rel_ts.secs); + callsinfo->stop_usec=pinfo->fd->rel_ts.nsecs; + + callsinfo->selected = FALSE; + tapinfo->callsinfo_list = g_list_append(tapinfo->callsinfo_list, callsinfo); + + } else { + if ((ii->callState > 0) && (ii->callState < (sizeof(tap_iax_voip_state)/sizeof(tap_iax_voip_state[0])))) + callsinfo->call_state = tap_iax_voip_state[ii->callState]; + + callsinfo->stop_sec=(gint32) (pinfo->fd->rel_ts.secs); + callsinfo->stop_usec=pinfo->fd->rel_ts.nsecs; + callsinfo->last_frame_num=pinfo->fd->num; + ++(callsinfo->npackets); + } + + comment = ""; + + add_to_graph(tapinfo, pinfo, ii->messageName, comment, + callsinfo->call_num, &(pinfo->src), &(pinfo->dst), 1); + + return 1; + +} + + +/****************************************************************************/ +/* TAP INTERFACE */ +/****************************************************************************/ +static gboolean have_iax2_tap_listener=FALSE; +/****************************************************************************/ +void +iax2_calls_init_tap(void) +{ + GString *error_string; + + if(have_iax2_tap_listener==FALSE) + { + /* don't register tap listener, if we have it already */ + /* we send an empty filter, to force a non null "tree" in the IAX2 dissector */ + error_string = register_tap_listener("IAX2", &(the_tapinfo_struct.iax2_dummy), g_strdup(""), + voip_calls_dlg_reset, + iax2_calls_packet, + voip_calls_dlg_draw + ); + if (error_string != NULL) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + error_string->str); + g_string_free(error_string, TRUE); + exit(1); + } + have_iax2_tap_listener=TRUE; + } +} + +/****************************************************************************/ +void +remove_tap_listener_iax2_calls(void) +{ + protect_thread_critical_region(); + remove_tap_listener(&(the_tapinfo_struct.iax2_dummy)); + unprotect_thread_critical_region(); + + have_iax2_tap_listener=FALSE; +} + +/****************************************************************************/ /* ***************************TAP for OTHER PROTOCOL **********************************/ /****************************************************************************/ diff --git a/gtk/voip_calls.h b/gtk/voip_calls.h index 64ddfbe9c7..bbcf960b35 100644 --- a/gtk/voip_calls.h +++ b/gtk/voip_calls.h @@ -59,6 +59,7 @@ typedef enum _voip_protocol { TEL_RANAP, VOIP_UNISTIM, VOIP_SKINNY, + VOIP_IAX2, VOIP_COMMON } voip_protocol; @@ -185,6 +186,7 @@ typedef struct _voip_calls_tapinfo { int megaco_dummy; int unistim_dummy; int skinny_dummy; + int iax2_dummy; int voip_dummy; } voip_calls_tapinfo_t; @@ -247,6 +249,7 @@ void h248_calls_init_tap(void); void sccp_calls_init_tap(void); void unistim_calls_init_tap(void); void skinny_calls_init_tap(void); +void iax2_calls_init_tap(void); void VoIPcalls_init_tap(void); /* @@ -269,6 +272,7 @@ void remove_tap_listener_h248_calls(void); void remove_tap_listener_sccp_calls(void); void remove_tap_listener_unistim_calls(void); void remove_tap_listener_skinny_calls(void); +void remove_tap_listener_iax2_calls(void); void remove_tap_listener_voip_calls(void); /* diff --git a/gtk/voip_calls_dlg.c b/gtk/voip_calls_dlg.c index 712bd4c35f..f065759f68 100644 --- a/gtk/voip_calls_dlg.c +++ b/gtk/voip_calls_dlg.c @@ -224,6 +224,7 @@ static void voip_calls_remove_tap_listener(void) remove_tap_listener_actrace_calls(); remove_tap_listener_t38(); remove_tap_listener_skinny_calls(); + remove_tap_listener_iax2_calls(); } /****************************************************************************/ @@ -377,6 +378,7 @@ voip_calls_on_filter (GtkButton *button _U_, case TEL_RANAP: case VOIP_UNISTIM: case VOIP_SKINNY: + case VOIP_IAX2: case VOIP_COMMON: /* XXX - not supported */ break; @@ -927,6 +929,7 @@ voip_calls_init_tap(const char *dummy _U_, void* userdata _U_) actrace_calls_init_tap(); t38_init_tap(); skinny_calls_init_tap(); + iax2_calls_init_tap(); /* create dialog box if necessary */ if (voip_calls_dlg == NULL) { |