/* tap-iostat.c
 * iostat   2002 Ronnie Sahlberg
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include <epan/epan_dissect.h>
#include <epan/tap.h>
#include <epan/stat_tap_ui.h>
#include "globals.h"
#include <wsutil/ws_assert.h>

#define CALC_TYPE_FRAMES 0
#define CALC_TYPE_BYTES  1
#define CALC_TYPE_FRAMES_AND_BYTES 2
#define CALC_TYPE_COUNT  3
#define CALC_TYPE_SUM    4
#define CALC_TYPE_MIN    5
#define CALC_TYPE_MAX    6
#define CALC_TYPE_AVG    7
#define CALC_TYPE_LOAD   8

void register_tap_listener_iostat(void);

typedef struct {
    const char *func_name;
    int calc_type;
} calc_type_ent_t;

static calc_type_ent_t calc_type_table[] = {
    { "FRAMES",       CALC_TYPE_FRAMES },
    { "BYTES",        CALC_TYPE_BYTES },
    { "FRAMES BYTES", CALC_TYPE_FRAMES_AND_BYTES },
    { "COUNT",        CALC_TYPE_COUNT },
    { "SUM",          CALC_TYPE_SUM },
    { "MIN",          CALC_TYPE_MIN },
    { "MAX",          CALC_TYPE_MAX },
    { "AVG",          CALC_TYPE_AVG },
    { "LOAD",         CALC_TYPE_LOAD },
    { NULL, 0 }
};

typedef struct _io_stat_t {
    guint64 interval;     /* The user-specified time interval (us) */
    guint invl_prec;      /* Decimal precision of the time interval (1=10s, 2=100s etc) */
    unsigned int num_cols;         /* The number of columns of stats in the table */
    struct _io_stat_item_t *items;  /* Each item is a single cell in the table */
    time_t start_time;    /* Time of first frame matching the filter */
    const char **filters; /* 'io,stat' cmd strings (e.g., "AVG(smb.time)smb.time") */
    guint64 *max_vals;    /* The max value sans the decimal or nsecs portion in each stat column */
    guint32 *max_frame;   /* The max frame number displayed in each stat column */
} io_stat_t;

typedef struct _io_stat_item_t {
    io_stat_t *parent;
    struct _io_stat_item_t *next;
    struct _io_stat_item_t *prev;
    guint64 start_time;   /* Time since start of capture (us)*/
    int calc_type;        /* The statistic type */
    int colnum;           /* Column number of this stat (0 to n) */
    int hf_index;
    guint32 frames;
    guint32 num;          /* The sample size of a given statistic (only needed for AVG) */
    guint64 counter;      /* The accumulated data for the calculation of that statistic */
    gfloat float_counter;
    gdouble double_counter;
} io_stat_item_t;

#define NANOSECS_PER_SEC G_GUINT64_CONSTANT(1000000000)

static guint64 last_relative_time;

