Use Tree Navigation
public class

TokenTracker

extends Object
/*
 * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package sun.security.jgss;

import org.ietf.jgss.MessageProp;
import java.util.LinkedList;

/**
 * A utility class that implements a number list that keeps track of which
 * tokens have arrived by storing their token numbers in the list. It helps
 * detect old tokens, out of sequence tokens, and duplicate tokens.
 *
 * Each element of the list is an interval [a, b]. Its existence in the
 * list implies that all token numbers in the range a, a+1, ..., b-1, b
 * have arrived. Gaps in arrived token numbers are represented by the
 * numbers that fall in between two elements of the list. eg. {[a,b],
 * [c,d]} indicates that the token numbers b+1, ..., c-1 have not arrived
 * yet.
 *
 * The maximum number of intervals that we keep track of is
 * MAX_INTERVALS. Thus if there are too many gaps, then some of the older
 * sequence numbers are deleted from the list. The earliest sequence number
 * that exists in the list is the windowStart. The next expected sequence
 * number, or expectedNumber, is one greater than the latest sequence
 * number in the list.
 *
 * The list keeps track the first token number that should have arrived
 * (initNumber) so that it is able to detect if certain numbers occur after
 * the first valid token number but before windowStart. That would happen
 * if the number of elements (intervals) exceeds MAX_INTERVALS and some
 * initial elements had  to be deleted.
 *
 * The working of the list is optimized for the normal case where the
 * tokens arrive in sequence.
 *
 * @author Mayank Upadhyay
 * @since 1.4
 */

