package brn.sim.handler;

import java.io.File;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import jist.swans.Constants;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;
import jist.swans.net.NetMessage.Ip;
import jist.swans.trans.TransTcp;
import jist.swans.trans.TransTcp.TcpMessage;
import jist.swans.trans.TransTcp.TcpOptions;

public class TcpTrace {

  public static interface TcpTraceEvents {
    public void plotterCreated(PLOTTER plotter);
  }

  protected static class BooleanOut {
    public boolean value;
  }

  /* 4 minutes */
  public static final long NONREAL_LIVE_CONN_INTERVAL = 4 * 60 * Constants.SECOND;

  public static final int SEGS_TO_REMEMBER = 8;

  public static final int TCP_RENO = 0;
  public static final int TCP_SACK = 1;
  public static final int TCP_DSACK = 2;

  public static final int A2B = 1;
  public static final int B2A = 2;

  /* ACK types */
  public static final int NORMAL = 1; /* no retransmits, just advance */
  public static final int AMBIG = 2; /* segment ACKed was rexmitted */
  public static final int CUMUL = 3; /* doesn't advance */
  public static final int TRIPLE = 4; /* triple dupack */
  public static final int NOSAMP = 5; /*
                                       * covers retransmitted segs, no rtt
                                       * sample
                                       */

  public static final String RTT_DUMP_FILE_EXTENSION = "_rttraw.dat";
  public static final String RTT_GRAPH_FILE_EXTENSION = "_rtt.xpl";
  public static final String PLOT_FILE_EXTENSION = "_tsg.xpl";
  public static final String SEGSIZE_FILE_EXTENSION = "_ssize.xpl";
  public static final String OWIN_FILE_EXTENSION = "_owin.xpl";
  public static final String TLINE_FILE_EXTENSION = "_tline.xpl";
  public static final String THROUGHPUT_FILE_EXTENSION = "_tput.xpl";
  public static final String CONTENTS_FILE_EXTENSION = "_contents.dat";

  public class PLOTTER {
    // MFILE *fplot; /* the file that hold the plot */
    Tcb p2plast; /* the TCB that this goes with (if any) */
    long zerotime; /* first time stamp in this plot (see -z) */
    // String filename; /* redundant copy of name for debugging */
    boolean header_done; /* Flag indicating plotter header written to file */
    boolean axis_switched; /*
                             * Switch x & y axis types. (Needed for Time Line
                             * Charts, Default = FALSE)
                             */
    String title; /* Plotter title */
    String xlabel; /* Plotter x-axis label */
    String ylabel; /* Plotter y-axis label */
    StringBuilder content = new StringBuilder();
    String temp_color = null;

    public PLOTTER(String title, String xlabel, String ylabel, Tcb plast,
        boolean axis_switched) {
      this.title = title;
      this.xlabel = xlabel;
      this.ylabel = ylabel;
      this.p2plast = plast;
      this.axis_switched = axis_switched;
      /* Write the plotter header if not already written */
      if (!header_done)
        WritePlotHeader();
    }

    // void DoPlot(String fmt) {
    // content.append(fmt);
    // if (null != temp_color)
    // content.append(" ").append(temp_color);
    // content.append("\n");
    // }
    void CallDoPlot(String plot_cmd) {
      content.append(plot_cmd);
      if (null != temp_color) {
        content.append(" ").append(temp_color);
        temp_color = null;
      }
      content.append("\n");
    }

    void CallDoPlot(String plot_cmd, long t1, long x1) {
      /* Get the arguments from the variable list */
      if (axis_switched) {
        content.append(plot_cmd).append(" ").append(x1).append(" -").append(
            xp_timestamp(t1));
        if (null != temp_color) {
          content.append(" ").append(temp_color);
          temp_color = null;
        }
        content.append("\n");
      } else {
        content.append(plot_cmd).append(" ").append(xp_timestamp(t1)).append(
            " ").append(x1);
        if (null != temp_color) {
          content.append(" ").append(temp_color);
          temp_color = null;
        }
        content.append("\n");
      }
    }

    void CallDoPlot(String plot_cmd, long t1, long x1, long t2, long x2) {
      if (axis_switched) {
        content.append(plot_cmd).append(" ").append(x1).append(" -").append(
            xp_timestamp(t1)).append(" ").append(x2).append(" -").append(
            xp_timestamp(t2));
        if (null != temp_color) {
          content.append(" ").append(temp_color);
          temp_color = null;
        }
        content.append("\n");
      } else {
        content.append(plot_cmd).append(" ").append(xp_timestamp(t1)).append(
            " ").append(x1).append(" ").append(xp_timestamp(t2)).append(" ")
            .append(x2);
        if (null != temp_color) {
          content.append(" ").append(temp_color);
          temp_color = null;
        }
        content.append("\n");
      }
    }

    void WritePlotHeader() {
      if (axis_switched) {
        /* Header for the Time Line Charts */
        content.append("unsigned dtime\n");
      } else {
        /* Header for all other plots */
        /* graph coordinates... */
        /* X coord is timeval unless graph_time_zero is true */
        /* Y is signed except when it's a sequence number */
        /*
         * ugly hack -- unsigned makes the graphs hard to work with and is only
         * needed for the time sequence graphs
         */
        /* suggestion by Michele Clark at UNC - make them double instead */
        content.append((graph_time_zero ? "dtime " : "timeval ")
            + (ylabel.equals("sequence number") && !graph_seq_zero ? "double\n"
                : "signed\n"));
      }

      if (show_title) {
        // if (xplot_title_prefix)
        // content += "title\n"+ExpandFormat(xplot_title_prefix)+" "+title+"\n";
        // else
        content.append("title\n" + title + "\n");
      }

      content.append("xlabel\n" + xlabel + "\n");
      content.append("ylabel\n" + ylabel + "\n");

      /* Indicate that the header has now been written to the plotter file */
      header_done = true;
    }

    /*
     * Return a string suitable for use as a timestamp in the xplot output. sdo
     * fix: originally, we were just plotting to the accuracy of 1/10 ms this
     * was mostly to help keep the .xpl files from being too large. However, on
     * faster networks this just isn't enough, so we'll now use all 6 digits of
     * the microsecond counter. Note that there's no guarantee that the
     * timestamps are really all that accurate!
     */
    public String xp_timestamp(long time) {
      // int secs;
      // int usecs;
      // int decimal;

      /* see if we're graphing from "0" OR if the axis type is switched */
      if (graph_time_zero || axis_switched) {

        if (0 == zerotime) {
          /* set "zero point" */
          zerotime = time;
        }

        /* (in)sanity check */
        if ((time < zerotime)) {
          time = 0;
          throw new Error(
              "Internal error in plotting (plot file '%s')...\n"
                  + "ZERO-based X-axis plotting requested and elements are not plotted in\n"
                  + "increasing time order.  Try without the '-z' flag\n");
        } else {
          /* computer offset from first plotter point */
          time -= zerotime;
        }
      }

      /* calculate time components */
      // secs = (int) (time / Constants.SECOND);
      // usecs = (int) (time / Constants.MICRO_SECOND);
      // decimal = usecs;
      /* use one of 4 rotating static buffers (for multiple calls per printf) */
      // TODO
      // snprintf(pbuf,sizeof(bufs[bufix]),"%u.%06u",secs,decimal);
      Formatter f = new Formatter(Locale.US);
      f.format("%.6f", time / (double) Constants.SECOND);
      return f.toString();
//      return (String.valueOf(time / (double) Constants.SECOND));
    }

    public String getContent() {
      return this.content.toString();
    }

  }

  public static class PLINE {
    String color;
    String label;
    long last_y;
    long last_time;
    PLOTTER plotter;
    boolean labelled;
  }

  // public static class ptp_snap {
  // int skew; /* Skew of the AVL tree node */
  // TcpPairAddrBlock addr_pair; /* just a copy */
  // ptp_snap left;
  // ptp_snap right; /* Left and right trees of the AVL node */
  // Object ptp;
  // }

  // public static class ptp_ptr {
  // ptp_ptr next;
  // ptp_ptr prev;
  // ptp_snap from;
  // TcpPair ptp;
  // }

  public static class segment {
    int seq_firstbyte; /* intber of first byte */
    int seq_lastbyte; /* intber of last byte */
    char retrans; /* retransmit count */
    int acked; /* how MANY times has has it been acked? */
    long time; /* time the segment was sent */
    segment next;
    segment prev;
  }

  public static class quadrant {
    segment seglist_head;
    segment seglist_tail;
    boolean full;
    quadrant prev;
    quadrant next;
  }

  public static class seqspace {
    quadrant[] pquad = new quadrant[4];
  }

  public static class Tcb {
    /* parent pointer */
    TcpPair ptp;
    Tcb ptwin;

    /* TCP information */
    int ack;
    int seq;
    int syn;
    int fin;
    int windowend;
    long time;

    /* TCP options */
    int mss;
    boolean f1323_ws; /* did he request 1323 window scaling? */
    boolean f1323_ts; /* did he request 1323 timestamps? */
    boolean fsack_req; /* did he request SACKs? */
    char window_scale;

    /*
     * If we are using window scaling, have we adjusted the win_min field from
     * the non-scaled window size that appeared in the SYN packet??
     */
    boolean window_stats_updated_for_scaling;
    long win_scaled_pkts; /* Used to calculate avg win adv */

    /* statistics added */
    long data_bytes;
    long data_pkts;
    long data_pkts_push;
    long unique_bytes; /* bytes sent (-FIN/SYN), excluding rexmits */
    long rexmit_bytes;
    long rexmit_pkts;
    long ack_pkts;
    long pureack_pkts; /* mallman - pure acks, no data */
    long win_max;
    long win_min;
    long win_tot;
    long win_last; /* last advertised window size */
    long win_zero_ct;
    long packets;
    char syn_count;
    char fin_count;
    char reset_count; /* resets SENT */
    long min_seg_size;
    long max_seg_size;
    long out_order_pkts; /* out of order packets */
    long sacks_sent; /* sacks returned */
    long ipv6_segments; /* how many segments were ipv6? */

    /* stats on urgent data */
    long urg_data_bytes;
    long urg_data_pkts;

    /*
     * Statistics to store the number of Zero window probes seen and the total
     * number of bytes spent for it.
     */
    long num_zwnd_probes;
    long zwnd_probe_bytes;

    /* stats on sequence numbers */

    int min_seq; /* smallest seq number seen */
    int max_seq; /* largest seq number seen */
    int latest_seq; /* most recent seq number seen */

    /* stats on sequence space wrap arounds */
    int quad1, quad2, quad3, quad4; /* was every quadrant visited */
    int seq_wrap_count; /* wrap count */

    /* hardware duplicate detection */
    public static class HardwareDups {
      int hwdup_seq; /* sequence number */
      short hwdup_id; /* IP ID */
      long hwdup_packnum; /* packet number */
    }

    HardwareDups[] hardware_dups = new HardwareDups[SEGS_TO_REMEMBER];
    long num_hardware_dups;
    int hardware_dups_ix;

    /* did I detect any "bad" tcp behavior? */
    /* at present, this means: */
    /* - SYNs retransmitted with different sequence numbers */
    /* - FINs retransmitted with different sequence numbers */
    boolean bad_behavior;

    /* added for initial window stats (for Mallman) */
    long initialwin_bytes; /* initial window (in bytes) */
    long initialwin_segs; /* initial window (in segments) */
    boolean data_acked; /* has any non-SYN data been acked? */

    /* added for (estimated) congestions window stats (for Mallman) */
    long owin_max;
    long owin_min;
    long owin_tot;
    long owin_wavg; /* weighted owin */
    long owin_count;
    long previous_owin_sample;
    long previous_owin_sample_time;

    /* RTT stats for singly-transmitted segments */
    double rtt_last; /* RTT as of last good ACK (microseconds) */
    long rtt_min;
    long rtt_max;
    double rtt_sum; /* for averages */
    double rtt_sum2; /* sum of squares, for stdev */
    long rtt_count; /* for averages */
    /* RTT stats for multiply-transmitted segments */
    long rtt_min_last;
    long rtt_max_last;
    double rtt_sum_last; /* from last transmission, for averages */
    double rtt_sum2_last; /* sum of squares, for stdev */
    long rtt_count_last; /* from last transmission, for averages */

    /*
     * To keep track of stats for FULL SIZE segments Simple heuristic : We shall
     * treat the largest packet, so far seen as the "full size" packet and
     * collect stats. accordingly. Upon seeing a bigger packet, we flush all
     * stats. collected incorrectly and begin all over again
     */
    long rtt_full_size;

    long rtt_full_min;
    long rtt_full_max;
    double rtt_full_sum; /* for averages */
    double rtt_full_sum2; /* sum of squares for stdev */
    long rtt_full_count; /* for averages */

    long rtt_3WHS; /* rtt value used to seed RTO timers */

    /* ACK Counters */
    long rtt_amback; /* ambiguous ACK */
    long rtt_cumack; /* segments only cumulativly ACKed */
    long rtt_nosample; /* segments ACKED, but after retransmission */
    /* of earlier segments, so sample isn't */
    /* valid */
    long rtt_unkack; /* unknown ACKs ??? */
    long rtt_dupack; /* duplicate ACKs */
    long rtt_triple_dupack; /* triple duplicate ACKs */
    /* retransmission information */
    seqspace ss; /* the sequence space */
    long retr_max; /* maximum retransmissions ct */
    long retr_min_tm; /* minimum retransmissions time */
    long retr_max_tm; /* maximum retransmissions time */
    double retr_tm_sum; /* for averages */
    double retr_tm_sum2; /* sum of squares, for stdev */
    long retr_tm_count; /* for averages */

    /* Instantaneous throughput info */
    long thru_firsttime; /* time of first packet this interval */
    long thru_bytes; /* number of bytes this interval */
    long thru_pkts; /* number of packets this interval */
    PLOTTER thru_plotter; /* throughput data dump file */
    long thru_lasttime; /* time of previous segment */
    PLINE thru_avg_line; /* average throughput line */
    PLINE thru_inst_line; /* instantaneous throughput line */

    /* data transfer time stamps - mallman */
    long first_data_time;
    long last_data_time;

    /* Time Sequence Graph info for this one */
    PLOTTER tsg_plotter;
    String tsg_plotfile;

    /* Time Line Graph */
    PLOTTER tline_plotter;