static tap_packet_status
iostat_packet(void *arg, packet_info *pinfo, epan_dissect_t *edt, const void *dummy _U_, tap_flags_t flags _U_)
{
    io_stat_t *parent;
    io_stat_item_t *mit;
    io_stat_item_t *it;
    guint64 relative_time, rt;
    const nstime_t *new_time;
    GPtrArray *gp;
    guint i;
    int ftype;

    mit = (io_stat_item_t *) arg;
    parent = mit->parent;

    /* If this frame's relative time is negative, set its relative time to last_relative_time
       rather than disincluding it from the calculations. */
    if ((pinfo->rel_ts.secs >= 0) && (pinfo->rel_ts.nsecs >= 0)) {
        relative_time = ((guint64)pinfo->rel_ts.secs * G_GUINT64_CONSTANT(1000000)) +
                        ((guint64)((pinfo->rel_ts.nsecs+500)/1000));
        last_relative_time = relative_time;
    } else {
        relative_time = last_relative_time;
    }

    if (mit->parent->start_time == 0) {
        mit->parent->start_time = pinfo->abs_ts.secs - pinfo->rel_ts.secs;
    }

    /* The prev item is always the last interval in which we saw packets. */
    it = mit->prev;

    /* If we have moved into a new interval (row), create a new io_stat_item_t struct for every interval
    *  between the last struct and this one. If an item was not found in a previous interval, an empty
    *  struct will be created for it. */
    rt = relative_time;
    while (rt >= it->start_time + parent->interval) {
        it->next = g_new(io_stat_item_t, 1);
        it->next->prev = it;
        it->next->next = NULL;
        it = it->next;
        mit->prev = it;

        it->start_time = it->prev->start_time + parent->interval;
        it->frames = 0;
        it->counter = 0;
        it->float_counter = 0;
        it->double_counter = 0;
        it->num = 0;
        it->calc_type = it->prev->calc_type;
        it->hf_index = it->prev->hf_index;
        it->colnum = it->prev->colnum;
    }

    /* Store info in the current structure */
    it->frames++;

    switch (it->calc_type) {
    case CALC_TYPE_FRAMES:
    case CALC_TYPE_BYTES:
    case CALC_TYPE_FRAMES_AND_BYTES:
        it->counter += pinfo->fd->pkt_len;
        break;
    case CALC_TYPE_COUNT:
        gp = proto_get_finfo_ptr_array(edt->tree, it->hf_index);
        if (gp) {
            it->counter += gp->len;
        }
        break;
    case CALC_TYPE_SUM:
        gp = proto_get_finfo_ptr_array(edt->tree, it->hf_index);
        if (gp) {
            guint64 val;

            for (i=0; i<gp->len; i++) {
                switch (proto_registrar_get_ftype(it->hf_index)) {
                case FT_UINT8:
                case FT_UINT16:
                case FT_UINT24:
                case FT_UINT32:
                    it->counter += fvalue_get_uinteger(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_UINT40:
                case FT_UINT48:
                case FT_UINT56:
                case FT_UINT64:
                    it->counter += fvalue_get_uinteger64(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_INT8:
                case FT_INT16:
                case FT_INT24:
                case FT_INT32:
                    it->counter += fvalue_get_sinteger(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_INT40:
                case FT_INT48:
                case FT_INT56:
                case FT_INT64:
                    it->counter += (gint64)fvalue_get_sinteger64(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_FLOAT:
                    it->float_counter +=
                        (gfloat)fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_DOUBLE:
                    it->double_counter += fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_RELATIVE_TIME:
                    new_time = fvalue_get_time(((field_info *)gp->pdata[i])->value);
                    val = ((guint64)new_time->secs * NANOSECS_PER_SEC) + (guint64)new_time->nsecs;
                    it->counter  +=  val;
                    break;
                default:
                    /*
                     * "Can't happen"; see the checks
                     * in register_io_tap().
                     */
                    ws_assert_not_reached();
                    break;
                }
            }
        }
        break;
    case CALC_TYPE_MIN:
        gp = proto_get_finfo_ptr_array(edt->tree, it->hf_index);
        if (gp) {
            guint64 val;
            gfloat float_val;
            gdouble double_val;

            ftype = proto_registrar_get_ftype(it->hf_index);
            for (i=0; i<gp->len; i++) {
                switch (ftype) {
                case FT_UINT8:
                case FT_UINT16:
                case FT_UINT24:
                case FT_UINT32:
                    val = fvalue_get_uinteger(((field_info *)gp->pdata[i])->value);
                    if ((it->frames == 1 && i == 0) || (val < it->counter)) {
                        it->counter = val;
                    }
                    break;
                case FT_UINT40:
                case FT_UINT48:
                case FT_UINT56:
                case FT_UINT64:
                    val = fvalue_get_uinteger64(((field_info *)gp->pdata[i])->value);
                    if ((it->frames == 1 && i == 0) || (val < it->counter)) {
                        it->counter = val;
                    }
                    break;
                case FT_INT8:
                case FT_INT16:
                case FT_INT24:
                case FT_INT32:
                    val = fvalue_get_sinteger(((field_info *)gp->pdata[i])->value);
                    if ((it->frames == 1 && i == 0) || ((gint32)val < (gint32)it->counter)) {
                        it->counter = val;
                    }
                    break;
                case FT_INT40:
                case FT_INT48:
                case FT_INT56:
                case FT_INT64:
                    val = fvalue_get_sinteger64(((field_info *)gp->pdata[i])->value);
                    if ((it->frames == 1 && i == 0) || ((gint64)val < (gint64)it->counter)) {
                        it->counter = val;
                    }
                    break;
                case FT_FLOAT:
                    float_val = (gfloat)fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    if ((it->frames == 1 && i == 0) || (float_val < it->float_counter)) {
                        it->float_counter = float_val;
                    }
                    break;
                case FT_DOUBLE:
                    double_val = fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    if ((it->frames == 1 && i == 0) || (double_val < it->double_counter)) {
                        it->double_counter = double_val;
                    }
                    break;
                case FT_RELATIVE_TIME:
                    new_time = fvalue_get_time(((field_info *)gp->pdata[i])->value);
                    val = ((guint64)new_time->secs * NANOSECS_PER_SEC) + (guint64)new_time->nsecs;
                    if ((it->frames == 1 && i == 0) || (val < it->counter)) {
                        it->counter = val;
                    }
                    break;
                default:
                    /*
                     * "Can't happen"; see the checks
                     * in register_io_tap().
                     */
                    ws_assert_not_reached();
                    break;
                }
            }
        }
        break;
    case CALC_TYPE_MAX:
        gp = proto_get_finfo_ptr_array(edt->tree, it->hf_index);
        if (gp) {
            guint64 val;
            gfloat float_val;
            gdouble double_val;

            ftype = proto_registrar_get_ftype(it->hf_index);
            for (i=0; i<gp->len; i++) {
                switch (ftype) {
                case FT_UINT8:
                case FT_UINT16:
                case FT_UINT24:
                case FT_UINT32:
                    val = fvalue_get_uinteger(((field_info *)gp->pdata[i])->value);
                    if (val > it->counter)
                        it->counter = val;
                    break;
                case FT_UINT40:
                case FT_UINT48:
                case FT_UINT56:
                case FT_UINT64:
                    val = fvalue_get_uinteger64(((field_info *)gp->pdata[i])->value);
                    if (val > it->counter)
                        it->counter = val;
                    break;
                case FT_INT8:
                case FT_INT16:
                case FT_INT24:
                case FT_INT32:
                    val = fvalue_get_sinteger(((field_info *)gp->pdata[i])->value);
                    if ((gint32)val > (gint32)it->counter)
                        it->counter = val;
                    break;
                case FT_INT40:
                case FT_INT48:
                case FT_INT56:
                case FT_INT64:
                    val = fvalue_get_sinteger64(((field_info *)gp->pdata[i])->value);
                    if ((gint64)val > (gint64)it->counter)
                        it->counter = val;
                    break;
                case FT_FLOAT:
                    float_val = (gfloat)fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    if (float_val > it->float_counter)
                        it->float_counter = float_val;
                    break;
                case FT_DOUBLE:
                    double_val = fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    if (double_val > it->double_counter)
                        it->double_counter = double_val;
                    break;
                case FT_RELATIVE_TIME:
                    new_time = fvalue_get_time(((field_info *)gp->pdata[i])->value);
                    val = ((guint64)new_time->secs * NANOSECS_PER_SEC) + (guint64)new_time->nsecs;
                    if (val > it->counter)
                        it->counter = val;
                    break;
                default:
                    /*
                     * "Can't happen"; see the checks
                     * in register_io_tap().
                     */
                    ws_assert_not_reached();
                    break;
                }
            }
        }
        break;
    case CALC_TYPE_AVG:
        gp = proto_get_finfo_ptr_array(edt->tree, it->hf_index);
        if (gp) {
            guint64 val;

            ftype = proto_registrar_get_ftype(it->hf_index);
            for (i=0; i<gp->len; i++) {
                it->num++;
                switch (ftype) {
                case FT_UINT8:
                case FT_UINT16:
                case FT_UINT24:
                case FT_UINT32:
                    val = fvalue_get_uinteger(((field_info *)gp->pdata[i])->value);
                    it->counter += val;
                    break;
                case FT_UINT40:
                case FT_UINT48:
                case FT_UINT56:
                case FT_UINT64:
                    val = fvalue_get_uinteger64(((field_info *)gp->pdata[i])->value);
                    it->counter += val;
                    break;
                case FT_INT8:
                case FT_INT16:
                case FT_INT24:
                case FT_INT32:
                    val = fvalue_get_sinteger(((field_info *)gp->pdata[i])->value);
                    it->counter += val;
                    break;
                case FT_INT40:
                case FT_INT48:
                case FT_INT56:
                case FT_INT64:
                    val = fvalue_get_sinteger64(((field_info *)gp->pdata[i])->value);
                    it->counter += val;
                    break;
                case FT_FLOAT:
                    it->float_counter += (gfloat)fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_DOUBLE:
                    it->double_counter += fvalue_get_floating(((field_info *)gp->pdata[i])->value);
                    break;
                case FT_RELATIVE_TIME:
                    new_time = fvalue_get_time(((field_info *)gp->pdata[i])->value);
                    val = ((guint64)new_time->secs * NANOSECS_PER_SEC) + (guint64)new_time->nsecs;
                    it->counter += val;
                    break;
                default:
                    /*
                     * "Can't happen"; see the checks
                     * in register_io_tap().
                     */
                    ws_assert_not_reached();
                    break;
                }
            }
        }
        break;
    case CALC_TYPE_LOAD:
        gp = proto_get_finfo_ptr_array(edt->tree, it->hf_index);
        if (gp) {
            ftype = proto_registrar_get_ftype(it->hf_index);
            if (ftype != FT_RELATIVE_TIME) {
                fprintf(stderr,
                    "\ntshark: LOAD() is only supported for relative-time fields such as smb.time\n");
                exit(10);
            }
            for (i=0; i<gp->len; i++) {
                guint64 val;
                int tival;
                io_stat_item_t *pit;

                new_time = fvalue_get_time(((field_info *)gp->pdata[i])->value);
                val = ((guint64)new_time->secs*G_GUINT64_CONSTANT(1000000)) + (guint64)(new_time->nsecs/1000);
                tival = (int)(val % parent->interval);
                it->counter += tival;
                val -= tival;
                pit = it->prev;
                while (val > 0) {
                    if (val < (guint64)parent->interval) {
                        pit->counter += val;
                        break;
                    }
                    pit->counter += parent->interval;
                    val -= parent->interval;
                    pit = pit->prev;
                }
            }
        }
        break;
    }
    /* Store the highest value for this item in order to determine the width of each stat column.
    *  For real numbers we only need to know its magnitude (the value to the left of the decimal point
    *  so round it up before storing it as an integer in max_vals. For AVG of RELATIVE_TIME fields,
    *  calc the average, round it to the next second and store the seconds. For all other calc types
    *  of RELATIVE_TIME fields, store the counters without modification.
    *  fields. */
    switch (it->calc_type) {
        case CALC_TYPE_FRAMES:
        case CALC_TYPE_FRAMES_AND_BYTES:
            parent->max_frame[it->colnum] =
                MAX(parent->max_frame[it->colnum], it->frames);
            if (it->calc_type == CALC_TYPE_FRAMES_AND_BYTES)
                parent->max_vals[it->colnum] =
                    MAX(parent->max_vals[it->colnum], it->counter);
            break;
        case CALC_TYPE_BYTES:
        case CALC_TYPE_COUNT:
        case CALC_TYPE_LOAD:
            parent->max_vals[it->colnum] = MAX(parent->max_vals[it->colnum], it->counter);
            break;
        case CALC_TYPE_SUM:
        case CALC_TYPE_MIN:
        case CALC_TYPE_MAX:
            ftype = proto_registrar_get_ftype(it->hf_index);
            switch (ftype) {
                case FT_FLOAT:
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], (guint64)(it->float_counter+0.5));
                    break;
                case FT_DOUBLE:
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], (guint64)(it->double_counter+0.5));
                    break;
                case FT_RELATIVE_TIME:
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], it->counter);
                    break;
                default:
                    /* UINT16-64 and INT8-64 */
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], it->counter);
                    break;
            }
            break;
        case CALC_TYPE_AVG:
            if (it->num == 0) /* avoid division by zero */
               break;
            ftype = proto_registrar_get_ftype(it->hf_index);
            switch (ftype) {
                case FT_FLOAT:
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], (guint64)it->float_counter/it->num);
                    break;
                case FT_DOUBLE:
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], (guint64)it->double_counter/it->num);
                    break;
                case FT_RELATIVE_TIME:
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], ((it->counter/(guint64)it->num) + G_GUINT64_CONSTANT(500000000)) / NANOSECS_PER_SEC);
                    break;
                default:
                    /* UINT16-64 and INT8-64 */
                    parent->max_vals[it->colnum] =
                        MAX(parent->max_vals[it->colnum], it->counter/it->num);
                    break;
            }
    }
    return TAP_PACKET_REDRAW;
}