public class TokenTracker {

   
static final int MAX_INTERVALS = 5;

   
private int initNumber;
   
private int windowStart;
   
private int expectedNumber;

   
private int windowStartIndex = 0;

   
private LinkedList<Entry> list = new LinkedList<Entry>();

   
public TokenTracker(int initNumber) {

       
this.initNumber = initNumber;
       
this.windowStart = initNumber;
       
this.expectedNumber = initNumber;

       
// Make an entry with one less than the expected first token
       
Entry entry = new Entry(initNumber-1);

        list
.add(entry);
   
}

   
/**
     * Returns the index for the entry into which this number will fit. If
     * there is none, it returns the index of the last interval
     * which precedes this number. It returns -1 if the number needs to be
     * a in a new interval ahead of the whole list.
     */

   
private int getIntervalIndex(int number) {
       
Entry entry = null;
       
int i;
       
// Start from the rear to optimize for the normal case
       
for (i = list.size() - 1; i >= 0; i--) {
            entry
= list.get(i);
           
if (entry.compareTo(number) <= 0)
               
break;
       
}
       
return i;
   
}

   
/**
     * Sets the sequencing and replay information for the given token
     * number.
     *
     * The following represents the number line with positions of
     * initNumber, windowStart, expectedNumber marked on it. Regions in
     * between them show the different sequencing and replay state
     * possibilites for tokens that fall in there.
     *
     *  (1)      windowStart
     *           initNumber               expectedNumber
     *              |                           |
     *           ---|---------------------------|---
     *          GAP |    DUP/UNSEQ              | GAP
     *
     *
     *  (2)       initNumber   windowStart   expectedNumber
     *              |               |              |
     *           ---|---------------|--------------|---
     *          GAP |      OLD      |  DUP/UNSEQ   | GAP
     *
     *
     *  (3)                                windowStart
     *           expectedNumber            initNumber
     *              |                           |
     *           ---|---------------------------|---
     *    DUP/UNSEQ |           GAP             | DUP/UNSEQ
     *
     *
     *  (4)      expectedNumber    initNumber   windowStart
     *              |               |              |
     *           ---|---------------|--------------|---
     *    DUP/UNSEQ |        GAP    |    OLD       | DUP/UNSEQ
     *
     *
     *
     *  (5)      windowStart   expectedNumber    initNumber
     *              |               |              |
     *           ---|---------------|--------------|---
     *          OLD |    DUP/UNSEQ  |     GAP      | OLD
     *
     *
     *
     * (This analysis leaves out the possibility that expectedNumber passes
     * initNumber after wrapping around. That may be added later.)
     */

   
synchronized public final void getProps(int number, MessageProp prop) {

       
boolean gap = false;
       
boolean old = false;
       
boolean unsequenced = false;
       
boolean duplicate = false;

       
// System.out.println("\n\n==========");
       
// System.out.println("TokenTracker.getProps(): number=" + number);
       
// System.out.println(toString());

       
int pos = getIntervalIndex(number);
       
Entry entry = null;
       
if (pos != -1)
            entry
= list.get(pos);

       
// Optimize for the expected case:

       
if (number == expectedNumber) {
            expectedNumber
++;
       
} else {

           
// Next trivial case is to check for duplicate
           
if (entry != null && entry.contains(number))
                duplicate
= true;
           
else {

               
if (expectedNumber >= initNumber) {

                   
// Cases (1) and (2)

                   
if (number > expectedNumber) {
                        gap
= true;
                   
} else if (number >= windowStart) {
                        unsequenced
= true;
                   
} else if (number >= initNumber) {
                        old
= true;
                   
} else {
                        gap
= true;
                   
}
               
} else {

                   
// Cases (3), (4) and (5)

                   
if (number > expectedNumber) {
                       
if (number < initNumber) {
                            gap
= true;
                       
} else if (windowStart >= initNumber) {
                           
if (number >= windowStart) {
                               unsequenced
= true;
                           
} else
                                old
= true;
                       
} else {
                            old
= true;
                       
}
                   
} else if (windowStart > expectedNumber) {
                        unsequenced
= true;
                   
} else if (number < windowStart) {
                        old
= true;
                   
} else
                        unsequenced
= true;
               
}
           
}
       
}

       
if (!duplicate && !old)
            add
(number, pos);

       
if (gap)
            expectedNumber
= number+1;

        prop
.setSupplementaryStates(duplicate, old, unsequenced, gap,
                                   
0, null);

       
// System.out.println("Leaving with state:");
       
// System.out.println(toString());
       
// System.out.println("==========\n");
   
}

   
/**
     * Adds the number to the list just after the entry that is currently
     * at position prevEntryPos. If prevEntryPos is -1, then the number
     * will begin a new interval at the front of the list.
     */

   
private void add(int number, int prevEntryPos) {

       
Entry entry;
       
Entry entryBefore = null;
       
Entry entryAfter = null;

       
boolean appended = false;
       
boolean prepended = false;

       
if (prevEntryPos != -1) {
            entryBefore
= list.get(prevEntryPos);

           
// Can this number simply be added to the previous interval?
           
if (number == (entryBefore.getEnd() + 1)) {
                entryBefore
.setEnd(number);
                appended
= true;
           
}
       
}

       
// Now check the interval that follows this number

       
int nextEntryPos = prevEntryPos + 1;
       
if ((nextEntryPos) < list.size()) {
            entryAfter
= list.get(nextEntryPos);

           
// Can this number simply be added to the next interval?
           
if (number == (entryAfter.getStart() - 1)) {
               
if (!appended) {
                    entryAfter
.setStart(number);
               
} else {
                   
// Merge the two entries
                    entryAfter
.setStart(entryBefore.getStart());
                    list
.remove(prevEntryPos);
                   
// Index of any entry following this gets decremented
                   
if (windowStartIndex > prevEntryPos)
                        windowStartIndex
--;
               
}
                prepended
= true;
           
}
       
}

       
if (prepended || appended)
           
return;

       
/*
         * At this point we know that the number will start a new interval
         * which needs to be added to the list. We might have to recyle an
         * older entry in the list.
         */


       
if (list.size() < MAX_INTERVALS) {
            entry
= new Entry(number);
           
if (prevEntryPos  < windowStartIndex)
                windowStartIndex
++; // due to the insertion which will happen
       
} else {
           
/*
             * Delete the entry that marks the start of the current window.
             * The marker will automatically point to the next entry in the
             * list when this happens. If the current entry is at the end
             * of the list then set the marker to the start of the list.
             */

           
int oldWindowStartIndex = windowStartIndex;
           
if (windowStartIndex == (list.size() - 1))
                windowStartIndex
= 0;

            entry
= list.remove(oldWindowStartIndex);
            windowStart
= list.get(windowStartIndex).getStart();
            entry
.setStart(number);
            entry
.setEnd(number);

           
if (prevEntryPos >= oldWindowStartIndex) {
                prevEntryPos
--; // due to the deletion that just happened
           
} else {
               
/*
                 * If the start of the current window just moved from the
                 * end of the list to the front of the list, and if the new
                 * entry will be added to the front of the list, then
                 * the new entry is the actual window start.
                 * eg, Consider { [-10, -8], ..., [-6, -3], [3, 9]}. In
                 * this list, suppose the element [3, 9] is the start of
                 * the window and has to be deleted to make place to add
                 * [-12, -12]. The resultant list will be
                 * {[-12, -12], [-10, -8], ..., [-6, -3]} and the new start
                 * of the window should be the element [-12, -12], not
                 * [-10, -8] which succeeded [3, 9] in the old list.
                 */

               
if (oldWindowStartIndex != windowStartIndex) {
                   
// windowStartIndex is 0 at this point
                   
if (prevEntryPos == -1)
                       
// The new entry is going to the front
                        windowStart
= number;
               
} else {
                   
// due to the insertion which will happen:
                    windowStartIndex
++;
               
}
           
}
       
}

       
// Finally we are ready to actually add to the list at index
       
// 'prevEntryPos+1'

        list
.add(prevEntryPos+1, entry);
   
}

   
public String toString() {
       
StringBuffer buf = new StringBuffer("TokenTracker: ");
        buf
.append(" initNumber=").append(initNumber);
        buf
.append(" windowStart=").append(windowStart);
        buf
.append(" expectedNumber=").append(expectedNumber);
        buf
.append(" windowStartIndex=").append(windowStartIndex);
        buf
.append("\n\tIntervals are: {");
       
for (int i = 0; i < list.size(); i++) {
           
if (i != 0)
                buf
.append(", ");
            buf
.append(list.get(i).toString());
       
}
        buf
.append('}');
       
return buf.toString();
   
}

   
/**
     * An entry in the list that represents the sequence of received
     * tokens. Each entry is actaully an interval of numbers, all of which
     * have been received.
     */

   
class Entry {

       
private int start;
       
private int end;

       
Entry(int number) {
            start
= number;
           
end = number;
       
}

       
/**
         * Returns -1 if this interval represented by this entry precedes
         * the number, 0 if the the number is contained in the interval,
         * and -1 if the interval occurs after the number.
         */

       
final int compareTo(int number) {
           
if (start > number)
               
return 1;
           
else if (end < number)
               
return -1;
           
else
               
return 0;
       
}

       
final boolean contains(int number) {
           
return (number >= start &&
                    number
<= end);
       
}

       
final void append(int number) {
           
if (number == (end + 1))
               
end = number;
       
}

       
final void setInterval(int start, int end) {
           
this.start = start;
           
this.end = end;
       
}

       
final void setEnd(int end) {
           
this.end = end;
       
}

       
final void setStart(int start) {
           
this.start = start;
       
}

       
final int getStart() {
           
return start;
       
}

       
final int getEnd() {
           
return end;
       
}

       
public String toString() {
           
return ("[" + start + ", " + end + "]");
       
}

   
}
}