    /* Dumped RTT samples */
    File rtt_dump_file;

    /* Extracted stream contents */
    File extr_contents_file;
    long trunc_bytes; /* data bytes not see due to trace file truncation */
    long trunc_segs; /* segments with trunc'd bytes */
    int extr_lastseq; /* last sequence number we stored */
    int extr_initseq; /*
                       * initial sequence number (same as SYN unless we missed
                       * it)
                       */

    /* RTT Graph info for this one */
    PLOTTER rtt_plotter;
    PLINE rtt_line;

    /* Segment size graph */
    PLOTTER segsize_plotter;
    PLINE segsize_line;
    PLINE segsize_avg_line;

    /* Congestion window graph */
    PLOTTER owin_plotter;
    PLINE owin_line;
    PLINE rwin_line;
    PLINE owin_avg_line;
    PLINE owin_wavg_line;

    /* for tracking unidirectional idle time */
    long last_time; /* last packet SENT from this side */
    long idle_max; /* maximum idle time observed (usecs) */

    /* for looking for interesting SACK blocks */
    long num_sacks;
    long max_sack_blocks;
    long num_dsacks;

    /* for computing LEAST (see FAQ) */
    int tcp_strain;
    long LEAST;
    boolean in_rto;
    long recovered, recovered_orig, rto_segment, lastackno;
    long event_retrans, event_dupacks;

    /* host name letter(s) */
    String host_letter;

    public String toString() {
      return (this == ptp.a2b ? ptp.a_endpoint + " => " + ptp.b_endpoint :
        ptp.b_endpoint + " => " + ptp.a_endpoint);
    }
  }

  public static class TcpPairAddrBlock {
    public TcpPairAddrBlock(Ip ipMsg, TcpMessage tcpMsg) {
      boolean is = (ipMsg.getSrc().hashCode() < ipMsg.getDst().hashCode());
      this.a_address = (is ? ipMsg.getSrc() : ipMsg.getDst());
      this.b_address = (!is ? ipMsg.getSrc() : ipMsg.getDst());
      this.a_port = (is ? tcpMsg.getSrcPort() : tcpMsg.getDstPort());
      this.b_port = (!is ? tcpMsg.getSrcPort() : tcpMsg.getDstPort());
    }

    NetAddress a_address;
    NetAddress b_address;
    int a_port;
    int b_port;

    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result
          + ((a_address == null) ? 0 : a_address.hashCode());
      result = prime * result + a_port;
      result = prime * result
          + ((b_address == null) ? 0 : b_address.hashCode());
      result = prime * result + b_port;
      return result;
    }

    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      final TcpPairAddrBlock other = (TcpPairAddrBlock) obj;
      if (a_address == null) {
        if (other.a_address != null)
          return false;
      } else if (!a_address.equals(other.a_address))
        return false;
      if (a_port != other.a_port)
        return false;
      if (b_address == null) {
        if (other.b_address != null)
          return false;
      } else if (!b_address.equals(other.b_address))
        return false;
      if (b_port != other.b_port)
        return false;
      return true;
    }
  }

  public static class TcpPair {
    /* are we ignoring this one?? */
    boolean ignore_pair;

    /* inactive (previous instance of current connection */
    boolean inactive;

    /* endpoint identification */
    TcpPairAddrBlock addr_pair;

    /* connection naming information */
    String a_hostname;
    String b_hostname;
    String a_portname;
    String b_portname;
    String a_endpoint;
    String b_endpoint;

    /* connection information */
    long first_time;
    long last_time;
    long packets;
    Tcb a2b = new Tcb();
    Tcb b2a = new Tcb();

    // /* module-specific structures, if requested */
    // void **pmod_info;

    /* which file this connection is from */
    // String filename;
    public int whichDir(Ip ipMsg, TcpMessage tcpMsg) {
      if (addr_pair.a_address.equals(ipMsg.getSrc())
          && addr_pair.b_address.equals(ipMsg.getDst())
          && addr_pair.a_port == tcpMsg.getSrcPort()
          && addr_pair.b_port == tcpMsg.getDstPort())
        return A2B;
      if (addr_pair.a_address.equals(ipMsg.getDst())
          && addr_pair.b_address.equals(ipMsg.getSrc())
          && addr_pair.a_port == tcpMsg.getDstPort()
          && addr_pair.b_port == tcpMsg.getSrcPort())
        return B2A;
      throw new RuntimeException("asdf?");
//      return 0;
    }
  }

  private Map ptp_hashtable = new HashMap();

  private TcpTraceEvents events;

  public void addEventListener(TcpTraceEvents events) {
    if (null != this.events)
      throw new Error("TODO");
    this.events = events;
  }

  /*
   * SEQCMP - sequence space comparator This handles sequence space wrap-around.
   * Overlow/Underflow makes the result below correct ( -, 0, + ) for any a, b
   * in the sequence space. Results: result implies - a < b 0 a = b + a > b
   */
  protected int SEQCMP(int a, int b) {
    return (int) ((long) (a) - (long) (b));
  }

  protected boolean SEQ_LESSTHAN(int a, int b) {
    return (SEQCMP(a, b) < 0);
  }

  protected boolean SEQ_GREATERTHAN(int a, int b) {
    return (SEQCMP(a, b) > 0);
  }

  protected static final int QUADSIZE = (0x40000000);

  protected int QUADNUM(int seq) {
    return (int) ((seq >> 30) + 1);
  }

  protected boolean IN_Q1(int seq) {
    return (QUADNUM(seq) == 1);
  }

  protected boolean IN_Q2(int seq) {
    return (QUADNUM(seq) == 2);
  }

  protected boolean IN_Q3(int seq) {
    return (QUADNUM(seq) == 3);
  }

  protected boolean IN_Q4(int seq) {
    return (QUADNUM(seq) == 4);
  }

  protected int FIRST_SEQ(int quadnum) {
    return (QUADSIZE * (quadnum - 1));
  }

  protected int LAST_SEQ(int quadnum) {
    return ((QUADSIZE * quadnum) - 1); /* bug fix by Priya */
  }

  protected boolean BOUNDARY(int beg, int fin) {
    return (QUADNUM((beg)) != QUADNUM((fin)));
  }

  public TcpPair findTTP(NetMessage.Ip ipMsg, TransTcp.TcpMessage tcpMsg,
      long current_time) {
    // ptp_snap pptph_head = null;
    // ptp_snap ptph;
    // int depth = 0;
    // int conn_status;
    int dir;
    TcpPair ptp;

    /* grab the address from this packet */
    TcpPairAddrBlock tp_in = new TcpPairAddrBlock(ipMsg, tcpMsg);

    ptp = (TcpPair) ptp_hashtable.get(tp_in);

    if (null != ptp) {
      /*
       * See if the current node in the AVL tree hash-bucket is the exact same
       * connection as ourselves, either in A2B or B2A directions.
       */

      dir = ptp.whichDir(ipMsg, tcpMsg);

      if (dir == A2B || dir == B2A) {
        /* OK, this looks good, suck it into memory */

        Tcb thisdir;
        Tcb otherdir;

        /* figure out which direction this packet is going */
        if (dir == A2B) {
          thisdir = ptp.a2b;
          otherdir = ptp.b2a;
        } else {
          thisdir = ptp.b2a;
          otherdir = ptp.a2b;
        }

        /* Fri Oct 16, 1998 */
        /* note: original heuristic was not sufficient. Bugs */
        /* were pointed out by Brian Utterback and later by */
        /* myself and Mark Allman */

        /* check for NEW connection on these same endpoints */
        /* 1) At least 4 minutes idle time */
        /* OR */
        /* 2) heuristic (we might miss some) either: */
        /* this packet has a SYN */
        /* last conn saw both FINs and/or RSTs */
        /* SYN sequence number outside last window (rfc 1122) */
        /* (or less than initial Sequence, */
        /* for wrap around trouble) - Tue Nov 3, 1998 */
        /* OR */
        /* 3) this is a SYN, last had a SYN, seq numbers differ */
        /* if so, mark it INACTIVE and skip from now on */
        // if (0 && tcpMsg.getSYN()) {
        // /* better keep this debugging around, it keeps breaking */
        // printf("elapsed: %f sec\n",
        // elapsed(ptp.last_time,current_time)/1000000);
        // printf("SYN_SET: %d\n", tcpMsg.getSYN());
        // printf("a2b.fin_count: %d\n", ptp.a2b.fin_count);
        // printf("b2a.fin_count: %d\n", ptp.b2a.fin_count);
        // printf("a2b.reset_count: %d\n", ptp.a2b.reset_count);
        // printf("b2a.reset_count: %d\n", ptp.b2a.reset_count);
        // printf("dir: %d (%s)\n", dir, dir==A2B?"A2B":"B2A");
        // printf("seq: %lu \n", (u_long)ntohl(ptcp.th_seq));
        // printf("winend: %lu \n", otherdir.windowend);
        // printf("syn: %lu \n", otherdir.syn);
        // printf("SEQ_GREATERTHAN winend: %d\n",
        // SEQ_GREATERTHAN(ntohl(ptcp.th_seq),otherdir.windowend));
        // printf("SEQ_LESSTHAN init syn: %d\n",
        // SEQ_LESSTHAN(ntohl(ptcp.th_seq),thisdir.syn));
        // }
        // (4*60)) Using nonreal_live_conn_interval instead of the 4 mins
        // heuristic
        if (/* rule 1 */
            ((current_time - ptp.last_time) > NONREAL_LIVE_CONN_INTERVAL)
            || /* rule 2 */
            (tcpMsg.getSYN()
                && (thisdir.fin_count >= 1 || otherdir.fin_count >= 1
                    || thisdir.reset_count >= 1 || otherdir.reset_count >= 1)
                && (SEQ_GREATERTHAN(tcpMsg.getSeqNum(), otherdir.windowend)
                    || SEQ_LESSTHAN(tcpMsg.getSeqNum(), thisdir.syn)))
           || /* rule 3 */
            (tcpMsg.getSYN()
                && thisdir.syn_count > 1
                && thisdir.syn != tcpMsg.getSeqNum())) {

          /* we won't need this one anymore, remove it from the */
          /* hash table so we won't have to skip over it */
          ptp.inactive = true;

          ptp_hashtable.remove(tp_in);
        }

        /* check for "inactive" */
        /* (this shouldn't happen anymore, they aren't on the list */
        if (!ptp.inactive) {
          return (ptp);
        }

        // } else { // WhichDir returned 0, meaning if it exists, it's deeper
        // conn_status = AVL_WhichDir(&tp_in,&ptph.addr_pair);
        // if (conn_status == LT)
        // ptph = ptph.left;
        // else if (conn_status == RT)
        // ptph = ptph.right;
        // else if (0 == conn_status) {
        // System.out.println("WARNING!! AVL_WhichDir() should not return 0
        // if\n"
        // +"\tWhichDir() didn't return A2B or B2A previously\n");
        // break;
        // }
      }
    }

    /* Didn't find it, make a new one, if possible */
    // if (0) {
    // printf("trace.c:FindTTP() calling MakePtpSnap()\n");
    // }
    // ptph = MakePtpSnap();
    ptp = newTTP(ipMsg, tcpMsg, current_time);

    /* To insert the new connection snapshot into the AVL tree */
    ptp_hashtable.put(ptp.addr_pair, ptp);

    return ptp;
  }

  String HostLetter(long ix) {
    return "a" + String.valueOf(ix);
    // String name;
    // String pname;
    //
    // /* basically, just convert to base 26 */
    // pname = &name[sizeof(name)-1];
    // *pname-- = '\00';
    // while (pname >= name) {
    // unsigned digit = ix % 26;
    // *pname-- = 'a'+digit;
    // ix = (ix / 26) - 1;
    // if (ix == -1)
    // return(pname+1);
    // }
    // fprintf(stderr,"Fatal, too many hosts to name (max length
    // %d)\n\nNOTE:\nIf
    // you are using gcc version 2.95.3, then this may be a compiler bug. This
    // particular version\nis known to generate incorrect assembly code when
    // used
    // with CCOPT=-O2.\nSuggested fixes are:\n 1. Update gcc to the latest
    // version
    // and recompile tcptrace.\n 2. Use the same version of gcc, but edit the
    // tcptrace Makefile, setting CCOPT=-O instead of\n CCOPT=-O2, and then
    // recompile tcptrace.\nEither of these steps should hopefully fix the
    // problem.\n\n", MAX_HOSTLETTER_LEN);
    // exit(-1);
    // return(null); /* NOTREACHED */
  }

  static long count = 0;

  int tline_left = 0; /* left and right time lines for the time line charts */
  int tline_right = 0;

  protected String NextHostLetter() {
    return (HostLetter(count++));
  }

  protected String ServiceName(int port) {
    return String.valueOf(port);
    // TODO
    // static int cache = -1;
    // tcelen len;
    // struct servent *pse;
    // static char port_buf[20];
    // char *sb_port;
    //
    // if (!resolve_ports) {
    // snprintf(port_buf,sizeof(port_buf),"%hu",port);
    // return(port_buf);
    // }
    //
    // /* only small numbers have names */
    // if (port > 1023) {
    // snprintf(port_buf,sizeof(port_buf),"%hu",port);
    // return(port_buf);
    // }
    //
    //
    // /* check the cache */
    // if (cache == -1) {
    // cache = cacreate("service",250,0);
    // }
    // len = sizeof(port_buf);
    // if (debug > 2)
    // fprintf(stderr,"Searching cache for service %d='%s'\n",
    // port, port_buf);
    // if (calookup(cache,
    // (char *) &port, (tcelen) sizeof(port),
    // (char *) port_buf, &len) == OK) {
    // if (debug > 2)
    // fprintf(stderr,"Found service %d='%s' in cache\n",
    // port, port_buf);
    // return(port_buf);
    // }
    //
    //
    // /* get port name as a string */
    // pse = getservbyport(port,"tcp");
    // if (pse != null) {
    // sb_port = pse.s_name;
    // } else {
    // snprintf(port_buf,sizeof(port_buf),"%d",port);
    // sb_port = port_buf;
    // }
    // if (debug > 2)
    // fprintf(stderr,"Putting service %d='%s' in cache\n",
    // port, sb_port);
    // cainsert(cache,
    // (char *) &port, (tcelen) sizeof(port),
    // (char *) sb_port, (tcelen) (strlen(sb_port)+1));
    //
    // return(sb_port);
  }

  /* turn an ipaddr into a printable format */
  /* N.B. - result comes from static memory, save it before calling back! */
  protected String HostAddr(NetAddress ipaddress) {
    return ipaddress.toString();
    // TODO
    // char *adr;
    //
    // if (ADDR_ISV6(&ipaddress)) {
    // static char adrv6[INET6_ADDRSTRLEN];
    // my_inet_ntop(AF_INET6,(char *) ipaddress.un.ip6.s6_addr,
    // adrv6, INET6_ADDRSTRLEN);
    // adr = adrv6;
    // } else {
    // adr = inet_ntoa(ipaddress.un.ip4);
    // }
    //
    // return(adr);
  }

  protected String EndpointName(NetAddress ipaddress, int port) {
    return ipaddress.toString() + ":" + String.valueOf(port);
    // TODO
    // static char name_buf[100];
    // char *sb_host;
    // char *sb_port;
    //
    // sb_host = HostName(ipaddress);
    // sb_port = ServiceName(port);
    //
    // snprintf(name_buf,sizeof(name_buf),"%s:%s", sb_host, sb_port);
    //
    // return(name_buf);
  }

  protected String HostName(NetAddress ipaddress) {
    return ipaddress.toString();
    // TODO
    // tcelen len;
    // static int cache = -1;
    // struct hostent *phe;
    // char *sb_host;
    // static char name_buf[100];
    // char *adr;
    //
    // adr = HostAddr(ipaddress);
    //
    // if (!resolve_ipaddresses) {
    // return(adr);
    // }
    //
    // /* check the cache */
    // if (cache == -1) {
    // cache = cacreate("host",250,0);
    // }
    // len = sizeof(name_buf);
    // if (debug > 2)
    // fprintf(stderr,"Searching cache for host '%s'\n",
    // adr);
    // if (calookup(cache,
    // (char *) &ipaddress, (tcelen) sizeof(ipaddress),
    // (char *) name_buf, &len) == OK) {
    // if (debug > 2)
    // fprintf(stderr,"Found host %s='%s' in cache\n",
    // adr, name_buf);
    // return(name_buf);
    // }
    //
    //
    // if (ADDR_ISV6(&ipaddress))
    // phe = gethostbyaddr ((char *)&ipaddress.un.ip6,
    // sizeof(ipaddress.un.ip6), AF_INET6);
    // else
    // phe = gethostbyaddr((char *)&ipaddress.un.ip4,
    // sizeof(ipaddress.un.ip4), AF_INET);
    // if (phe != null) {
    // sb_host = phe.h_name;
    // } else {
    // sb_host = adr;
    // }
    //
    // if (use_short_names) {
    // char *pdot;
    //
    // if ((pdot = strchr(sb_host,'.')) != null) {
    // *pdot = '\00'; /* chop off the end */
    // }
    // }
    //
    // if (debug > 2)
    // fprintf(stderr,"Putting host %s='%s' in cache\n",
    // adr, sb_host);
    //
    // cainsert(cache,
    // (char *) &ipaddress, (tcelen)sizeof(ipaddress),
    // (char *) sb_host, (tcelen)(strlen(sb_host)+1));
    //
    // return(sb_host);
  }

  boolean graph_rtt = true;
  boolean graph_tsg = true;
  boolean ignore_non_comp = false;
  boolean graph_time_zero = false;
  boolean graph_seq_zero = true;
  boolean graph_owin = true;
  boolean show_rwinline = true;
  boolean graph_tline = true;
  boolean graph_segsize = true;
  boolean graph_tput = true;
  boolean docheck_hw_dups = true;
  boolean graph_zero_len_pkts = true;
  boolean plot_tput_instant = false;
  boolean dup_ack_handling = true;
  boolean triple_dupack_allows_data = true;
  // boolean dump_rtt = false;
  boolean show_title = true;

  /* options */
  boolean show_zero_window = true;
  boolean show_rexmit = true;
  boolean show_out_order = true;
  boolean show_sacks = true;
  boolean show_rtt_dongles = true;
  boolean show_triple_dupack = true;
  boolean show_zwnd_probes = true;
  boolean nonames = false;
  boolean use_short_names = false;
  boolean show_urg = true;
  int thru_interval = 10; /* in segments */

  /* what colors to use */
  /*
   * choose from: "green" "red" "blue" "yellow" "purple" "orange" "magenta"
   * "pink"
   */
  String window_color = "yellow";
  String ack_color = "green";
  String sack_color = "purple";
  String data_color = "white";
  String retrans_color = "red";
  String hw_dup_color = "blue";
  String out_order_color = "pink";
  String text_color = "magenta";
  String default_color = "white";
  String synfin_color = "orange";
  String push_color = "white"; /* top arrow for PUSHed segments */
  String ecn_color = "yellow";
  String urg_color = "red";
  String probe_color = "orange";
  String a2b_seg_color = "green"; /* colors for segments on the time line chart */
  String b2a_seg_color = "yellow";

  /* ack diamond dongle colors */
  String ackdongle_nosample_color = "blue";
  String ackdongle_ambig_color = "red";

  boolean colorplot = true;

  double sample_elapsed_time = 0; /* to keep track of owin samples */
  double total_elapsed_time = 0; /* to keep track of owin samples */

  long pnum = 0;

  protected PLINE new_line(PLOTTER plotter, String label, String color) {
    PLINE pl = new PLINE();
    pl.plotter = plotter;
    pl.label = label;
    pl.color = color;

    return (pl);
  }

  protected void extend_line(PLINE pline, long xval, long yval) {
    PLOTTER p;

    if (null == pline)
      return;

    p = pline.plotter;

    /* attach a label midway on the first line segment above 0 */
    /* for whom the second point is NOT 0 */
    if (!pline.labelled) {
      if ((yval != 0) && (0 != pline.last_time)) {
        long tv_elapsed;
        long tv_avg;
        int avg_yval;

        /* computer elapsed time for these 2 points */
        tv_elapsed = xval;
        tv_elapsed -= pline.last_time;

        /* divide elapsed time by 2 */
        tv_elapsed /= 2;

        /* add 1/2 of the elapsed time to the oldest point */
        /* (giving us the average time) */
        tv_avg = pline.last_time;
        tv_avg += tv_elapsed;

        /* average the Y values */
        avg_yval = (int) ((1 + pline.last_y + yval) / 2);
        /* (rounding UP, please) */

        /* draw the label */
        plotter_temp_color(p, pline.color);
        plotter_text(p, tv_avg, avg_yval, "l", pline.label);

        /* remember that it's been done */
        pline.labelled = true;
      }
    }

    /* draw a dot at the current point */
    plotter_perm_color(p, pline.color);
    plotter_dot(p, xval, yval);

    /* if this isn't the FIRST point, connect with a line */
    if (0 != pline.last_time) {
      plotter_line(p, xval, yval, pline.last_time, pline.last_y);
    }

    /* remember this point for the next line segment */
    pline.last_time = xval;
    pline.last_y = yval;
  }

  protected void plotter_tick(PLOTTER pl, long t, long x, String dir) {
    pl.CallDoPlot(dir + "tick", t, x);
  }

  protected void plotter_dtick(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("dtick", t, x);
    // plotter_tick(pl,t,x,"d");
  }

  protected void plotter_utick(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("utick", t, x);
    // plotter_tick(pl,t,x,"u");
  }

  protected void plotter_ltick(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("ltick", t, x);
    // plotter_tick(pl,t,x,"l");
  }

  protected void plotter_rtick(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("rtick", t, x);
    // plotter_tick(pl,t,x,"r");
  }

  protected void plotter_htick(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("htick", t, x);
    // plotter_tick(pl,t,x,"h");
  }

  protected void plotter_vtick(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("vtick", t, x);
    // plotter_tick(pl,t,x,"v");
  }

  protected void plotter_diamond(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("diamond", t, x);
  }

  protected void plotter_dot(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("dot", t, x);
  }

  protected void plotter_box(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("box", t, x);
  }

  protected void plotter_arrow(PLOTTER pl, long t, long x, String dir) {
    pl.CallDoPlot(dir + "arrow", t, x);
  }

  protected void plotter_uarrow(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("uarrow", t, x);
    // plotter_arrow(pl,t,x,"u");
  }

  protected void plotter_darrow(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("darrow", t, x);
    // plotter_arrow(pl,t,x,"d");
  }

  protected void plotter_rarrow(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("rarrow", t, x);
    // plotter_arrow(pl,t,x,"r");
  }

  protected void plotter_larrow(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("larrow", t, x);
    // plotter_arrow(pl,t,x,"l");
  }

  protected void plotter_line(PLOTTER pl, long t1, long x1, long t2, long x2) {
    pl.CallDoPlot("line", t1, x1, t2, x2);
  }

  protected void plotter_dline(PLOTTER pl, long t1, long x1, long t2, long x2) {
    pl.CallDoPlot("dline", t1, x1, t2, x2);
  }

  protected void plotter_text(PLOTTER pl, long t, long x, String where,
      String str) {
    pl.CallDoPlot(where + "text", t, x);
    /* fix by Bill Fenner - Wed Feb 5, 1997, thanks */
    /* This is a little ugly. Text commands take 2 lines. */
    /* A temporary color could have been */
    /* inserted after that line, but would NOT be inserted after */
    /* the next line, so we'll be OK. I can't think of a better */
    /* way right now, and this works fine (famous last words) */
    pl.CallDoPlot(str);
  }

  protected void plotter_temp_color(PLOTTER pl, String color) {
    if (colorplot)
      pl.temp_color = color;
  }

  protected void plotter_perm_color(PLOTTER pl, String color) {
    if (colorplot) {
      pl.temp_color = null;
      pl.CallDoPlot(color);
    }
  }