static unsigned int
magnitude (guint64 val, unsigned int max_w)
{
    unsigned int i, mag = 0;

    for (i=0; i<max_w; i++) {
        mag++;
        if ((val /= 10) == 0)
            break;
    }
    return(mag);
}

/*
*  Print the calc_type_table[] function label centered in the column header.
*/
static void
printcenter (const char *label, int lenval, int numpad)
{
    int lenlab = (int) strlen(label), len;
    const char spaces[] = "      ", *spaces_ptr;

    len = (int) (strlen(spaces)) - (((lenval-lenlab) / 2) + numpad);
    if (len > 0 && len < 6) {
        spaces_ptr = &spaces[len];
        if ((lenval-lenlab)%2 == 0) {
            printf("%s%s%s|", spaces_ptr, label, spaces_ptr);
        } else {
            printf("%s%s%s|", spaces_ptr-1, label, spaces_ptr);
        }
    } else if (len > 0 && len <= 15) {
        printf("%s|", label);
    }
}

typedef struct {
    int fr;  /* Width of this FRAMES column sans padding and border chars */
    int val; /* Width of this non-FRAMES column sans padding and border chars */
} column_width;

static void
iostat_draw(void *arg)
{
    guint32 num;
    guint64 interval, duration, t, invl_end, dv;
    unsigned int i, j, k, num_cols, num_rows, dur_secs_orig, dur_nsecs_orig, dur_secs, dur_nsecs, dur_mag,
        invl_mag, invl_prec, tabrow_w, borderlen, invl_col_w, numpad = 1, namelen, len_filt, type,
        maxfltr_w, ftype;
    unsigned int fr_mag;    /* The magnitude of the max frame number in this column */
    unsigned int val_mag;   /* The magnitude of the max value in this column */
    char *spaces, *spaces_s, *filler_s = NULL, **fmts, *fmt = NULL;
    const char *filter;
    static gchar dur_mag_s[3], invl_prec_s[3], fr_mag_s[3], val_mag_s[3], *invl_fmt, *full_fmt;
    io_stat_item_t *mit, **stat_cols, *item, **item_in_column;
    gboolean last_row = FALSE;
    io_stat_t *iot;
    column_width *col_w;
    struct tm *tm_time;
    time_t the_time;

    mit = (io_stat_item_t *)arg;
    iot = mit->parent;
    num_cols = iot->num_cols;
    col_w = g_new(column_width, num_cols);
    fmts = (char **)g_malloc(sizeof(char *) * num_cols);
    duration = ((guint64)cfile.elapsed_time.secs * G_GUINT64_CONSTANT(1000000)) +
                (guint64)((cfile.elapsed_time.nsecs + 500) / 1000);

    /* Store the pointer to each stat column */
    stat_cols = (io_stat_item_t **)g_malloc(sizeof(io_stat_item_t *) * num_cols);
    for (j=0; j<num_cols; j++)
        stat_cols[j] = &iot->items[j];

    /* The following prevents gross inaccuracies when the user specifies an interval that is greater
    *  than the capture duration. */
    if (iot->interval > duration || iot->interval == G_MAXUINT64) {
        interval = duration;
        iot->interval = G_MAXUINT64;
    } else {
        interval = iot->interval;
    }

    /* Calc the capture duration's magnitude (dur_mag) */
    dur_secs  = (unsigned int)(duration/G_GUINT64_CONSTANT(1000000));
    dur_secs_orig = dur_secs;
    dur_nsecs = (unsigned int)(duration%G_GUINT64_CONSTANT(1000000));
    dur_nsecs_orig = dur_nsecs;
    dur_mag = magnitude((guint64)dur_secs, 5);
    snprintf(dur_mag_s, 3, "%u", dur_mag);

    /* Calc the interval's magnitude */
    invl_mag = magnitude(interval/G_GUINT64_CONSTANT(1000000), 5);

    /* Set or get the interval precision */
    if (interval == duration) {
        /*
        * An interval arg of 0 or an interval size exceeding the capture duration was specified.
        * Set the decimal precision of duration based on its magnitude. */
        if (dur_mag >= 2)
            invl_prec = 1;
        else if (dur_mag == 1)
            invl_prec = 3;
        else
            invl_prec = 6;

        borderlen = 30 + dur_mag + (invl_prec == 0 ? 0 : invl_prec+1);
    } else {
        invl_prec = iot->invl_prec;
        borderlen = 25 + MAX(invl_mag,dur_mag) + (invl_prec == 0 ? 0 : invl_prec+1);
    }

    /* Round the duration according to invl_prec */
    dv = 1000000;
    for (i=0; i<invl_prec; i++)
        dv /= 10;
    if ((duration%dv) > 5*(dv/10)) {
        duration += 5*(dv/10);
        duration = (duration/dv) * dv;
        dur_secs  = (unsigned int)(duration/G_GUINT64_CONSTANT(1000000));
        dur_nsecs = (unsigned int)(duration%G_GUINT64_CONSTANT(1000000));
        /*
         * Recalc dur_mag in case rounding has increased its magnitude */
        dur_mag  = magnitude((guint64)dur_secs, 5);
    }
    if (iot->interval == G_MAXUINT64)
        interval = duration;

    /* Calc the width of the time interval column (incl borders and padding). */
    if (invl_prec == 0)
        invl_col_w = (2*dur_mag) + 8;
    else
        invl_col_w = (2*dur_mag) + (2*invl_prec) + 10;

    /* Update the width of the time interval column if date is shown */
    switch (timestamp_get_type()) {
    case TS_ABSOLUTE_WITH_YMD:
    case TS_ABSOLUTE_WITH_YDOY:
    case TS_UTC_WITH_YMD:
    case TS_UTC_WITH_YDOY:
        invl_col_w = MAX(invl_col_w, 23);
        break;

    default:
        invl_col_w = MAX(invl_col_w, 12);
        break;
    }

    borderlen = MAX(borderlen, invl_col_w);

    /* Calc the total width of each row in the stats table and build the printf format string for each
    *  column based on its field type, width, and name length.
    *  NOTE: The magnitude of all types including float and double are stored in iot->max_vals which
    *        is an *integer*. */
    tabrow_w = invl_col_w;
    for (j=0; j<num_cols; j++) {
        type = iot->items[j].calc_type;
        if (type == CALC_TYPE_FRAMES_AND_BYTES) {
            namelen = 5;
        } else {
            namelen = (unsigned int)strlen(calc_type_table[type].func_name);
        }
        if (type == CALC_TYPE_FRAMES
         || type == CALC_TYPE_FRAMES_AND_BYTES) {

            fr_mag = magnitude(iot->max_frame[j], 15);
            fr_mag = MAX(6, fr_mag);
            col_w[j].fr = fr_mag;
            tabrow_w += col_w[j].fr + 3;
            snprintf(fr_mag_s, 3, "%u", fr_mag);

            if (type == CALC_TYPE_FRAMES) {
                fmt = g_strconcat(" %", fr_mag_s, "u |", NULL);
            } else {
                /* CALC_TYPE_FRAMES_AND_BYTES
                */
                val_mag = magnitude(iot->max_vals[j], 15);
                val_mag = MAX(5, val_mag);
                col_w[j].val = val_mag;
                tabrow_w += (col_w[j].val + 3);
                snprintf(val_mag_s, 3, "%u", val_mag);
                fmt = g_strconcat(" %", fr_mag_s, "u |", " %", val_mag_s, PRIu64 " |", NULL);
            }
            if (fmt)
                fmts[j] = fmt;
            continue;
        }
        switch (type) {
        case CALC_TYPE_BYTES:
        case CALC_TYPE_COUNT:

            val_mag = magnitude(iot->max_vals[j], 15);
            val_mag = MAX(5, val_mag);
            col_w[j].val = val_mag;
            snprintf(val_mag_s, 3, "%u", val_mag);
            fmt = g_strconcat(" %", val_mag_s, PRIu64 " |", NULL);
            break;

        default:
            ftype = proto_registrar_get_ftype(stat_cols[j]->hf_index);
            switch (ftype) {
                case FT_FLOAT:
                case FT_DOUBLE:
                    val_mag = magnitude(iot->max_vals[j], 15);
                    snprintf(val_mag_s, 3, "%u", val_mag);
                    fmt = g_strconcat(" %", val_mag_s, ".6f |", NULL);
                    col_w[j].val = val_mag + 7;
                    break;
                case FT_RELATIVE_TIME:
                    /* Convert FT_RELATIVE_TIME field to seconds
                    *  CALC_TYPE_LOAD was already converted in iostat_packet() ) */
                    if (type == CALC_TYPE_LOAD) {
                        iot->max_vals[j] /= interval;
                    } else if (type != CALC_TYPE_AVG) {
                        iot->max_vals[j] = (iot->max_vals[j] + G_GUINT64_CONSTANT(500000000)) / NANOSECS_PER_SEC;
                    }
                    val_mag = magnitude(iot->max_vals[j], 15);
                    snprintf(val_mag_s, 3, "%u", val_mag);
                    fmt = g_strconcat(" %", val_mag_s, "u.%06u |", NULL);
                    col_w[j].val = val_mag + 7;
                   break;

                default:
                    val_mag = magnitude(iot->max_vals[j], 15);
                    val_mag = MAX(namelen, val_mag);
                    col_w[j].val = val_mag;
                    snprintf(val_mag_s, 3, "%u", val_mag);

                    switch (ftype) {
                    case FT_UINT8:
                    case FT_UINT16:
                    case FT_UINT24:
                    case FT_UINT32:
                    case FT_UINT64:
                        fmt = g_strconcat(" %", val_mag_s, PRIu64 " |", NULL);
                        break;
                    case FT_INT8:
                    case FT_INT16:
                    case FT_INT24:
                    case FT_INT32:
                    case FT_INT64:
                        fmt = g_strconcat(" %", val_mag_s, PRId64 " |", NULL);
                        break;
                    }
            } /* End of ftype switch */
        } /* End of calc_type switch */
        tabrow_w += col_w[j].val + 3;
        if (fmt)
            fmts[j] = fmt;
    } /* End of for loop (columns) */

    borderlen = MAX(borderlen, tabrow_w);

    /* Calc the max width of the list of filters. */
    maxfltr_w = 0;
    for (j=0; j<num_cols; j++) {
        if (iot->filters[j]) {
            k = (unsigned int) (strlen(iot->filters[j]) + 11);
            maxfltr_w = MAX(maxfltr_w, k);
        } else {
            maxfltr_w = MAX(maxfltr_w, 26);
        }
    }
    /* The stat table is not wrapped (by tshark) but filter is wrapped at the width of the stats table
    *  (which currently = borderlen); however, if the filter width exceeds the table width and the
    *  table width is less than 102 bytes, set borderlen to the lesser of the max filter width and 102.
    *  The filters will wrap at the lesser of borderlen-2 and the last space in the filter.
    *  NOTE: 102 is the typical size of a user window when the font is fixed width (e.g., COURIER 10).
    *  XXX: A pref could be added to change the max width from the default size of 102. */
    if (maxfltr_w > borderlen && borderlen < 102)
            borderlen = MIN(maxfltr_w, 102);

    /* Prevent double right border by adding a space */
    if (borderlen-tabrow_w == 1)
        borderlen++;

    /* Display the top border */
    printf("\n");
    for (i=0; i<borderlen; i++)
        printf("=");

    spaces = (char *)g_malloc(borderlen+1);
    for (i=0; i<borderlen; i++)
        spaces[i] = ' ';
    spaces[borderlen] = '\0';

    spaces_s = &spaces[16];
    printf("\n| IO Statistics%s|\n", spaces_s);
    spaces_s = &spaces[2];
    printf("|%s|\n", spaces_s);

    if (invl_prec == 0) {
        invl_fmt = g_strconcat("%", dur_mag_s, "u", NULL);
        full_fmt = g_strconcat("| Duration: ", invl_fmt, ".%6u secs%s|\n", NULL);
        spaces_s = &spaces[25 + dur_mag];
        printf(full_fmt, dur_secs_orig, dur_nsecs_orig, spaces_s);
        g_free(full_fmt);
        full_fmt = g_strconcat("| Interval: ", invl_fmt, " secs%s|\n", NULL);
        spaces_s = &spaces[18 + dur_mag];
        printf(full_fmt, (guint32)(interval/G_GUINT64_CONSTANT(1000000)), spaces_s);
    } else {
        snprintf(invl_prec_s, 3, "%u", invl_prec);
        invl_fmt = g_strconcat("%", dur_mag_s, "u.%0", invl_prec_s, "u", NULL);
        full_fmt = g_strconcat("| Duration: ", invl_fmt, " secs%s|\n", NULL);
        spaces_s = &spaces[19 + dur_mag + invl_prec];
        printf(full_fmt, dur_secs, dur_nsecs/(int)dv, spaces_s);
        g_free(full_fmt);

        full_fmt = g_strconcat("| Interval: ", invl_fmt, " secs%s|\n", NULL);
        spaces_s = &spaces[19 + dur_mag + invl_prec];
        printf(full_fmt, (guint32)(interval/G_GUINT64_CONSTANT(1000000)),
               (guint32)((interval%G_GUINT64_CONSTANT(1000000))/dv), spaces_s);
    }
    g_free(full_fmt);

    spaces_s = &spaces[2];
    printf("|%s|\n", spaces_s);

    /* Display the list of filters and their column numbers vertically */
    printf("| Col");
    for (j=0; j<num_cols; j++) {
        printf((j == 0 ? "%2u: " : "|    %2u: "), j+1);
        if (!iot->filters[j]) {
            /*
            * An empty (no filter) comma field was specified */
            spaces_s = &spaces[16 + 10];
            printf("Frames and bytes%s|\n", spaces_s);
        } else {
            filter = iot->filters[j];
            len_filt = (unsigned int) strlen(filter);

            /* If the width of the widest filter exceeds the width of the stat table, borderlen has
            *  been set to 102 bytes above and filters wider than 102 will wrap at 91 bytes. */
            if (len_filt+11 <= borderlen) {
                printf("%s", filter);
                if (len_filt+11 <= borderlen) {
                    spaces_s = &spaces[len_filt + 10];
                    printf("%s", spaces_s);
                }
                printf("|\n");
            } else {
                gchar *sfilter1, *sfilter2;
                const gchar *pos;
                gsize len;
                unsigned int next_start, max_w = borderlen-11;

                do {
                    if (len_filt > max_w) {
                        sfilter1 = g_strndup(filter, (gsize) max_w);
                        /*
                        * Find the pos of the last space in sfilter1. If a space is found, set
                        * sfilter2 to the string prior to that space and print it; otherwise, wrap
                        * the filter at max_w. */
                        pos = g_strrstr(sfilter1, " ");
                        if (pos) {
                            len = (gsize)(pos-sfilter1);
                            next_start = (unsigned int) len+1;
                        } else {
                            len = (gsize) strlen(sfilter1);
                            next_start = (unsigned int)len;
                        }
                        sfilter2 = g_strndup(sfilter1, len);
                        printf("%s%s|\n", sfilter2, &spaces[len+10]);
                        g_free(sfilter1);
                        g_free(sfilter2);

                        printf("|        ");
                        filter = &filter[next_start];
                        len_filt = (unsigned int) strlen(filter);
                    } else {
                        printf("%s%s|\n", filter, &spaces[strlen(filter)+10]);
                        break;
                    }
                } while (1);
            }
        }
    }

    printf("|-");
    for (i=0; i<borderlen-3; i++) {
        printf("-");
    }
    printf("|\n");

    /* Display spaces above "Interval (s)" label */
    spaces_s = &spaces[borderlen-(invl_col_w-2)];
    printf("|%s|", spaces_s);

    /* Display column number headers */
    for (j=0; j<num_cols; j++) {
        item = stat_cols[j];
        if (item->calc_type == CALC_TYPE_FRAMES_AND_BYTES)
            spaces_s = &spaces[borderlen - (col_w[j].fr + col_w[j].val)] - 3;
        else if (item->calc_type == CALC_TYPE_FRAMES)
            spaces_s = &spaces[borderlen - col_w[j].fr];
        else
            spaces_s = &spaces[borderlen - col_w[j].val];

        printf("%-2d%s|", j+1, spaces_s);
    }
    if (tabrow_w < borderlen) {
        filler_s = &spaces[tabrow_w+1];
        printf("%s|", filler_s);
    }

    k = 11;
    switch (timestamp_get_type()) {
    case TS_ABSOLUTE:
        printf("\n| Time    ");
        break;
    case TS_ABSOLUTE_WITH_YMD:
    case TS_ABSOLUTE_WITH_YDOY:
    case TS_UTC_WITH_YMD:
    case TS_UTC_WITH_YDOY:
        printf("\n| Date and time");
        k = 16;
        break;
    case TS_RELATIVE:
    case TS_NOT_SET:
        printf("\n| Interval");
        break;
    default:
        break;
    }

    spaces_s = &spaces[borderlen-(invl_col_w-k)];
    printf("%s|", spaces_s);

    /* Display the stat label in each column */
    for (j=0; j<num_cols; j++) {
        type = stat_cols[j]->calc_type;
        if (type == CALC_TYPE_FRAMES) {
            printcenter (calc_type_table[type].func_name, col_w[j].fr, numpad);
        } else if (type == CALC_TYPE_FRAMES_AND_BYTES) {
            printcenter ("Frames", col_w[j].fr, numpad);
            printcenter ("Bytes", col_w[j].val, numpad);
        } else {
            printcenter (calc_type_table[type].func_name, col_w[j].val, numpad);
        }
    }
    if (filler_s)
        printf("%s|", filler_s);
    printf("\n|-");

    for (i=0; i<tabrow_w-3; i++)
        printf("-");
    printf("|");

    if (tabrow_w < borderlen)
        printf("%s|", &spaces[tabrow_w+1]);

    printf("\n");
    t = 0;
    if (invl_prec == 0 && dur_mag == 1)
        full_fmt = g_strconcat("|  ", invl_fmt, " <> ", invl_fmt, "  |", NULL);
    else
        full_fmt = g_strconcat("| ", invl_fmt, " <> ", invl_fmt, " |", NULL);

    if (interval == 0 || duration == 0) {
        num_rows = 0;
    } else {
        num_rows = (unsigned int)(duration/interval) + ((unsigned int)(duration%interval) > 0 ? 1 : 0);
    }

    /* Load item_in_column with the first item in each column */
    item_in_column = (io_stat_item_t **)g_malloc(sizeof(io_stat_item_t *) * num_cols);
    for (j=0; j<num_cols; j++) {
        item_in_column[j] = stat_cols[j];
    }

    /* Display the table values
    *
    * The outer loop is for time interval rows and the inner loop is for stat column items.*/
    for (i=0; i<num_rows; i++) {

        if (i == num_rows-1)
            last_row = TRUE;

        /* Compute the interval for this row */
        if (!last_row) {
            invl_end = t + interval;
        } else {
            invl_end = duration;
        }

        /* Patch for Absolute Time */
        /* XXX - has a Y2.038K problem with 32-bit time_t */
        the_time = (time_t)(iot->start_time + (t/G_GUINT64_CONSTANT(1000000)));

        /* Display the interval for this row */
        switch (timestamp_get_type()) {
        case TS_ABSOLUTE:
          tm_time = localtime(&the_time);
          if (tm_time != NULL) {
            printf("| %02d:%02d:%02d |",
               tm_time->tm_hour,
               tm_time->tm_min,
               tm_time->tm_sec);
          } else
            printf("| XX:XX:XX |");
          break;

        case TS_ABSOLUTE_WITH_YMD:
          tm_time = localtime(&the_time);
          if (tm_time != NULL) {
            printf("| %04d-%02d-%02d %02d:%02d:%02d |",
               tm_time->tm_year + 1900,
               tm_time->tm_mon + 1,
               tm_time->tm_mday,
               tm_time->tm_hour,
               tm_time->tm_min,
               tm_time->tm_sec);
          } else
            printf("| XXXX-XX-XX XX:XX:XX |");
          break;

        case TS_ABSOLUTE_WITH_YDOY:
          tm_time = localtime(&the_time);
          if (tm_time != NULL) {
            printf("| %04d/%03d %02d:%02d:%02d |",
               tm_time->tm_year + 1900,
               tm_time->tm_yday + 1,
               tm_time->tm_hour,
               tm_time->tm_min,
               tm_time->tm_sec);
          } else
            printf("| XXXX/XXX XX:XX:XX |");
          break;

        case TS_UTC:
          tm_time = gmtime(&the_time);
          if (tm_time != NULL) {
            printf("| %02d:%02d:%02d |",
               tm_time->tm_hour,
               tm_time->tm_min,
               tm_time->tm_sec);
          } else
            printf("| XX:XX:XX |");
          break;

        case TS_UTC_WITH_YMD:
          tm_time = gmtime(&the_time);
          if (tm_time != NULL) {
            printf("| %04d-%02d-%02d %02d:%02d:%02d |",
               tm_time->tm_year + 1900,
               tm_time->tm_mon + 1,
               tm_time->tm_mday,
               tm_time->tm_hour,
               tm_time->tm_min,
               tm_time->tm_sec);
          } else
            printf("| XXXX-XX-XX XX:XX:XX |");
          break;

        case TS_UTC_WITH_YDOY:
          tm_time = gmtime(&the_time);
          if (tm_time != NULL) {
            printf("| %04d/%03d %02d:%02d:%02d |",
               tm_time->tm_year + 1900,
               tm_time->tm_yday + 1,
               tm_time->tm_hour,
               tm_time->tm_min,
               tm_time->tm_sec);
          } else
            printf("| XXXX/XXX XX:XX:XX |");
          break;

        case TS_RELATIVE:
        case TS_NOT_SET:
          if (invl_prec == 0) {
              if (last_row) {
                  int maxw;
                  maxw = dur_mag >= 3 ? dur_mag+1 : 3;
                  g_free(full_fmt);
                  snprintf(dur_mag_s, 3, "%u", maxw);
                  full_fmt = g_strconcat( dur_mag == 1 ? "|  " : "| ",
                                          invl_fmt, " <> ", "%-",
                                          dur_mag_s, "s|", NULL);
                  printf(full_fmt, (guint32)(t/G_GUINT64_CONSTANT(1000000)), "Dur");
              } else {
                  printf(full_fmt, (guint32)(t/G_GUINT64_CONSTANT(1000000)),
                         (guint32)(invl_end/G_GUINT64_CONSTANT(1000000)));
              }
          } else {
              printf(full_fmt, (guint32)(t/G_GUINT64_CONSTANT(1000000)),
                     (guint32)(t%G_GUINT64_CONSTANT(1000000) / dv),
                     (guint32)(invl_end/G_GUINT64_CONSTANT(1000000)),
                     (guint32)(invl_end%G_GUINT64_CONSTANT(1000000) / dv));
          }
          break;
     /* case TS_DELTA:
        case TS_DELTA_DIS:
        case TS_EPOCH:
            are not implemented */
        default:
          break;
        }

        /* Display stat values in each column for this row */
        for (j=0; j<num_cols; j++) {
            fmt = fmts[j];
            item = item_in_column[j];

            if (item) {
                switch (item->calc_type) {
                case CALC_TYPE_FRAMES:
                    printf(fmt, item->frames);
                    break;
                case CALC_TYPE_BYTES:
                case CALC_TYPE_COUNT:
                    printf(fmt, item->counter);
                    break;
                case CALC_TYPE_FRAMES_AND_BYTES:
                    printf(fmt, item->frames, item->counter);
                    break;

                case CALC_TYPE_SUM:
                case CALC_TYPE_MIN:
                case CALC_TYPE_MAX:
                    ftype = proto_registrar_get_ftype(stat_cols[j]->hf_index);
                    switch (ftype) {
                    case FT_FLOAT:
                        printf(fmt, item->float_counter);
                        break;
                    case FT_DOUBLE:
                        printf(fmt, item->double_counter);
                        break;
                    case FT_RELATIVE_TIME:
                        item->counter = (item->counter + G_GUINT64_CONSTANT(500)) / G_GUINT64_CONSTANT(1000);
                        printf(fmt,
                               (int)(item->counter/G_GUINT64_CONSTANT(1000000)),
                               (int)(item->counter%G_GUINT64_CONSTANT(1000000)));
                        break;
                    default:
                        printf(fmt, item->counter);
                        break;
                    }
                    break;

                case CALC_TYPE_AVG:
                    num = item->num;
                    if (num == 0)
                        num = 1;
                    ftype = proto_registrar_get_ftype(stat_cols[j]->hf_index);
                    switch (ftype) {
                    case FT_FLOAT:
                        printf(fmt, item->float_counter/num);
                        break;
                    case FT_DOUBLE:
                        printf(fmt, item->double_counter/num);
                        break;
                    case FT_RELATIVE_TIME:
                        item->counter = ((item->counter / (guint64)num) + G_GUINT64_CONSTANT(500)) / G_GUINT64_CONSTANT(1000);
                        printf(fmt,
                               (int)(item->counter/G_GUINT64_CONSTANT(1000000)),
                               (int)(item->counter%G_GUINT64_CONSTANT(1000000)));
                        break;
                    default:
                        printf(fmt, item->counter / (guint64)num);
                        break;
                    }
                    break;

                case CALC_TYPE_LOAD:
                    ftype = proto_registrar_get_ftype(stat_cols[j]->hf_index);
                    switch (ftype) {
                    case FT_RELATIVE_TIME:
                        if (!last_row) {
                            printf(fmt,
                                (int) (item->counter/interval),
                                   (int)((item->counter%interval)*G_GUINT64_CONSTANT(1000000) / interval));
                        } else {
                            printf(fmt,
                                   (int) (item->counter/(invl_end-t)),
                                   (int)((item->counter%(invl_end-t))*G_GUINT64_CONSTANT(1000000) / (invl_end-t)));
                        }
                        break;
                    }
                    break;
                }

                if (last_row) {
                    g_free(fmt);
                } else {
                    item_in_column[j] = item_in_column[j]->next;
                }
            } else {
                printf(fmt, (guint64)0, (guint64)0);
            }
        }
        if (filler_s)
            printf("%s|", filler_s);
        printf("\n");
        t += interval;

    }
    for (i=0; i<borderlen; i++) {
        printf("=");
    }
    printf("\n");
    g_free(iot->items);
    g_free(iot->max_vals);
    g_free(iot->max_frame);
    g_free(iot);
    g_free(col_w);
    g_free(invl_fmt);
    g_free(full_fmt);
    g_free(fmts);
    g_free(spaces);
    g_free(stat_cols);
    g_free(item_in_column);
}


