From Carlos Mendioroz:
authorAnders Broman <anders.broman@ericsson.com>
Wed, 11 Mar 2009 06:43:46 +0000 (06:43 -0000)
committerAnders Broman <anders.broman@ericsson.com>
Wed, 11 Mar 2009 06:43:46 +0000 (06:43 -0000)
IAX2 calls statistics window ala RTP.
https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=3217

svn path=/trunk/; revision=27696

epan/dissectors/packet-iax2.c
epan/dissectors/packet-iax2.h
gtk/Makefile.common
gtk/iax2_analysis.c [new file with mode: 0644]
gtk/iax2_analysis.h [new file with mode: 0644]
gtk/voip_calls.c
gtk/voip_calls.h
gtk/voip_calls_dlg.c

index 28a31521ef82192b1602a2e16f9b084534224a89..c094a464f3f32b503b5b1e59bd88a9187ddc0222 100644 (file)
@@ -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>
 /* 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
index 44cc0de5df75e85c1c21e9f4c13e7b461fc11825..92652155e82f82409c4bbdb3de329ef45954b28e 100644 (file)
 #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
index e10847e1a7144209d6fb7c4af154568e63b21ed8..0b29b4e84293d7b9da98e747161e168787c7c824 100644 (file)
@@ -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 (file)
index 0000000..8cde90b
--- /dev/null
@@ -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 (file)
index 0000000..1b83df7
--- /dev/null
@@ -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*/
index 54710830977bf75ddb7b4ff73b3e01c43a7ca45d..e89ab2ee8554ec3bf4c5442ed58cddb1ba267c10 100644 (file)
@@ -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"
 };
 
@@ -3727,6 +3729,185 @@ remove_tap_listener_skinny_calls(void)
        have_skinny_tap_listener=FALSE;
 }
 
+/****************************************************************************/
+/* ***************************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 **********************************/
 /****************************************************************************/
index 64ddfbe9c7ffc129d5e2039f6ec44b15f8e9e1c4..bbcf960b3518d1841d2c03b5313e28a013b71f1d 100644 (file)
@@ -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);
 
 /*
index 712bd4c35ffe1932b5489cf4e425e90371278864..f065759f683a56d7e21b8a5f499a82046d515e53 100644 (file)
@@ -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) {