//  /* Switch the x and y axis type (Needed for Time Line Charts. Default = FLASE) */
//  protected void plotter_switch_axis(PLOTTER pl, boolean flag) {
//    pl.axis_switched = flag;
//  }

  /* don't plot ANYTHING, just make sure ZERO point is set! */
  protected void plotter_nothing(PLOTTER pl, long t) {
    pl.xp_timestamp(t);
  }

  protected void plotter_invisible(PLOTTER pl, long t, long x) {
    pl.CallDoPlot("invisible", t, x);
  }

  protected PLOTTER new_plotter(Tcb plast, String filename,
      String title, String xlabel, String ylabel, String suffix) {
    return new_plotter(plast, filename, title, xlabel, ylabel, suffix, false);
  }

  protected PLOTTER new_plotter(Tcb plast, String filename,
  String title, String xlabel, String ylabel, String suffix, boolean axis_switched) {
    PLOTTER ppi = new PLOTTER(title, xlabel, ylabel, plast, axis_switched);

    if (null != events)
      events.plotterCreated(ppi);

    // if (filename == null)
    // filename = TSGPlotName(plast,pl,suffix);
    // else if (suffix != null) {
    // char buf[100];
    // snprintf(buf,sizeof(buf),"%s%s", filename, suffix);
    // filename = buf;
    // }

    // if ((f = Mfopen(filename,"w")) == null) {
    // perror(filename);
    // return(null);
    // }

    // ppi.p2plast = plast;
    // ppi.filename = strdup(filename);
    // ppi.axis_switched = false;
    // ppi.header_done = false;

    /* Save these fields to be writtn to the plotter header later in DoPlot() */
    // ppi.title = title;
    // ppi.xlabel = xlabel;
    // ppi.ylabel = ylabel;
    return (ppi);
  }

  private TcpPair newTTP(Ip ipMsg, TcpMessage tcpMsg, long current_time) {
    String title;
    TcpPair ptp;

    ptp = new TcpPair();
    // ++num_tcp_pairs;

    // if (!run_continuously) {
    /* make a new one, if possible */
    // if ((num_tcp_pairs+1) >= max_tcp_pairs) {
    // MoreTcpPairs(num_tcp_pairs+1);
    // }
    // /* create a new TCP pair record and remember where you put it */
    // ttp[num_tcp_pairs] = ptp;
    // ptp.ignore_pair = ignore_pairs[num_tcp_pairs];
    // }

    /* grab the address from this packet */
    ptp.addr_pair = new TcpPairAddrBlock(ipMsg, tcpMsg);

    ptp.a2b.time = -1;
    ptp.b2a.time = -1;

    ptp.a2b.host_letter = (NextHostLetter());
    ptp.b2a.host_letter = (NextHostLetter());

    ptp.a2b.ptp = ptp;
    ptp.b2a.ptp = ptp;
    ptp.a2b.ptwin = ptp.b2a;
    ptp.b2a.ptwin = ptp.a2b;

    /* fill in connection name fields */
    ptp.a_hostname = (HostName(ptp.addr_pair.a_address));
    ptp.a_endpoint = (EndpointName(ptp.addr_pair.a_address,
        ptp.addr_pair.a_port));
    ptp.a_portname = (ServiceName(ptp.addr_pair.a_port));
    ptp.b_hostname = (HostName(ptp.addr_pair.b_address));
    ptp.b_portname = (ServiceName(ptp.addr_pair.b_port));
    ptp.b_endpoint = (EndpointName(ptp.addr_pair.b_address,
        ptp.addr_pair.b_port));

    /* make the initial guess that each side is a reno tcp */
    /*
     * this might actually be a poor thing to do in the sense that we could be
     * looking at a Tahoe trace ... but the only side effect for the moment is
     * that the LEAST estimate may be busted, although it very well may not be
     */
    ptp.a2b.tcp_strain = TCP_RENO;
    ptp.b2a.tcp_strain = TCP_RENO;

    ptp.a2b.LEAST = ptp.b2a.LEAST = 0;
    ptp.a2b.in_rto = ptp.b2a.in_rto = false;

    /* init time sequence graphs */
    ptp.a2b.tsg_plotter = ptp.b2a.tsg_plotter = null;
    if (graph_tsg && !ptp.ignore_pair) {
      if (!ignore_non_comp || ((tcpMsg.getSYN()))) {
        title = ptp.a_endpoint + "_==>_" + ptp.b_endpoint
            + " (time sequence graph)";
        ptp.a2b.tsg_plotter = new_plotter(ptp.a2b, null, title,
            graph_time_zero ? "relative time" : "time",
            graph_seq_zero ? "sequence offset" : "sequence number",
            PLOT_FILE_EXTENSION);
        title = ptp.b_endpoint + "_==>_" + ptp.a_endpoint
            + " (time sequence graph)";
        ptp.b2a.tsg_plotter = new_plotter(null, null, title,
            graph_time_zero ? "relative time" : "time",
            graph_seq_zero ? "sequence offset" : "sequence number",
            PLOT_FILE_EXTENSION);
        if (graph_time_zero) {
          /* set graph zero points */
          plotter_nothing(ptp.a2b.tsg_plotter, current_time);
          plotter_nothing(ptp.b2a.tsg_plotter, current_time);
        }
      }
    }

    /* init owin graphs */
    ptp.a2b.owin_plotter = ptp.b2a.owin_plotter = null;
    if (graph_owin && !ptp.ignore_pair) {
      if (!ignore_non_comp || (tcpMsg.getSYN())) {
        title = ptp.a_endpoint + "_==>_" + ptp.b_endpoint
            + " (outstanding data)";
        ptp.a2b.owin_plotter = new_plotter(ptp.a2b, null, title,
            graph_time_zero ? "relative time" : "time",
            "Outstanding Data (bytes)", OWIN_FILE_EXTENSION);
        title = ptp.b_endpoint + "_==>_" + ptp.a_endpoint
            + " (outstanding data)";
        ptp.b2a.owin_plotter = new_plotter(ptp.b2a, null, title,
            graph_time_zero ? "relative time" : "time",
            "Outstanding Data (bytes)", OWIN_FILE_EXTENSION);
        if (graph_time_zero) {
          /* set graph zero points */
          plotter_nothing(ptp.a2b.owin_plotter, current_time);
          plotter_nothing(ptp.b2a.owin_plotter, current_time);
        }
        ptp.a2b.owin_line = new_line(ptp.a2b.owin_plotter, "owin", "red");
        ptp.b2a.owin_line = new_line(ptp.b2a.owin_plotter, "owin", "red");

        if (show_rwinline) {
          ptp.a2b.rwin_line = new_line(ptp.a2b.owin_plotter, "rwin", "yellow");
          ptp.b2a.rwin_line = new_line(ptp.b2a.owin_plotter, "rwin", "yellow");
        }

        ptp.a2b.owin_avg_line = new_line(ptp.a2b.owin_plotter, "avg owin",
            "blue");
        ptp.b2a.owin_avg_line = new_line(ptp.b2a.owin_plotter, "avg owin",
            "blue");
        ptp.a2b.owin_wavg_line = new_line(ptp.a2b.owin_plotter, "wavg owin",
            "green");
        ptp.b2a.owin_wavg_line = new_line(ptp.b2a.owin_plotter, "wavg owin",
            "green");
      }
    }

    /* init time line graphs (Avinash, 2 July 2002) */
    ptp.a2b.tline_plotter = ptp.b2a.tline_plotter = null;
    if (graph_tline && !ptp.ignore_pair) {
      if (!ignore_non_comp || (tcpMsg.getSYN())) {
        /*
         * We don't want the standard a2b type name so we will specify a
         * filename of type a_b when we call new_plotter.
         */
        String filename = ptp.a2b.host_letter + "_" + ptp.a2b.ptwin.host_letter;

        title = ptp.a_endpoint + "_==>_" + ptp.b_endpoint
            + " (time line graph)";
        /*
         * We will keep both the plotters the same since we want all segments
         * going in either direction to be plotted on the same graph
         */
        ptp.a2b.tline_plotter = ptp.b2a.tline_plotter = new_plotter(ptp.a2b,
            filename, title, "segments", "relative time", TLINE_FILE_EXTENSION, true);

        /*
         * Switch the x & y axis types. The default is x - long, y - unsigned,
         * we need x - unsigned, y - dtime. Both the plotters are the same so we
         * will only call this function once.
         */
//        plotter_switch_axis(ptp.a2b.tline_plotter, true);

        /* set graph zero points */
        plotter_nothing(ptp.a2b.tline_plotter, current_time);
        plotter_nothing(ptp.b2a.tline_plotter, current_time);

        /*
         * Some graph initializations Generating a drawing space between
         * x=0-100. The time lines will be at x=40 for source, x=60 for
         * destination. Rest of the area on either sides will be used to print
         * segment information.
         *
         * seg info |----.| |<-----| seg info
         */
        tline_left = 40;
        tline_right = 60;
        plotter_invisible(ptp.a2b.tline_plotter, current_time, 0);
        plotter_invisible(ptp.a2b.tline_plotter, current_time, 100);
      }
    }

    /* init segment size graphs */
    ptp.a2b.segsize_plotter = ptp.b2a.segsize_plotter = null;
    if (graph_segsize && !ptp.ignore_pair) {
      title = ptp.a_endpoint + "_==>_" + ptp.b_endpoint
          + " (segment size graph)";
      ptp.a2b.segsize_plotter = new_plotter(ptp.a2b, null, title,
          graph_time_zero ? "relative time" : "time", "segment size (bytes)",
          SEGSIZE_FILE_EXTENSION);
      title = ptp.b_endpoint + "_==>_" + ptp.a_endpoint
          + " (segment size graph)";
      ptp.b2a.segsize_plotter = new_plotter(ptp.b2a, null, title,
          graph_time_zero ? "relative time" : "time", "segment size (bytes)",
          SEGSIZE_FILE_EXTENSION);
      if (graph_time_zero) {
        /* set graph zero points */
        plotter_nothing(ptp.a2b.segsize_plotter, current_time);
        plotter_nothing(ptp.b2a.segsize_plotter, current_time);
      }
      ptp.a2b.segsize_line = new_line(ptp.a2b.segsize_plotter, "segsize", "red");
      ptp.b2a.segsize_line = new_line(ptp.b2a.segsize_plotter, "segsize", "red");
      ptp.a2b.segsize_avg_line = new_line(ptp.a2b.segsize_plotter,
          "avg segsize", "blue");
      ptp.b2a.segsize_avg_line = new_line(ptp.b2a.segsize_plotter,
          "avg segsize", "blue");
    }

    /* init RTT graphs */
    ptp.a2b.rtt_plotter = ptp.b2a.rtt_plotter = null;

    ptp.a2b.ss = new seqspace();
    ptp.b2a.ss = new seqspace();

    // ptp.filename = cur_filename;

    return (ptp);
  }

  public TcpPair dotrace(NetMessage.Ip ipMsg, long current_time) {
    TcpMessage tcpMsg = (TcpMessage) ipMsg.getPayload();
    TcpOptions tcpOptions = tcpMsg.getOptions();
    TcpPair ptp_save;
    int tcp_length;
    int tcp_data_length;
    int start;
    int end;
    Tcb thisdir;
    Tcb otherdir;
    // TcpPair tp_in;
    PLOTTER to_tsgpl;
    PLOTTER from_tsgpl;
    PLOTTER tlinepl;
    int dir;
    boolean retrans;
    boolean probe;
    boolean hw_dup = false; /* duplicate at the hardware level */
    // boolean ecn_ce = false;
    // boolean ecn_echo = false;
    // boolean cwr = false;
    boolean urg = false;
    int retrans_num_bytes;
    BooleanOut out_order = new BooleanOut(); /* out of order */
    // short th_sport; /* source port */
    // short th_dport; /* destination port */
    int th_seq; /* sequence number */
    int th_ack; /* acknowledgement number */
    short th_win; /* window */
    int eff_win; /* window after scaling */
    // short th_urp; /* URGENT pointer */
    // short ip_len; /* total length */
    int ack_type = NORMAL; /* how should we draw the ACK */
    long old_this_windowend; /* for graphing */

    /* convert interesting fields to local byte order */
    th_seq = tcpMsg.getSeqNum();
    th_ack = tcpMsg.getAckNum();
    // th_sport = tcpMsg.getSrcPort();
    // th_dport = tcpMsg.getDstPort();
    th_win = tcpMsg.getWindowSize();
    // th_urp = tcpMsg.getURG(); // ntohs(ptcp.th_urp);
    // ip_len = gethdrlength(pip, plast) + getpayloadlength(pip,plast);

    /* make sure this is one of the connections we want */
    ptp_save = findTTP(ipMsg, tcpMsg, current_time);
    dir = ptp_save.whichDir(ipMsg, tcpMsg);

    if (ptp_save == null) {
      return (null);
    }

    // if (run_continuously && (tcp_ptr == null)) {
    // fprintf(stderr, "Did not initialize tcp pair pointer\n");
    // exit(1);
    // }

    /* do time stats */
    if (0 == ptp_save.first_time) {
      ptp_save.first_time = current_time;
    }
    ptp_save.last_time = current_time;

    /* bug fix: it's legal to have the same end points reused. The */
    /* program uses a heuristic of looking at the elapsed time from */
    /* the last packet on the previous instance and the number of FINs */
    /* in the last instance. If we don't increment the fin_count */
    /* before bailing out in "ignore_pair" below, this heuristic breaks */

    /* figure out which direction this packet is going */
    if (dir == A2B) {
      thisdir = ptp_save.a2b;
      otherdir = ptp_save.b2a;
    } else {
      thisdir = ptp_save.b2a;
      otherdir = ptp_save.a2b;
    }

    /* meta connection stats */
    if (tcpMsg.getSYN())
      ++thisdir.syn_count;
    if (tcpMsg.getRST())
      ++thisdir.reset_count;
    if (tcpMsg.getFIN())
      ++thisdir.fin_count;

    /* end bug fix */

    /* compute the "effective window", which is the advertised window */
    /* with scaling */
    if (tcpMsg.getACK() || tcpMsg.getSYN()) {
      eff_win = (int) th_win;

      /* N.B., the window_scale stored for the connection DURING 3way */
      /* handshaking is the REQUESTED scale. It's only valid if both */
      /* sides request scaling. AFTER we've seen both SYNs, that field */
      /* is reset (above) to contain zero. Note that if we */
      /* DIDN'T see the SYNs, the windows will be off. */
      /* Jamshid: Remember that the window is never scaled in SYN */
      /* packets, as per RFC 1323. */
      if (thisdir.f1323_ws && otherdir.f1323_ws && !tcpMsg.getSYN())
        eff_win <<= thisdir.window_scale;
    } else {
      eff_win = 0;
    }

    /* idle-time stats */
    if (0 != thisdir.last_time) {
      long itime = current_time - thisdir.last_time;
      if (itime > thisdir.idle_max)
        thisdir.idle_max = itime;
    }
    thisdir.last_time = current_time;

    /* calculate data length */
    tcp_length = tcpMsg.getSize();
    tcp_data_length = tcp_length - TcpMessage.HEADER_SIZE
        - tcpOptions.getSize();

    /* calc. data range */
    start = th_seq;
    end = start + tcp_data_length;

    /* seq. space wrap around stats */
    /*
     * If all four quadrants have been visited and the current packet is in the
     * same quadrant as the syn, check if latest seq. num is wrapping past the
     * syn. If it is, increment wrap_count
     */
    if ((0 != thisdir.quad1 && 0 != thisdir.quad2 && 0 != thisdir.quad3 && 0 != thisdir.quad4)) {
      if ((IN_Q1(thisdir.syn) && (IN_Q1(end)))
          || (IN_Q2(thisdir.syn) && (IN_Q2(end)))
          || ((IN_Q3(thisdir.syn) && (IN_Q3(end))) || ((IN_Q4(thisdir.syn) && (IN_Q4(end)))))) {
        if (end >= thisdir.syn) {
          thisdir.seq_wrap_count++;
          thisdir.quad1 = 0;
          thisdir.quad2 = 0;
          thisdir.quad3 = 0;
          thisdir.quad4 = 0;
        }
      }
    }

    /* Mark the visited quadrants */
    if (0 == thisdir.quad1) {
      if (IN_Q1(start) || IN_Q1(end))
        thisdir.quad1 = 1;
    }
    if (0 == thisdir.quad2) {
      if (IN_Q2(start) || IN_Q2(end))
        thisdir.quad2 = 1;
    }
    if (0 == thisdir.quad3) {
      if (IN_Q3(start) || IN_Q3(end))
        thisdir.quad3 = 1;
    }
    if (0 == thisdir.quad4) {
      if (IN_Q4(start) || IN_Q4(end))
        thisdir.quad4 = 1;
    }

    /* record sequence limits */
    if (tcpMsg.getSYN()) {
      /* error checking - better not change! */
      if ((thisdir.syn_count > 1) && (thisdir.syn != start)) {
        /* it changed, that shouldn't happen! */
        // if (warn_printbad_syn_fin_seq)
        // fprintf(stderr, "%s.%s: rexmitted SYN had diff. int! (was %lu, now
        // %lu, etime: %d sec)\n",
        // thisdir.host_letter,thisdir.ptwin.host_letter,
        // thisdir.syn, start,
        // (int)(elapsed(ptp_save.first_time,current_time)/1000000));
        // thisdir.bad_behavior = true;
      }
      thisdir.syn = start;
      otherdir.ack = start;
      /* bug fix for Rob Austein <sra@epilogue.com> */
    }
    if (tcpMsg.getFIN()) {
      /* bug fix, if there's data here too, we need to bump up the FIN */
      /* (psc data file shows example) */
      int fin = start + tcp_data_length;
      /* error checking - better not change! */
      if ((thisdir.fin_count > 1) && (thisdir.fin != fin)) {
        /* it changed, that shouldn't happen! */
        // if (warn_printbad_syn_fin_seq)
        // fprintf(stderr, "%s.%s: rexmitted FIN had diff. int! (was %lu, now
        // %lu, etime: %d sec)\n",
        // thisdir.host_letter,thisdir.ptwin.host_letter,
        // thisdir.fin, fin,
        // (int)(elapsed(ptp_save.first_time,current_time)/1000000));
        // thisdir.bad_behavior = true;
      }
      thisdir.fin = fin;
    }

    /* "ONLY" bug fix - Wed Feb 24, 1999 */
    /* the tcp-splicing heuristic needs "windowend", which was only being */
    /* calculated BELOW the "only" point below. Move that part of the */
    /* calculation up here! */

    /* remember the OLD window end for graphing */
    /* (bug fix - Thu Aug 12, 1999) */
    old_this_windowend = thisdir.windowend;

    if (tcpMsg.getACK()) {
      thisdir.windowend = th_ack + eff_win;
    }
    /* end bugfix */

    /** ******************************************************************** */
    /** ******************************************************************** */
    /* if we're ignoring this connection, do no further processing */
    /** ******************************************************************** */
    /** ******************************************************************** */
    if (ptp_save.ignore_pair) {
      return (ptp_save);
    }

    /* save to a file if requested */
    // if (output_filename) {
    // PcapSavePacket(output_filename,pip,plast);
    // }
    /* now, print it if requested */
    // if (printem && !printallofem) {
    // printf("Packet %lu\n", pnum);
    // printpacket(0, /* original length not available */
    // (char *)plast - (char *)pip + 1,
    // null,0, /* physical stuff not known here */
    // pip,plast,thisdir);
    // }
    // /* grab the address from this packet */
    // tp_in.addr_pair = new TcpPairAddrBlock(ipMsg, tcpMsg);

    /* simple bookkeeping */
    // if (PIP_ISV6(pip)) {
    // ++thisdir.ipv6_segments;
    // }

    /* plotter shorthand */
    to_tsgpl = otherdir.tsg_plotter;
    from_tsgpl = thisdir.tsg_plotter;

    /* plotter shorthand (NOTE: we are using one plotter for both directions) */
    tlinepl = thisdir.tline_plotter;

    // /* check the options */
    // if (tcpOptions.mss != -1)
    // thisdir.mss = ptcpo.mss;
    // if (ptcpo.ws != -1) {
    // thisdir.window_scale = ptcpo.ws;
    // thisdir.f1323_ws = true;
    // }
    // if (ptcpo.tsval != -1) {
    // thisdir.window_scale = ptcpo.ws;
    // thisdir.f1323_ws = true;
    // }
    /* NOW, unless BOTH sides asked for window scaling in their SYN */
    /* segments, we aren't using window scaling */
    if (!tcpMsg.getSYN() && ((!thisdir.f1323_ws) || (!otherdir.f1323_ws))) {
      thisdir.window_scale = otherdir.window_scale = 0;
    }

    // /* check sacks */
    // if (ptcpo.sack_req) {
    // thisdir.fsack_req = 1;
    // }
    // if (ptcpo.sack_count > 0) {
    // ++thisdir.sacks_sent;
    // }

    /*
     * unless both sides advertised sack, we shouldn't see them, otherwise we
     * hope they actually send them
     */
    if (!tcpMsg.getSYN() && (thisdir.fsack_req && otherdir.fsack_req)) {
      thisdir.tcp_strain = otherdir.tcp_strain = TCP_SACK;
    }

    /* do data stats */
    urg = false;
    if (tcp_data_length > 0) {
      thisdir.data_pkts += 1;
      if (tcpMsg.getPSH())
        thisdir.data_pkts_push += 1;
      thisdir.data_bytes += tcp_data_length;
      // if (tcpMsg.getURG()) { /* Checking if URGENT bit is set */
      // urg = true;
      // thisdir.urg_data_pkts += 1;
      // thisdir.urg_data_bytes += th_urp;
      // }
      if (tcp_data_length > thisdir.max_seg_size)
        thisdir.max_seg_size = tcp_data_length;
      if ((thisdir.min_seg_size == 0)
          || (tcp_data_length < thisdir.min_seg_size))
        thisdir.min_seg_size = tcp_data_length;
      /* record first and last times for data (Mallman) */
      if (0 == thisdir.first_data_time)
        thisdir.first_data_time = current_time;
      thisdir.last_data_time = current_time;
    }

    /* total packets stats */
    ++ptp_save.packets;
    ++thisdir.packets;

    /* If we are using window scaling, update the win_scaled_pkts counter */
    if (thisdir.window_stats_updated_for_scaling)
      ++thisdir.win_scaled_pkts;

    /* instantaneous throughput stats */
    if (graph_tput) {
      DoThru(thisdir, tcp_data_length, current_time);
    }

    /* segment size graphs */
    if ((tcp_data_length > 0) && (thisdir.segsize_plotter != null)) {
      extend_line(thisdir.segsize_line, current_time, tcp_data_length);
      extend_line(thisdir.segsize_avg_line, current_time, thisdir.data_bytes
          / thisdir.data_pkts);
    }

    /* sequence number stats */
    if ((thisdir.min_seq == 0) && (start != 0)) {
      thisdir.min_seq = start; /* first byte in this segment */
      thisdir.max_seq = end; /* last byte in this segment */
    }
    if (SEQ_GREATERTHAN(end, thisdir.max_seq)) {
      thisdir.max_seq = end;
    }
    thisdir.latest_seq = end;

    /* check for hardware duplicates */
    /* only works for IPv4, IPv6 has no mandatory ID field */
    if (PIP_ISV4(ipMsg) && docheck_hw_dups)
      hw_dup = check_hw_dups(ipMsg.getId(), th_seq, thisdir);

    // /* Kevin Lahey's ECN code */
    // /* only works for IPv4 */
    // if (PIP_ISV4(ipMsg)) {
    // ecn_ce = IP_ECT(pip) && IP_CE(pip);
    // }
    // cwr = CWR_SET(ptcp);
    // ecn_echo = ECN_ECHO_SET(ptcp);

    /* save the stream contents, if requested */
    if (tcp_data_length > 0) {
      long saved;
      long missing;

      saved = tcp_data_length;
      if (tcp_data_length > tcpMsg.getPayload().getSize())
        saved = tcpMsg.getPayload().getSize();

      /* see what's missing */
      missing = tcp_data_length - saved;
      if (missing > 0) {
        thisdir.trunc_bytes += missing;
        ++thisdir.trunc_segs;
      }

      // if (save_tcp_data)
      // ExtractContents(start,tcp_data_length,saved,pdata,thisdir);
    }

    /* do rexmit stats */
    retrans = false;
    probe = false;
    out_order.value = false;
    retrans_num_bytes = 0;
    if (tcpMsg.getSYN() || tcpMsg.getFIN() || tcp_data_length > 0) {
      int len = tcp_data_length;
      int retrans_cnt = 0;

      if (tcpMsg.getSYN())
        ++len;
      if (tcpMsg.getFIN())
        ++len;

      /* Don't consider for rexmit, if the send window is 0 */
      /* We are probably doing window probing.. */
      /*
       * Patch from Ulisses Alonso Camaro : Not treat the SYN segments as
       * probes, even though a zero window was advertised from the opposite
       * direction
       */
      if ((otherdir.win_last == 0) && (otherdir.packets > 0) &&
      /*
       * Patch from Ulisses Alonso Camaro : Not treat the SYN segments as
       * probes, even though a zero window was advertised from the opposite
       * direction
       */
      (!tcpMsg.getSYN())) {
        probe = true;
        thisdir.num_zwnd_probes++;
        thisdir.zwnd_probe_bytes += tcp_data_length;
      } else
        retrans_cnt = retrans_num_bytes = rexmit(thisdir, start, len,
            out_order, current_time);

      if (out_order.value)
        ++thisdir.out_order_pkts;

      /* count anything NOT retransmitted as "unique" */
      /* exclude SYN and FIN */
      if (tcpMsg.getSYN()) {
        /* don't count the SYN as data */
        --len;
        /* if the SYN was rexmitted, then don't count it */
        if (thisdir.syn_count > 1)
          --retrans_cnt;
      }
      if (tcpMsg.getFIN()) {
        /* don't count the FIN as data */
        --len;
        /* if the FIN was rexmitted, then don't count it */
        if (thisdir.fin_count > 1)
          --retrans_cnt;
      }
      if (!probe) {
        if (retrans_cnt < len)
          thisdir.unique_bytes += (len - retrans_cnt);
      }
    }

    /* do rtt stats */
    if (tcpMsg.getACK()) {
      ack_type = ack_in(otherdir, th_ack, tcp_data_length, eff_win,
          current_time);

      if ((th_ack == (otherdir.syn + 1)) && (otherdir.syn_count == 1))
        otherdir.rtt_3WHS = (long) otherdir.rtt_last;
      /* otherdir.rtt_last was set in the call to ack_in() */

      otherdir.lastackno = th_ack;
    }

    /* LEAST */
    if (thisdir.tcp_strain == TCP_RENO) {
      if (thisdir.in_rto && tcp_data_length > 0) {
        if (retrans_num_bytes > 0 && th_seq < thisdir.recovered)
          thisdir.event_retrans++;
        if (IsRTO(thisdir, th_seq)) {
          thisdir.recovered = thisdir.recovered_orig = thisdir.seq;
          thisdir.rto_segment = th_seq;
        }
        if (!(retrans_num_bytes > 0) && thisdir.ack <= thisdir.recovered_orig)
          thisdir.recovered = th_seq;
      }
      if (otherdir.in_rto && tcpMsg.getACK()) {
        if (th_ack > otherdir.recovered) {
          otherdir.LEAST -= (otherdir.event_dupacks < otherdir.event_retrans) ? otherdir.event_dupacks
              : otherdir.event_retrans;
          otherdir.in_rto = false;
        } else if (th_ack == otherdir.lastackno
            && th_ack >= otherdir.rto_segment)
          otherdir.event_dupacks++;
      }
    }

    /* plot out-of-order segments, if asked */
    if (out_order.value && (from_tsgpl != null) && show_out_order) {
      plotter_perm_color(from_tsgpl, out_order_color);
      plotter_text(from_tsgpl, current_time, SeqRep(thisdir, end), "a", "O");
    }

    /* stats for rexmitted data */
    if (retrans_num_bytes > 0) {
      retrans = true;
      /* for reno LEAST estimate */
      if (thisdir.tcp_strain == TCP_RENO && !thisdir.in_rto
          && IsRTO(thisdir, th_seq)) {
        thisdir.in_rto = true;
        thisdir.recovered = thisdir.recovered_orig = thisdir.seq;
        thisdir.rto_segment = th_seq;
        thisdir.event_retrans = 1;
        thisdir.event_dupacks = 0;
      }
      thisdir.rexmit_pkts += 1;
      thisdir.LEAST++;
      thisdir.rexmit_bytes += retrans_num_bytes;
      /* don't color the SYNs and FINs, it's confusing, we'll do them */
      /* differently below... */
      if (!(tcpMsg.getFIN() || tcpMsg.getSYN()) && from_tsgpl != null
          && show_rexmit) {
        plotter_perm_color(from_tsgpl, retrans_color);
        plotter_text(from_tsgpl, current_time, SeqRep(thisdir, end), "a",
            hw_dup ? "HD" : "R");
      }
    } else {
      thisdir.seq = end;
    }

    if (probe) {
      if (from_tsgpl != null && show_zwnd_probes) {
        plotter_perm_color(from_tsgpl, probe_color);
        plotter_text(from_tsgpl, current_time, SeqRep(thisdir, end), "b", "P");
      }
    }

    /* draw the packet */
    if (from_tsgpl != null) {
      plotter_perm_color(from_tsgpl, data_color);
      if (tcpMsg.getSYN()) { /* SYN */
        /* if we're using time offsets from zero, it's easier if */
        /* both graphs (a2b and b2a) start at the same point. That */
        /* will only happen if the "left-most" graphic is in the */
        /* same place in both. To make sure, mark the SYNs */
        /* as a green dot in the other direction */
        if (tcpMsg.getACK()) {
          plotter_temp_color(from_tsgpl, ack_color);
          plotter_dot(from_tsgpl, ptp_save.first_time, SeqRep(thisdir, start));
        }
        plotter_perm_color(from_tsgpl, hw_dup ? hw_dup_color
            : retrans_num_bytes > 0 ? retrans_color : synfin_color);
        plotter_diamond(from_tsgpl, current_time, SeqRep(thisdir, start));
        plotter_text(from_tsgpl, current_time, SeqRep(thisdir, start + 1), "a",
            hw_dup ? "HD SYN" : retrans_num_bytes > 0 ? "R SYN" : "SYN");
        plotter_uarrow(from_tsgpl, current_time, SeqRep(thisdir, start + 1));
        plotter_line(from_tsgpl, current_time, SeqRep(thisdir, start),
            current_time, SeqRep(thisdir, start + 1));
      } else if (tcpMsg.getFIN()) { /* FIN */
        /*
         * Wed Sep 18, 2002 - bugfix Check if data is present in the last
         * packet. We will draw the data bytes with the normal color and then
         * change the color for the last byte of FIN.
         */
        if (tcp_data_length > 0) { /* DATA + FIN */
          /* Data - default color */
          plotter_darrow(from_tsgpl, current_time, SeqRep(thisdir, start));
          plotter_line(from_tsgpl, current_time, SeqRep(thisdir, start),
              current_time, SeqRep(thisdir, end));
          /* FIN - synfin color */
          plotter_perm_color(from_tsgpl, hw_dup ? hw_dup_color
              : retrans_num_bytes > 0 ? retrans_color : synfin_color);
        } else { /* Only FIN */
          /* FIN - synfin color */
          plotter_perm_color(from_tsgpl, hw_dup ? hw_dup_color
              : retrans_num_bytes > 0 ? retrans_color : synfin_color);
          plotter_darrow(from_tsgpl, current_time, SeqRep(thisdir, end));
        }
        plotter_line(from_tsgpl, current_time, SeqRep(thisdir, end),
            current_time, SeqRep(thisdir, end + 1));
        plotter_box(from_tsgpl, current_time, SeqRep(thisdir, end + 1));
        plotter_text(from_tsgpl, current_time, SeqRep(thisdir, end + 1), "a",
            hw_dup ? "HD FIN" : retrans_num_bytes > 0 ? "R FIN" : "FIN");

      } else if (tcp_data_length > 0) { /* DATA */
        if (hw_dup) {
          plotter_perm_color(from_tsgpl, hw_dup_color);
        } else if (retrans) {
          plotter_perm_color(from_tsgpl, retrans_color);
        }
        plotter_darrow(from_tsgpl, current_time, SeqRep(thisdir, start));
        if (tcpMsg.getPSH()) {
          /* colored diamond is PUSH */
          plotter_temp_color(from_tsgpl, push_color);
          plotter_diamond(from_tsgpl, current_time, SeqRep(thisdir, end));
          plotter_temp_color(from_tsgpl, push_color);
          plotter_dot(from_tsgpl, current_time, SeqRep(thisdir, end));
        } else {
          plotter_uarrow(from_tsgpl, current_time, SeqRep(thisdir, end));
        }
        plotter_line(from_tsgpl, current_time, SeqRep(thisdir, start),
            current_time, SeqRep(thisdir, end));
      } else if (tcp_data_length == 0) {
        /* for Brian Utterback */
        if (graph_zero_len_pkts) {
          /* draw zero-length packets */
          /* shows up as an X, really two arrow heads */
          plotter_darrow(from_tsgpl, current_time, SeqRep(thisdir, start));
          plotter_uarrow(from_tsgpl, current_time, SeqRep(thisdir, start));
        }
      }

      // /* Kevin Lahey's code */
      // /* XXX: can this overwrite other labels!? */
      // if (cwr || ecn_ce) {
      // plotter_perm_color(from_tsgpl, ecn_color);
      // plotter_diamond(from_tsgpl,
      // current_time, SeqRep(thisdir,start));
      // plotter_text(from_tsgpl, current_time, SeqRep(thisdir, start), "a",
      // cwr ? (ecn_ce ? "CWR CE" : "CWR") : "CE");
      // }
    }

    /* Plotting URGENT data */
    if (urg) {
      if (from_tsgpl != null && show_urg) {
        plotter_perm_color(from_tsgpl, urg_color);
        plotter_text(from_tsgpl, current_time, SeqRep(thisdir, end), "a", "U");
      }
    }

    /* graph time line */
    /*
     * Since the axis types have been switched specially for these graphs, x is
     * actually used as y and y as x -Avinash.
     *
     * NOTE: This code is lacking about a 1000 lines of intellegence that is
     * needed ----- to draw these graphs correctly. I have left it in here as
     * the starting point to work on. Whoever is working on this project would
     * want to clean up this file trace.c (based on the patches in the
     * README.tline_graphs file), and continue development as a seperate module.
     * We started this project thinking it is easy to draw these graphs, and
     * then realized that it is infact quite a complicated task. All this works
     * with a -L option at command line.
     */
    if (tlinepl != null) {
      String buf1 = "";
      String buf2 = "";
      int a2b_first_int = 0;
      int b2a_first_int = 0;
      /*
       * 1/3rd rtt. Since we have the timestamps only on one side, we calculate
       * the arrrival/departure time of the segments on the other side by
       * adding/subtracting 1/3rd rtt. We assume that it takes 1/3rd time for
       * the segment to travel in either direction, and 1/3rd time for
       * processing. We also skew the calculated times so that the acks are not
       * seen before the segments actually arrive.
       */
      long one3rd_rtt;
      long copy_current_time = 0;
      /* Make a copy of the current time (Needed for calculations) */
      copy_current_time = current_time;
      /* Compute 1/3rd rtt */
      one3rd_rtt = (long) (thisdir.rtt_last / 3);

      /* Segment information */
      /* Check the flags */
      if (tcpMsg.getSYN())
        buf1 += "SYN ";
      if (tcpMsg.getFIN())
        buf1 += "FIN ";
      if (tcpMsg.getRST())
        buf1 += "RST ";
      if (tcpMsg.getPSH())
        buf1 += "PSH ";
      if (tcpMsg.getURG())
        buf1 += "URG ";

      /* Write the sequence numbers */
      if (dir == A2B) {
        /*
         * Use relative sequence numbers after the first segment in either
         * direction
         */
        buf2 = (start - a2b_first_int) + ":" + (end - a2b_first_int) + "("
            + (end - start) + ")";
        buf1 += buf2;
        if (a2b_first_int == 0 && !tcpMsg.getSYN()) // Don't use relative
                                                    // sequence
          // numbers until handshake is
          // complete.
          a2b_first_int = thisdir.min_seq;
      } else if (dir == B2A) {
        /*
         * Use relative sequence numbers after the first segment in either
         * direction
         */
        buf2 = (start - b2a_first_int) + ":" + (end - b2a_first_int) + "("
            + (end - start) + ") ";
        buf1 += buf2;
        if (b2a_first_int == 0 && !tcpMsg.getSYN())
          b2a_first_int = thisdir.min_seq;
      }

      /* Acknowledgements */
      if (tcpMsg.getACK()) {
        buf2 = "";
        if (dir == A2B)
          buf2 = "ack " + (th_ack - b2a_first_int) + " ";
        else if (dir == B2A)
          buf2 = "ack " + (th_ack - a2b_first_int) + " ";
        buf1 += buf2;
      }

      /* Advertised Window */
      buf2 = "win " + eff_win + " ";
      buf1 += buf2;

      /* Retransmits */
      if (retrans) {
        buf1 += "R ";
      }

      /* Hardware Duplicates */
      if (hw_dup) {
        buf1 += "HD ";
      }

      /* Draw the segment -----./<------- */
      if (dir == A2B) {
        copy_current_time += one3rd_rtt;
        plotter_line(tlinepl, ptp_save.first_time, tline_left,
            copy_current_time, tline_left);
        plotter_line(tlinepl, ptp_save.first_time, tline_right,
            copy_current_time, tline_right);
        if (tcpMsg.getSYN() || tcpMsg.getFIN() || tcpMsg.getRST())
          plotter_perm_color(tlinepl, synfin_color);
        else
          plotter_perm_color(tlinepl, a2b_seg_color);
        plotter_line(tlinepl, current_time, tline_left, copy_current_time,
            tline_right);
        plotter_rarrow(tlinepl, copy_current_time, tline_right);
        plotter_perm_color(tlinepl, default_color);
        plotter_text(tlinepl, current_time, tline_left, "l", buf1);
      } else if (dir == B2A) {
        copy_current_time -= one3rd_rtt;
        plotter_line(tlinepl, ptp_save.first_time, tline_left,
            copy_current_time, tline_left);
        plotter_line(tlinepl, ptp_save.first_time, tline_right,
            copy_current_time, tline_right);
        if (tcpMsg.getSYN() || tcpMsg.getFIN() || tcpMsg.getRST())
          plotter_perm_color(tlinepl, synfin_color);
        else
          plotter_perm_color(tlinepl, b2a_seg_color);
        plotter_line(tlinepl, copy_current_time, tline_right, current_time,
            tline_left);
        plotter_larrow(tlinepl, current_time, tline_left);
        plotter_perm_color(tlinepl, default_color);
        plotter_text(tlinepl, copy_current_time, tline_right, "r", buf1);
      }

    }

    /* check for RESET */
    if (tcpMsg.getRST()) {
      long plot_at;

      /* if there's an ACK in this packet, plot it there */
      /* otherwise, plot it at the last valid ACK we have */
      if (tcpMsg.getACK())
        plot_at = th_ack;
      else
        plot_at = thisdir.ack;

      if (to_tsgpl != null) {
        plotter_temp_color(to_tsgpl, text_color);
        plotter_text(to_tsgpl, current_time, SeqRep(otherdir, plot_at), "a",
            "RST_IN");
      }
      if (from_tsgpl != null) {
        plotter_temp_color(from_tsgpl, text_color);
        plotter_text(from_tsgpl, current_time, SeqRep(thisdir, start), "a",
            "RST_OUT");
      }
      if (tcpMsg.getACK())
        ++thisdir.ack_pkts;

      // if (run_continuously) {
      // UpdateConnLists(tcp_ptr, ptcp);
      // }
      return (ptp_save);
    }

    /* do window stats (include first SYN too!) */
    thisdir.win_last = eff_win;

    if (tcpMsg.getACK() || tcpMsg.getSYN()) {
      if (eff_win > thisdir.win_max)
        thisdir.win_max = eff_win;

      /*
       * If we *are* going to use window scaling, i.e., if we saw both SYN
       * segments of the connection requesting window scaling, we flush out all
       * the window stats we gathered till now from the SYN segments.
       *
       * o We set the flag window_stats_updated_for_scaling to true o Set
       * win_min and win_max to the value found in this first window-scaled
       * segment o Reset win_tot value too, as this is used to calculate the
       * average window advertisement seen in this direction at the end o We
       * also use the field win_scaled_pkts for this purpose, so that in the end
       * we calculate
       *
       * avg_win_adv = win_tot/win_scaled_pkts // Refer output.c
       *
       * Note : for a connection that doesn't use window scaling,
       *
       * avg_win_adv = win_tot/packets // Refer again to output.c
       */

      if ((eff_win > 0)
          && (thisdir.f1323_ws && otherdir.f1323_ws && !tcpMsg.getSYN() && !thisdir.window_stats_updated_for_scaling)) {
        thisdir.window_stats_updated_for_scaling = true;
        thisdir.win_min = thisdir.win_max = eff_win;
        thisdir.win_tot = 0;
        thisdir.win_scaled_pkts = 1;
      } else if ((eff_win > 0)
          && ((thisdir.win_min == 0) || (eff_win < thisdir.win_min)))
        thisdir.win_min = eff_win;

      /* Add the window advertisement to win_tot */
      thisdir.win_tot += eff_win;
    }

    /* draw the ack and win in the other plotter */
    if (tcpMsg.getACK()) {
      int ack = th_ack;
      long winend;

      winend = ack + eff_win;

      if (eff_win == 0) {
        ++thisdir.win_zero_ct;
        if (to_tsgpl != null && show_zero_window) {
          plotter_temp_color(to_tsgpl, text_color);
          plotter_text(to_tsgpl, current_time, SeqRep(otherdir, winend), "a",
              "Z");
        }
      }

      ++thisdir.ack_pkts;
      if ((tcp_data_length == 0) && !tcpMsg.getSYN() && !tcpMsg.getFIN()
          && !tcpMsg.getRST()) {
        ++thisdir.pureack_pkts;
      }

      if (to_tsgpl != null && thisdir.time != -1) {
        plotter_perm_color(to_tsgpl, ack_color);
        plotter_line(to_tsgpl, thisdir.time, SeqRep(otherdir, thisdir.ack),
            current_time, SeqRep(otherdir, thisdir.ack));
        if (thisdir.ack != ack) {
          plotter_line(to_tsgpl, current_time, SeqRep(otherdir, thisdir.ack),
              current_time, SeqRep(otherdir, ack));
          if (show_rtt_dongles) {
            /* draw dongles for "interesting" acks */
            switch (ack_type) {
            case NORMAL: /* normal case */
              /* no dongle */
              break;
            case CUMUL: /* cumulative */
              /* won't happen, not plotted here */
              break;
            case TRIPLE: /* triple dupacks */
              /* won't happen, not plotted here */
              break;
            case AMBIG: /* ambiguous */
              plotter_temp_color(to_tsgpl, ackdongle_ambig_color);
              plotter_diamond(to_tsgpl, current_time, SeqRep(otherdir, ack));
              break;
            case NOSAMP: /* acks retransmitted stuff cumulatively */
              plotter_temp_color(to_tsgpl, ackdongle_nosample_color);
              plotter_diamond(to_tsgpl, current_time, SeqRep(otherdir, ack));
              break;
            }
          }
        } else {
          plotter_dtick(to_tsgpl, current_time, SeqRep(otherdir, ack));
          if (show_triple_dupack && (ack_type == TRIPLE)) {
            plotter_text(to_tsgpl, current_time, SeqRep(otherdir, ack), "a",
                "3"); /* '3' is for triple dupack */
          }
        }

        // /* Kevin Lahey's code */
        // if (ecn_echo && !tcpMsg.getSYN()) {
        // plotter_perm_color(to_tsgpl, ecn_color);
        // plotter_diamond(to_tsgpl, current_time, SeqRep(otherdir, ack));
        // }

        plotter_perm_color(to_tsgpl, window_color);
        plotter_line(to_tsgpl, thisdir.time, SeqRep(otherdir,
            old_this_windowend), current_time, SeqRep(otherdir,
            old_this_windowend));
        if (old_this_windowend != winend) {
          plotter_line(to_tsgpl, current_time, SeqRep(otherdir,
              old_this_windowend), current_time, SeqRep(otherdir, winend));
        } else {
          plotter_utick(to_tsgpl, current_time, SeqRep(otherdir, winend));
        }
      }

      // /* track the most sack blocks in a single ack */
      // if (ptcpo.sack_count > 0) {
      // ++thisdir.num_sacks;
      // if (ptcpo.sack_count > thisdir.max_sack_blocks)
      // thisdir.max_sack_blocks = ptcpo.sack_count;
      //
      // /* also see if any of them are DSACKS - weddy */
      // /*
      // * eventually may come back and fix this, what if we+++++ didn't see all
      // the
      // * rexmits and so LEAST wesn't set high enough, now it's too low
      // */
      // /* case 1, first block under cumack */
      // if (ptcpo.sacks[0].sack_right <= th_ack) {
      // thisdir.num_dsacks++;
      // if (otherdir.LEAST > 0) otherdir.LEAST--;
      // /* case 2, first block inside second */
      // } else if (ptcpo.sack_count > 1) {
      // if (ptcpo.sacks[0].sack_right <= ptcpo.sacks[1].sack_right
      // && ptcpo.sacks[0].sack_left >= ptcpo.sacks[1].sack_left)
      // {
      // thisdir.num_dsacks++;
      // if (otherdir.LEAST > 0) otherdir.LEAST--;
      // /* case 3, first and second block overlap */
      // } else if ((ptcpo.sacks[0].sack_left <=
      // ptcpo.sacks[1].sack_left &&
      // ptcpo.sacks[0].sack_right >
      // ptcpo.sacks[1].sack_left) ||
      // (ptcpo.sacks[0].sack_right >=
      // ptcpo.sacks[1].sack_right &&
      // ptcpo.sacks[0].sack_left <
      // ptcpo.sacks[1].sack_right)) {
      // thisdir.num_dsacks++;
      // if (otherdir.LEAST > 0) otherdir.LEAST--;
      // }
      // }
      // /*
      // * if we saw any dsacks from the other guy, we'll assume he did it on
      // * purpose and is a dsack tcp
      // */
      // if (thisdir.num_dsacks > 0) thisdir.tcp_strain = TCP_DSACK;
      // }

      // /* draw sacks, if appropriate */
      // if (to_tsgpl != null && show_sacks
      // && (ptcpo.sack_count > 0)) {
      // int scount;
      // int sack_top = ptcpo.sacks[0].sack_right;
      //
      // plotter_perm_color(to_tsgpl, sack_color);
      // for (scount = 0; scount < ptcpo.sack_count; ++scount) {
      // plotter_line(to_tsgpl,
      // current_time,
      // SeqRep(otherdir,ptcpo.sacks[scount].sack_left),
      // current_time,
      // SeqRep(otherdir,ptcpo.sacks[scount].sack_right));
      // /*
      // * make it easier to read multiple sacks by making them look like
      // |-----|
      // * (sideways)
      // */
      // plotter_htick(to_tsgpl,
      // current_time,
      // SeqRep(otherdir,ptcpo.sacks[scount].sack_left));
      // plotter_htick(to_tsgpl,
      // current_time,
      // SeqRep(otherdir,ptcpo.sacks[scount].sack_right));
      //
      // /* if there's more than one, label the order */
      // /* purple number to the right of the top ("right" edge) */
      // if (ptcpo.sack_count > 1) {
      // char buf[5]; /* can't be more than 1 digit! */
      // snprintf(buf,sizeof(buf),"%u",scount+1); /*
      // * 1-base, rather than
      // * 0-base
      // */
      // plotter_text(to_tsgpl,
      // current_time,
      // SeqRep(otherdir,ptcpo.sacks[scount].sack_right),
      // "r", buf);
      // }
      //
      // /* maintain the highest SACK so we can label them all at once */
      // if (SEQ_GREATERTHAN(ptcpo.sacks[scount].sack_right, sack_top))
      // sack_top = ptcpo.sacks[scount].sack_right;
      // }
      // /* change - just draw the 'S' above the highest one */
      // plotter_text(to_tsgpl, current_time,
      // SeqRep(otherdir,sack_top),
      // "a", "S"); /* 'S' is for Sack */
      // }
      thisdir.time = current_time;
      thisdir.ack = ack;

      /* thisdir.windowend = winend; (moved above "only" point) */
    } /* end tcpMsg.getACK() */

    /* do stats for initial window (first slow start) */
    /* (if there's data in this and we've NEVER seen */
    /* an ACK coming back from the other side) */
    /* this is for Mark Allman for slow start testing -- Mon Mar 10, 1997 */
    if (!otherdir.data_acked && tcpMsg.getACK()
        && ((otherdir.syn + 1) != th_ack)) {
      otherdir.data_acked = true;
    }
    if ((tcp_data_length > 0) && (!thisdir.data_acked)) {
      if (!retrans) {
        /* don't count it if it was retransmitted */
        thisdir.initialwin_bytes += tcp_data_length;
        thisdir.initialwin_segs += 1;
      }
    }

    /* do stats for congestion window (estimated) */
    /* estimate the congestion window as the number of outstanding */
    /* un-acked bytes */
    if (!tcpMsg.getSYN() && !out_order.value && !retrans) {
      long owin;
      /*
       * If there has been no ack from the other direction, owin is just bytes
       * in this pkt.
       */
      if (otherdir.ack == 0) {
        owin = end - start;
      } else {
        /*
         * ack always acks 'received + 1' bytes, so subtract 1 for owin
         */
        owin = end - (otherdir.ack - 1);
      }

      if (owin > thisdir.owin_max)
        thisdir.owin_max = owin;
      if ((owin > 0) && ((thisdir.owin_min == 0) || (owin < thisdir.owin_min)))
        thisdir.owin_min = owin;

      thisdir.owin_tot += owin;
      thisdir.owin_count++;

      /* adding mark's suggestion of weighted owin */
      if (thisdir.previous_owin_sample_time == 0) {
        /* if this is first ever sample for thisdir */
        thisdir.previous_owin_sample_time = thisdir.last_time;
        thisdir.previous_owin_sample = owin;
      } else {
        /* weight each owin sample with the duration that it exists for */
        sample_elapsed_time = (ptp_save.last_time - thisdir.previous_owin_sample_time) / 1000000;
        total_elapsed_time = (ptp_save.last_time - ptp_save.first_time) / 1000000;
        thisdir.owin_wavg += (long) ((thisdir.previous_owin_sample) * sample_elapsed_time);
        /* graph owin_wavg */
        if (thisdir.owin_plotter != null) {
          extend_line(
              thisdir.owin_wavg_line,
              thisdir.previous_owin_sample_time,
              (0 != total_elapsed_time) ? ((long) ((thisdir.owin_wavg) / total_elapsed_time))
                  : 0);
        }
        thisdir.previous_owin_sample_time = thisdir.last_time;
        thisdir.previous_owin_sample = owin;
      }

      /* graph owin */
      if (thisdir.owin_plotter != null) {
        extend_line(thisdir.owin_line, current_time, owin);
        if (show_rwinline) {
          extend_line(thisdir.rwin_line, current_time, otherdir.win_last);
        }
        extend_line(thisdir.owin_avg_line, current_time,
            (0 != thisdir.owin_count ? (thisdir.owin_tot / thisdir.owin_count)
                : 0));
      }
    }
    // if (run_continuously) {
    // UpdateConnLists(tcp_ptr, ptcp);
    // }

    return (ptp_save);
  }

  private boolean PIP_ISV4(Ip ipMsg) {
    return true;
  }

  /* represent the sequence numbers absolute or relative to 0 */
  protected long SeqRep(Tcb tcb, long seq) {
    if (graph_seq_zero) {
      return (seq - tcb.min_seq);
    } else {
      return (seq);
    }
  }

  protected boolean IsRTO(Tcb tcb, int s) {
    quadrant pquad = whichquad(tcb.ss, s);
    segment pseg;

    for (pseg = pquad.seglist_head; pseg != null; pseg = pseg.next) {
      if (s == (pseg.seq_lastbyte + 1)) {
        if (pseg.acked < 4)
          return true;
        else
          return false;
      }
    }

    return true;
  }

  protected void collapse_quad(quadrant pquad) {
    boolean freed;
    segment pseg;
    segment tmpseg;

    if ((pquad == null) || (pquad.seglist_head == null))
      return;

    pseg = pquad.seglist_head;
    while (pseg != null) {
      freed = false;
      if (pseg.next == null)
        break;

      /* if this segment has not been ACKed, then neither have the */
      /* ones that follow, so no need to continue */
      if (0 == pseg.acked)
        break;

      /* if this segment and the next one have both been ACKed and they */
      /* "fit together", then collapse them into one (larger) segment */
      if (0 != pseg.acked && 0 != pseg.next.acked
          && (pseg.seq_lastbyte + 1 == pseg.next.seq_firstbyte)) {
        pseg.seq_lastbyte = pseg.next.seq_lastbyte;

        /* the new ACK count is the ACK count of the later segment */
        pseg.acked = pseg.next.acked;

        /* the new "transmit time" is the greater of the two */
        if ((pseg.next.time > pseg.time))
          pseg.time = pseg.next.time;

        tmpseg = pseg.next;
        pseg.next = pseg.next.next;
        if (pseg.next != null)
          pseg.next.prev = pseg;
        if (tmpseg == pquad.seglist_tail)
          pquad.seglist_tail = pseg;
        tmpseg = null;
        freed = true;
      }

      if (!freed)
        pseg = pseg.next;
      /* else, see if the next one also can be collapsed into me */
    }

    /* see if the quadrant is now "full" */
    if ((pquad.seglist_head.seq_lastbyte - pquad.seglist_head.seq_firstbyte + 1) == QUADSIZE) {
      pquad.full = true;
    }
  }

  protected quadrant whichquad(seqspace sspace, int seq) {
    int qid = QUADNUM(seq);
    quadrant pquad;
    int qix;
    int qix_next;
    int qix_opposite;
    int qix_prev;

    /* optimize expected case, it's all set up correctly already */
    qix = qid - 1;
    if (null != (pquad = sspace.pquad[qix]) && null != pquad.next
        && null != pquad.prev)
      return (pquad);

    /* determine indices of "neighbor" quadrants */
    qix_next = (qix + 1) % 4;
    qix_opposite = (qix + 2) % 4;
    qix_prev = (qix + 3) % 4;

    /* make sure that THIS quadrant exists */
    if (sspace.pquad[qix] == null) {
      sspace.pquad[qix] = new quadrant();
    }

    /* make sure that the quadrant AFTER this one exists */
    if (sspace.pquad[qix_next] == null) {
      sspace.pquad[qix_next] = new quadrant();
    }

    /* make sure that the quadrant BEFORE this one exists */
    if (sspace.pquad[qix_prev] == null) {
      sspace.pquad[qix_prev] = new quadrant();
    }

    /* clear out the opposite side, we don't need it anymore */
    if (sspace.pquad[qix_opposite] != null) {
      sspace.pquad[qix_opposite] = null;

      sspace.pquad[qix_opposite] = null;
    }

    /* set all the pointers */
    sspace.pquad[qix].prev = sspace.pquad[qix_prev];
    sspace.pquad[qix].next = sspace.pquad[qix_next];
    sspace.pquad[qix_next].prev = sspace.pquad[qix];
    sspace.pquad[qix_prev].next = sspace.pquad[qix];
    sspace.pquad[qix_next].next = null;
    sspace.pquad[qix_prev].prev = null;

    return (sspace.pquad[qix]);
  }

  /*
   * check for not-uncommon error of hardware-level duplicates (same IP ID and
   * TCP sequence number)
   */
  protected boolean check_hw_dups(short id, int seq, Tcb tcb) {
    int i;
    Tcb.HardwareDups pshd;

    /* see if we've seen this one before */
    for (i = 0; i < SEGS_TO_REMEMBER; ++i) {
      pshd = tcb.hardware_dups[i];
      if (null == pshd) {
        pshd = new Tcb.HardwareDups();
        tcb.hardware_dups[i] = pshd;
      }

      if ((pshd.hwdup_seq == seq) && (pshd.hwdup_id == id)
          && (pshd.hwdup_seq != 0) && (pshd.hwdup_id != 0)) {
        /* count it */
        tcb.num_hardware_dups += 1;
        // if (warn_printhwdups) {
        // printf("%s.%s: saw hardware duplicate of TCP seq %lu, IP ID %u
        // (packet %lu == %lu)\n",
        // tcb.host_letter,tcb.ptwin.host_letter,
        // seq, id, pnum,pshd.hwdup_packnum);
        // }
        return (true);
      }
    }

    /* remember it */
    pshd = tcb.hardware_dups[tcb.hardware_dups_ix];
    pshd.hwdup_seq = seq;
    pshd.hwdup_id = id;
    pshd.hwdup_packnum = pnum;
    tcb.hardware_dups_ix = (tcb.hardware_dups_ix + 1) % SEGS_TO_REMEMBER;

    return (false);
  }

  protected void DoThru(Tcb ptcb, int nbytes, long current_time) {
    double etime;
    double thruput;
    String myname;
    String hisname;

    /* init, if not already done */
    if (0 == ptcb.thru_firsttime) {
      String title;

      ptcb.thru_firsttime = current_time;
      ptcb.thru_lasttime = current_time;
      ptcb.thru_pkts = 1;
      ptcb.thru_bytes = nbytes;

      /* bug fix from Michele Clark - UNC */
      if (ptcb.ptp.a2b == ptcb) {
        myname = ptcb.ptp.a_endpoint;
        hisname = ptcb.ptp.b_endpoint;
      } else {
        myname = ptcb.ptp.b_endpoint;
        hisname = ptcb.ptp.a_endpoint;
      }
      /* create the plotter file */
      title = myname + "_==>_" + hisname + " (throughput)";
      ptcb.thru_plotter = new_plotter(ptcb, null, title, "time",
          "thruput (bytes/sec)", THROUGHPUT_FILE_EXTENSION);
      if (graph_time_zero) {
        /* set graph zero points */
        plotter_nothing(ptcb.thru_plotter, current_time);
      }

      /* create lines for average and instantaneous values */
      ptcb.thru_avg_line = new_line(ptcb.thru_plotter, "avg. tput", "blue");
      ptcb.thru_inst_line = new_line(ptcb.thru_plotter, "inst. tput", "red");

      return;
    }

    /* if no data, then nothing to do */
    if (nbytes == 0)
      return;

    /* see if we should output the stats yet */
    if (ptcb.thru_pkts + 1 >= thru_interval) {

      /* compute stats for this interval */
      etime = current_time - ptcb.thru_firsttime;
      if (etime == 0.0)
        etime = 1000; /* ick, what if "no time" has passed?? */
      thruput = (double) ptcb.thru_bytes / ((double) etime / (double)Constants.SECOND);

      /* instantaneous plot */
      extend_line(ptcb.thru_inst_line, current_time, (int) thruput);

      /* compute stats for connection lifetime */
      etime = current_time - ptcb.ptp.first_time;
      if (etime == 0.0)
        etime = 1000; /* ick, what if "no time" has passed?? */
      thruput = (double) ptcb.data_bytes / ((double) etime / (double)Constants.SECOND);

      /* long-term average */
      extend_line(ptcb.thru_avg_line, current_time, (int) thruput);

      /* reset stats for this interval */
      ptcb.thru_firsttime = current_time;
      ptcb.thru_pkts = 0;
      ptcb.thru_bytes = 0;
    }

    /* immediate value in yellow ticks */
    if (plot_tput_instant) {
      etime = current_time - ptcb.thru_lasttime;
      if (etime == 0.0)
        etime = 1000; /* ick, what if "no time" has passed?? */
      thruput = (double) nbytes / ((double) etime / (double)Constants.SECOND);
      plotter_temp_color(ptcb.thru_plotter, "yellow");
      plotter_dot(ptcb.thru_plotter, current_time, (int) thruput);
    }

    /* add in the latest packet */
    ptcb.thru_lasttime = current_time;
    ++ptcb.thru_pkts;
    ptcb.thru_bytes += nbytes;
  }

  public static final int BSD_VERSION = 1;/*
                                           * Handling of duplicate ack's based
                                           * on the specifications of BSD code
                                           */
  public static final int LEGACY_VERSION = 2; /*
                                               * Handling of duplicate ack's
                                               * according to the old versions
                                               * of "tcptrace"
                                               */

  protected int ack_in(Tcb ptcb, int ack, int tcp_data_length, long eff_win,
      long current_time) {
    quadrant pquad;
    quadrant pquad_prev;
    segment pseg;
    boolean changed_one = false;
    boolean intervening_xmits = false;
    long last_xmit = 0;
    int ret = 0;

    int dup_ack_type; /*
                       * default type is the code based on BSD specifications
                       */

    /* check each segment in the segment list for the PREVIOUS quadrant */
    pquad = whichquad(ptcb.ss, ack);
    pquad_prev = pquad.prev;
    for (pseg = pquad_prev.seglist_head; pseg != null; pseg = pseg.next) {
      if (0 == pseg.acked) {
        ++pseg.acked;
        changed_one = true;
        ++ptcb.rtt_cumack;

        /* keep track of the newest transmission */
        if (pseg.time > last_xmit)
          last_xmit = pseg.time;
      }
    }
    if (changed_one)
      collapse_quad(pquad_prev);

    /* check each segment in the segment list for the CURRENT quadrant */
    changed_one = false;
    for (pseg = pquad.seglist_head; pseg != null; pseg = pseg.next) {
      if (ack <= pseg.seq_firstbyte) {
        /* doesn't cover anything else on the list */
        break;
      }

      /* keep track of the newest transmission */
      if ((pseg.time > last_xmit))
        last_xmit = pseg.time;

      /* (ELSE) ACK covers this sequence */
      if (0 != pseg.acked) {
        /*
         * default will be the BSD version, it can be changed by giving
         * '--turn_off_BSD_dupack' switch
         */
        dup_ack_type = (dup_ack_handling) ? BSD_VERSION : LEGACY_VERSION;

        /* default type is the specifications based on BSD code */
        switch (dup_ack_type) {
        case LEGACY_VERSION:
          if (ack == (pseg.seq_lastbyte + 1)) {
            ++pseg.acked; /* already acked this one */
            ++ptcb.rtt_dupack; /* one more duplicate ack */
            ret = CUMUL;
            if (pseg.acked == 4) {
              /* some people say these CAN'T have data */
              if ((tcp_data_length == 0) || triple_dupack_allows_data) {
                ++ptcb.rtt_triple_dupack;
                ret = TRIPLE;
              }
            }
          }
          break;
        case BSD_VERSION:
          /*
           * For an acknowledgement to be considered as duplicate ACK in BSD
           * version, following rules must be followed: 1) the received segment
           * should contain the biggest ACK TCP has seen, 2) the length of the
           * segment containing dup ack should be 0, 3) advertised window in
           * this segment should not change, 4) and there must be some
           * outstanding data
           */

          if ((ack == (pseg.seq_lastbyte + 1)) && (ack == ptcb.ptwin.ack)
              && (tcp_data_length == 0) && (eff_win == ptcb.ptwin.win_last)
              && (ptcb.owin_tot > 0)) {
            ++ptcb.rtt_dupack;
            ret = CUMUL;

            /* already acked this one */
            ++pseg.acked;
            if (pseg.acked == 4) {
              ++ptcb.rtt_triple_dupack;
              ret = TRIPLE;
            }
          } else
            pseg.acked = 1; /*
                             * received segment is not pure duplicate
                             * acknowledgement
                             */
        }
        continue;
      }
      /* ELSE !acked */

      ++pseg.acked;
      changed_one = true;

      if (ack == (pseg.seq_lastbyte + 1)) {
        /*
         * if ANY preceding segment was xmitted after this one, the the RTT
         * sample is invalid
         */
        intervening_xmits = ((last_xmit > pseg.time));

        ret = rtt_ackin(ptcb, pseg, intervening_xmits, current_time);
      } else {
        /* cumulatively ACKed */
        ++ptcb.rtt_cumack;
        ret = CUMUL;
      }
    }
    if (changed_one)
      collapse_quad(pquad);
    return (ret);
  }

  /*
   * rexmit: is the specified segment a retransmit? returns: number of
   * retransmitted bytes in segment, 0 if not a rexmit *pout_order to to true if
   * segment is out of order
   */
  protected int rexmit(Tcb ptcb, int seq, int len, BooleanOut pout_order,
      long current_time) {
    seqspace sspace = ptcb.ss;
    int seq_last = seq + len - 1;
    quadrant pquad;
    int rexlen = 0;

    /* unless told otherwise, it's IN order */
    pout_order.value = false;

    /* see which quadrant it starts in */
    pquad = whichquad(sspace, seq);

    /* add the new segment into the segment database */
    if (BOUNDARY(seq, seq_last)) {
      /* lives in two different quadrants (can't be > 2) */
      int seq1, seq2;
      int len1, len2;

      /* in first quadrant */
      seq1 = seq;
      len1 = LAST_SEQ(QUADNUM(seq1)) - seq1 + 1;
      rexlen = addseg(ptcb, pquad, seq1, len1, pout_order, current_time);

      /* in second quadrant */
      seq2 = FIRST_SEQ(QUADNUM(seq_last));
      len2 = len - len1;
      rexlen += addseg(ptcb, pquad.next, seq2, len2, pout_order, current_time);
    } else {
      rexlen = addseg(ptcb, pquad, seq, len, pout_order, current_time);
    }

    return (rexlen);
  }

  protected int rtt_ackin(Tcb ptcb, segment pseg, boolean rexmit_prev,
      long current_time) {
    long etime_rtt;
    int ret;

    long current_size = 0;

    /* how long did it take */
    etime_rtt = current_time - (pseg.time);

    if (rexmit_prev) {
      /* first, check for the situation in which the segment being ACKed */
      /* was sent a while ago, and we've been piddling around */
      /* retransmitting lost segments that came before it */
      ptcb.rtt_last = 0.0; /* don't use this sample, it's very long */
      etime_rtt = 0;

      ++ptcb.rtt_nosample; /* no sample, even though not ambig */
      ret = NOSAMP;
    } else if (pseg.retrans == 0) {
      ptcb.rtt_last = etime_rtt;

      if ((ptcb.rtt_min == 0) || (ptcb.rtt_min > etime_rtt))
        ptcb.rtt_min = etime_rtt;

      if (ptcb.rtt_max < etime_rtt)
        ptcb.rtt_max = etime_rtt;

      ptcb.rtt_sum += etime_rtt;
      ptcb.rtt_sum2 += etime_rtt * etime_rtt;
      ++ptcb.rtt_count;

      /* Collecting stats for full size segments */
      /*
       * Calculate the current_size of the segment, taking care of possible
       * sequence space wrap around
       */

      if (pseg.seq_lastbyte > pseg.seq_firstbyte)
        current_size = pseg.seq_lastbyte - pseg.seq_firstbyte + 1;
      else
        /*
         * MAX_32 is 0x1,0000,0000 So we don't need the "+ 1" while calculating
         * the size here
         */
        current_size = (Integer.MAX_VALUE - pseg.seq_firstbyte)
            + pseg.seq_lastbyte;

      if (0 == ptcb.rtt_full_size || (ptcb.rtt_full_size < current_size)) {
        /* Found a bigger segment.. Reset all stats. */
        ptcb.rtt_full_size = current_size;

        ptcb.rtt_full_min = etime_rtt;
        ptcb.rtt_full_max = etime_rtt;
        ptcb.rtt_full_sum = etime_rtt;
        ptcb.rtt_full_sum2 = (etime_rtt * etime_rtt);
        ptcb.rtt_full_count = 1;
      } else if (ptcb.rtt_full_size == current_size) {
        ++ptcb.rtt_full_count;

        if ((ptcb.rtt_full_min == 0) || (ptcb.rtt_full_min > etime_rtt))
          ptcb.rtt_full_min = etime_rtt;

        if (ptcb.rtt_full_max < etime_rtt)
          ptcb.rtt_full_max = etime_rtt;

        ptcb.rtt_full_sum += etime_rtt;
        ptcb.rtt_full_sum2 += (etime_rtt * etime_rtt);
      }
      ret = NORMAL;
    } else {
      /* retrans, can't use it */
      if ((ptcb.rtt_min_last == 0) || (ptcb.rtt_min_last > etime_rtt))
        ptcb.rtt_min_last = etime_rtt;

      if (ptcb.rtt_max_last < etime_rtt)
        ptcb.rtt_max_last = etime_rtt;

      ptcb.rtt_sum_last += etime_rtt;
      ptcb.rtt_sum2_last += etime_rtt * etime_rtt;
      ++ptcb.rtt_count_last;

      ++ptcb.rtt_amback; /* ambiguous ACK */

      /* numbers not useful for plotting/dumping */
      ptcb.rtt_last = 0.0;
      etime_rtt = 0;

      ret = AMBIG;
    }

    // /* dump RTT samples, if asked */
    // if (dump_rtt && (etime_rtt != 0.0)) {
    // dump_rtt_sample (ptcb, pseg, etime_rtt);
    // }

    /* plot RTT samples, if asked */
    if (graph_rtt && (pseg.retrans == 0)) {
      graph_rtt_sample(ptcb, pseg, etime_rtt, current_time);
    }

    return (ret);
  }

  /* graph RTT samples in milliseconds */
  protected void graph_rtt_sample(Tcb ptcb, segment pseg, long etime_rtt,
      long current_time) {
    String title;

    /* if the FILE is null, open file */
    if (ptcb.rtt_plotter == null) {
      String name_from;
      String name_to;
      if (ptcb == ptcb.ptp.a2b) {
        name_from = ptcb.ptp.a_endpoint;
        name_to = ptcb.ptp.b_endpoint;
      } else {
        name_from = ptcb.ptp.b_endpoint;
        name_to = ptcb.ptp.a_endpoint;
      }
      title = name_from + "_==>_" + name_to + " (rtt samples)";
      ptcb.rtt_plotter = new_plotter(ptcb, null, title, "time", "rtt (ms)",
          RTT_GRAPH_FILE_EXTENSION);
      plotter_perm_color(ptcb.rtt_plotter, "red");

      if (graph_time_zero) {
        /* set graph zero points */
        plotter_nothing(ptcb.rtt_plotter, current_time);
      }
      ptcb.rtt_line = new_line(ptcb.rtt_plotter, "rtt", "red");
    }

    if (etime_rtt <= 1)
      return;

    extend_line(ptcb.rtt_line, current_time, (int) (etime_rtt / Constants.MILLI_SECOND));
  }

  protected int addseg(Tcb ptcb, quadrant pquad, int thisseg_firstbyte,
      int len, BooleanOut pout_order, long current_time) {
    int thisseg_lastbyte = thisseg_firstbyte + len - 1;
    segment pseg;
    segment pseg_new;
    int rexlen = 0;
    boolean split = false;

    /* check each segment in the segment list */
    pseg = pquad.seglist_head;

    /* (optimize expected case, it just goes at the end) */
    if (null != pquad.seglist_tail
        && (thisseg_firstbyte > pquad.seglist_tail.seq_lastbyte))
      pseg = null;
    for (; pseg != null; pseg = pseg.next) {
      if (thisseg_firstbyte > pseg.seq_lastbyte) {
        /* goes beyond this one */
        continue;
      }

      if (thisseg_firstbyte < pseg.seq_firstbyte) {
        /* starts BEFORE this recorded segment */

        /* if it also FINISHES before this segment, then it's */
        /* out of order (otherwise it's a resend the collapsed */
        /* multiple segments into one */
        if (thisseg_lastbyte < pseg.seq_lastbyte)
          pout_order.value = true;

        /* make a new segment record for it */
        pseg_new = create_seg(thisseg_firstbyte, len, current_time);
        insert_seg_between(pquad, pseg_new, pseg.prev, pseg);

        /* see if we overlap the next segment in the list */
        if (thisseg_lastbyte < pseg.seq_firstbyte) {
          /* we don't overlap, so we're done */
          return (rexlen);
        } else {
          /* overlap him, split myself in 2 */

          /* adjust new piece to mate with old piece */
          pseg_new.seq_lastbyte = pseg.seq_firstbyte - 1;

          /* pretend to be just the second half of this segment */
          pseg_new.seq_lastbyte = pseg.seq_firstbyte - 1;
          thisseg_firstbyte = pseg.seq_firstbyte;
          len = thisseg_lastbyte - thisseg_firstbyte + 1;

          /* fall through */
        }
      }

      /* no ELSE, we might have fallen through */
      if (thisseg_firstbyte >= pseg.seq_firstbyte) {
        /* starts within this recorded sequence */
        ++pseg.retrans;
        if (!split)
          rtt_retrans(ptcb, pseg, current_time); /* must be a retransmission */

        if (thisseg_lastbyte <= pseg.seq_lastbyte) {
          /* entirely contained within this sequence */
          rexlen += len;
          return (rexlen);
        }
        /* else */
        /* we extend beyond this sequence, split ourself in 2 */
        /* (pretend to be just the second half of this segment) */
        split = true;
        rexlen += pseg.seq_lastbyte - thisseg_firstbyte + 1;
        thisseg_firstbyte = pseg.seq_lastbyte + 1;
        len = thisseg_lastbyte - thisseg_firstbyte + 1;
      }
    }

    /* if we got to the end, then it doesn't go BEFORE anybody, */
    /* tack it onto the end */
    pseg_new = create_seg(thisseg_firstbyte, len, current_time);
    insert_seg_between(pquad, pseg_new, pquad.seglist_tail, null);

    return (rexlen);
  }

  segment create_seg(int seq, int len, long current_time) {
    segment pseg = new segment();

    pseg.time = current_time;
    pseg.seq_firstbyte = seq;
    pseg.seq_lastbyte = seq + len - 1;

    return (pseg);
  }

  void insert_seg_between(quadrant pquad, segment pseg_new,
      segment pseg_before, segment pseg_after) {
    /* fix forward pointers */
    pseg_new.next = pseg_after;
    if (pseg_after != null) {
      pseg_after.prev = pseg_new;
    } else {
      /* I'm the tail of the list */
      pquad.seglist_tail = pseg_new;
    }

    /* fix backward pointers */
    pseg_new.prev = pseg_before;
    if (pseg_before != null) {
      pseg_before.next = pseg_new;
    } else {
      /* I'm the head of the list */
      pquad.seglist_head = pseg_new;
    }
  }

  void rtt_retrans(Tcb ptcb, segment pseg, long current_time) {
    long etime;

    if (0 == pseg.acked) {
      /* if it was acked, then it's been collapsed and these */
      /* are no longer meaningful */
      etime = current_time - (pseg.time);
      if (pseg.retrans > ptcb.retr_max)
        ptcb.retr_max = pseg.retrans;

      if (etime > ptcb.retr_max_tm)
        ptcb.retr_max_tm = etime;
      if ((ptcb.retr_min_tm == 0) || (etime < ptcb.retr_min_tm))
        ptcb.retr_min_tm = etime;

      ptcb.retr_tm_sum += etime;
      ptcb.retr_tm_sum2 += etime * etime;
      ++ptcb.retr_tm_count;
    }

    pseg.time = current_time;
  }

}