static void
register_io_tap(io_stat_t *io, unsigned int i, const char *filter)
{
    GString *error_string;
    const char *flt;
    int j;
    size_t namelen;
    const char *p, *parenp;
    char *field;
    header_field_info *hfi;

    io->items[i].prev       = &io->items[i];
    io->items[i].next       = NULL;
    io->items[i].parent     = io;
    io->items[i].start_time = 0;
    io->items[i].calc_type  = CALC_TYPE_FRAMES_AND_BYTES;
    io->items[i].frames     = 0;
    io->items[i].counter    = 0;
    io->items[i].num        = 0;

    io->filters[i] = filter;
    flt = filter;

    field = NULL;
    hfi = NULL;
    for (j=0; calc_type_table[j].func_name; j++) {
        namelen = strlen(calc_type_table[j].func_name);
        if (filter && strncmp(filter, calc_type_table[j].func_name, namelen) == 0) {
            io->items[i].calc_type = calc_type_table[j].calc_type;
            io->items[i].colnum = i;
            if (*(filter+namelen) == '(') {
                p = filter+namelen+1;
                parenp = strchr(p, ')');
                if (!parenp) {
                    fprintf(stderr,
                        "\ntshark: Closing parenthesis missing from calculated expression.\n");
                    exit(10);
                }

                if (io->items[i].calc_type == CALC_TYPE_FRAMES || io->items[i].calc_type == CALC_TYPE_BYTES) {
                    if (parenp != p) {
                        fprintf(stderr,
                            "\ntshark: %s does not require or allow a field name within the parens.\n",
                            calc_type_table[j].func_name);
                        exit(10);
                    }
                } else {
                    if (parenp == p) {
                            /* bail out if a field name was not specified */
                            fprintf(stderr, "\ntshark: You didn't specify a field name for %s(*).\n",
                                calc_type_table[j].func_name);
                            exit(10);
                    }
                }

                field = (char *)g_malloc(parenp-p+1);
                memcpy(field, p, parenp-p);
                field[parenp-p] = '\0';
                flt = parenp + 1;
                if (io->items[i].calc_type == CALC_TYPE_FRAMES || io->items[i].calc_type == CALC_TYPE_BYTES)
                    break;
                hfi = proto_registrar_get_byname(field);
                if (!hfi) {
                    fprintf(stderr, "\ntshark: There is no field named '%s'.\n",
                        field);
                    g_free(field);
                    exit(10);
                }

                io->items[i].hf_index = hfi->id;
                break;
            }
        } else {
            if (io->items[i].calc_type == CALC_TYPE_FRAMES || io->items[i].calc_type == CALC_TYPE_BYTES)
                flt = "";
            io->items[i].colnum = i;
        }
    }
    if (hfi && !(io->items[i].calc_type == CALC_TYPE_BYTES ||
                 io->items[i].calc_type == CALC_TYPE_FRAMES ||
                 io->items[i].calc_type == CALC_TYPE_FRAMES_AND_BYTES)) {
        /* check that the type is compatible */
        switch (hfi->type) {
        case FT_UINT8:
        case FT_UINT16:
        case FT_UINT24:
        case FT_UINT32:
        case FT_UINT64:
        case FT_INT8:
        case FT_INT16:
        case FT_INT24:
        case FT_INT32:
        case FT_INT64:
            /* these types support all calculations */
            break;
        case FT_FLOAT:
        case FT_DOUBLE:
            /* these types only support SUM, COUNT, MAX, MIN, AVG */
            switch (io->items[i].calc_type) {
            case CALC_TYPE_SUM:
            case CALC_TYPE_COUNT:
            case CALC_TYPE_MAX:
            case CALC_TYPE_MIN:
            case CALC_TYPE_AVG:
                break;
            default:
                fprintf(stderr,
                    "\ntshark: %s is a float field, so %s(*) calculations are not supported on it.",
                    field,
                    calc_type_table[j].func_name);
                exit(10);
            }
            break;
        case FT_RELATIVE_TIME:
            /* this type only supports SUM, COUNT, MAX, MIN, AVG, LOAD */
            switch (io->items[i].calc_type) {
            case CALC_TYPE_SUM:
            case CALC_TYPE_COUNT:
            case CALC_TYPE_MAX:
            case CALC_TYPE_MIN:
            case CALC_TYPE_AVG:
            case CALC_TYPE_LOAD:
                break;
            default:
                fprintf(stderr,
                    "\ntshark: %s is a relative-time field, so %s(*) calculations are not supported on it.",
                    field,
                    calc_type_table[j].func_name);
                exit(10);
            }
            break;
        default:
            /*
             * XXX - support all operations on floating-point
             * numbers?
             */
            if (io->items[i].calc_type != CALC_TYPE_COUNT) {
                fprintf(stderr,
                    "\ntshark: %s doesn't have integral values, so %s(*) "
                    "calculations are not supported on it.\n",
                    field,
                    calc_type_table[j].func_name);
                exit(10);
            }
            break;
        }
    }
    g_free(field);

    error_string = register_tap_listener("frame", &io->items[i], flt, TL_REQUIRES_PROTO_TREE, NULL,
                                       iostat_packet, i ? NULL : iostat_draw, NULL);
    if (error_string) {
        g_free(io->items);
        g_free(io);
        fprintf(stderr, "\ntshark: Couldn't register io,stat tap: %s\n",
            error_string->str);
        g_string_free(error_string, TRUE);
        exit(1);
    }
}

static void
iostat_init(const char *opt_arg, void *userdata _U_)
{
    gdouble interval_float;
    guint32 idx = 0;
    unsigned int i;
    io_stat_t *io;
    const gchar *filters, *str, *pos;

    if ((*(opt_arg+(strlen(opt_arg)-1)) == ',') ||
        (sscanf(opt_arg, "io,stat,%lf%n", &interval_float, (int *)&idx) != 1) ||
        (idx < 8)) {
        fprintf(stderr, "\ntshark: invalid \"-z io,stat,<interval>[,<filter>][,<filter>]...\" argument\n");
        exit(1);
    }

    filters = opt_arg+idx;
    if (*filters) {
        if (*filters != ',') {
            /* For locale's that use ',' instead of '.', the comma might
             * have been consumed during the floating point conversion. */
            --filters;
            if (*filters != ',') {
                fprintf(stderr, "\ntshark: invalid \"-z io,stat,<interval>[,<filter>][,<filter>]...\" argument\n");
                exit(1);
            }
        }
    } else
        filters = NULL;

    switch (timestamp_get_type()) {
    case TS_DELTA:
    case TS_DELTA_DIS:
    case TS_EPOCH:
        fprintf(stderr, "\ntshark: invalid -t operand. io,stat only supports -t <r|a|ad|adoy|u|ud|udoy>\n");
        exit(1);
    default:
        break;
    }

    io = g_new(io_stat_t, 1);

    /* If interval is 0, calculate statistics over the whole file by setting the interval to
    *  G_MAXUINT64 */
    if (interval_float == 0) {
        io->interval = G_MAXUINT64;
        io->invl_prec = 0;
    } else {
        /* Set interval to the number of us rounded to the nearest integer */
        io->interval = (guint64)(interval_float * 1000000.0 + 0.5);
        /*
        * Determine what interval precision the user has specified */
        io->invl_prec = 6;
        for (i=10; i<10000000; i*=10) {
            if (io->interval%i > 0)
                break;
            io->invl_prec--;
        }
        if (io->invl_prec == 0) {
            /* The precision is zero but if the user specified one of more zeros after the decimal point,
               they want that many decimal places shown in the table for all time intervals except
               response time values such as smb.time which always have 6 decimal places of precision.
               This feature is useful in cases where for example the duration is 9.1, you specify an
               interval of 1 and the last interval becomes "9 <> 9". If the interval is instead set to
               1.1, the last interval becomes
               last interval is rounded up to value that is greater than the duration. */
            const gchar *invl_start = opt_arg+8;
            gchar *intv_end;
            int invl_len;

            intv_end = g_strstr_len(invl_start, -1, ",");
            invl_len = (int)(intv_end - invl_start);
            invl_start = g_strstr_len(invl_start, invl_len, ".");

            if (invl_start != NULL) {
                invl_len = (int)(intv_end - invl_start - 1);
                if (invl_len)
                    io->invl_prec = MIN(invl_len, 6);
            }
        }
    }
    if (io->interval < 1) {
        fprintf(stderr,
            "\ntshark: \"-z\" interval must be >=0.000001 seconds or \"0\" for the entire capture duration.\n");
        exit(10);
    }

    /* Find how many ',' separated filters we have */
    io->num_cols = 1;
    io->start_time = 0;

    if (filters && (*filters != '\0')) {
        /* Eliminate the first comma. */
        filters++;
        str = filters;
        while ((str = strchr(str, ','))) {
            io->num_cols++;
            str++;
        }
    }

    io->items     = g_new(io_stat_item_t, io->num_cols);
    io->filters   = (const char **)g_malloc(sizeof(char *) * io->num_cols);
    io->max_vals  = g_new(guint64, io->num_cols);
    io->max_frame = g_new(guint32, io->num_cols);

    for (i=0; i<io->num_cols; i++) {
        io->max_vals[i]  = 0;
        io->max_frame[i] = 0;
    }

    /* Register a tap listener for each filter */
    if ((!filters) || (filters[0] == 0)) {
        register_io_tap(io, 0, NULL);
    } else {
        gchar *filter;
        i = 0;
        str = filters;
        do {
            pos = (gchar*) strchr(str, ',');
            if (pos == str) {
                register_io_tap(io, i, NULL);
            } else if (pos == NULL) {
                str = (const char*) g_strstrip((gchar*)str);
                filter = g_strdup(str);
                if (*filter)
                    register_io_tap(io, i, filter);
                else
                    register_io_tap(io, i, NULL);
            } else {
                filter = (gchar *)g_malloc((pos-str)+1);
                (void) g_strlcpy( filter, str, (gsize) ((pos-str)+1));
                filter = g_strstrip(filter);
                register_io_tap(io, i, (char *) filter);
            }
            str = pos+1;
            i++;
        } while (pos);
    }
}

static stat_tap_ui iostat_ui = {
    REGISTER_STAT_GROUP_GENERIC,
    NULL,
    "io,stat",
    iostat_init,
    0,
    NULL
};

void
register_tap_listener_iostat(void)
{
    register_stat_tap_ui(&iostat_ui, NULL);
}
