1/*
2 * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.jgss;
27
28import org.ietf.jgss.MessageProp;
29import java.util.LinkedList;
30
31/**
32 * A utility class that implements a number list that keeps track of which
33 * tokens have arrived by storing their token numbers in the list. It helps
34 * detect old tokens, out of sequence tokens, and duplicate tokens.
35 *
36 * Each element of the list is an interval [a, b]. Its existence in the
37 * list implies that all token numbers in the range a, a+1, ..., b-1, b
38 * have arrived. Gaps in arrived token numbers are represented by the
39 * numbers that fall in between two elements of the list. eg. {[a,b],
40 * [c,d]} indicates that the token numbers b+1, ..., c-1 have not arrived
41 * yet.
42 *
43 * The maximum number of intervals that we keep track of is
44 * MAX_INTERVALS. Thus if there are too many gaps, then some of the older
45 * sequence numbers are deleted from the list. The earliest sequence number
46 * that exists in the list is the windowStart. The next expected sequence
47 * number, or expectedNumber, is one greater than the latest sequence
48 * number in the list.
49 *
50 * The list keeps track the first token number that should have arrived
51 * (initNumber) so that it is able to detect if certain numbers occur after
52 * the first valid token number but before windowStart. That would happen
53 * if the number of elements (intervals) exceeds MAX_INTERVALS and some
54 * initial elements had  to be deleted.
55 *
56 * The working of the list is optimized for the normal case where the
57 * tokens arrive in sequence.
58 *
59 * @author Mayank Upadhyay
60 * @since 1.4
61 */
62public class TokenTracker {
63
64    static final int MAX_INTERVALS = 5;
65
66    private int initNumber;
67    private int windowStart;
68    private int expectedNumber;
69
70    private int windowStartIndex = 0;
71
72    private LinkedList<Entry> list = new LinkedList<Entry>();
73
74    public TokenTracker(int initNumber) {
75
76        this.initNumber = initNumber;
77        this.windowStart = initNumber;
78        this.expectedNumber = initNumber;
79
80        // Make an entry with one less than the expected first token
81        Entry entry = new Entry(initNumber-1);
82
83        list.add(entry);
84    }
85
86    /**
87     * Returns the index for the entry into which this number will fit. If
88     * there is none, it returns the index of the last interval
89     * which precedes this number. It returns -1 if the number needs to be
90     * a in a new interval ahead of the whole list.
91     */
92    private int getIntervalIndex(int number) {
93        Entry entry = null;
94        int i;
95        // Start from the rear to optimize for the normal case
96        for (i = list.size() - 1; i >= 0; i--) {
97            entry = list.get(i);
98            if (entry.compareTo(number) <= 0)
99                break;
100        }
101        return i;
102    }
103
104    /**
105     * Sets the sequencing and replay information for the given token
106     * number.
107     *
108     * The following represents the number line with positions of
109     * initNumber, windowStart, expectedNumber marked on it. Regions in
110     * between them show the different sequencing and replay state
111     * possibilites for tokens that fall in there.
112     *
113     *  (1)      windowStart
114     *           initNumber               expectedNumber
115     *              |                           |
116     *           ---|---------------------------|---
117     *          GAP |    DUP/UNSEQ              | GAP
118     *
119     *
120     *  (2)       initNumber   windowStart   expectedNumber
121     *              |               |              |
122     *           ---|---------------|--------------|---
123     *          GAP |      OLD      |  DUP/UNSEQ   | GAP
124     *
125     *
126     *  (3)                                windowStart
127     *           expectedNumber            initNumber
128     *              |                           |
129     *           ---|---------------------------|---
130     *    DUP/UNSEQ |           GAP             | DUP/UNSEQ
131     *
132     *
133     *  (4)      expectedNumber    initNumber   windowStart
134     *              |               |              |
135     *           ---|---------------|--------------|---
136     *    DUP/UNSEQ |        GAP    |    OLD       | DUP/UNSEQ
137     *
138     *
139     *
140     *  (5)      windowStart   expectedNumber    initNumber
141     *              |               |              |
142     *           ---|---------------|--------------|---
143     *          OLD |    DUP/UNSEQ  |     GAP      | OLD
144     *
145     *
146     *
147     * (This analysis leaves out the possibility that expectedNumber passes
148     * initNumber after wrapping around. That may be added later.)
149     */
150    synchronized public final void getProps(int number, MessageProp prop) {
151
152        boolean gap = false;
153        boolean old = false;
154        boolean unsequenced = false;
155        boolean duplicate = false;
156
157        // System.out.println("\n\n==========");
158        // System.out.println("TokenTracker.getProps(): number=" + number);
159        // System.out.println(toString());
160
161        int pos = getIntervalIndex(number);
162        Entry entry = null;
163        if (pos != -1)
164            entry = list.get(pos);
165
166        // Optimize for the expected case:
167
168        if (number == expectedNumber) {
169            expectedNumber++;
170        } else {
171
172            // Next trivial case is to check for duplicate
173            if (entry != null && entry.contains(number))
174                duplicate = true;
175            else {
176
177                if (expectedNumber >= initNumber) {
178
179                    // Cases (1) and (2)
180
181                    if (number > expectedNumber) {
182                        gap = true;
183                    } else if (number >= windowStart) {
184                        unsequenced = true;
185                    } else if (number >= initNumber) {
186                        old = true;
187                    } else {
188                        gap = true;
189                    }
190                } else {
191
192                    // Cases (3), (4) and (5)
193
194                    if (number > expectedNumber) {
195                        if (number < initNumber) {
196                            gap = true;
197                        } else if (windowStart >= initNumber) {
198                            if (number >= windowStart) {
199                               unsequenced = true;
200                            } else
201                                old = true;
202                        } else {
203                            old = true;
204                        }
205                    } else if (windowStart > expectedNumber) {
206                        unsequenced = true;
207                    } else if (number < windowStart) {
208                        old = true;
209                    } else
210                        unsequenced = true;
211                }
212            }
213        }
214
215        if (!duplicate && !old)
216            add(number, pos);
217
218        if (gap)
219            expectedNumber = number+1;
220
221        prop.setSupplementaryStates(duplicate, old, unsequenced, gap,
222                                    0, null);
223
224        // System.out.println("Leaving with state:");
225        // System.out.println(toString());
226        // System.out.println("==========\n");
227    }
228
229    /**
230     * Adds the number to the list just after the entry that is currently
231     * at position prevEntryPos. If prevEntryPos is -1, then the number
232     * will begin a new interval at the front of the list.
233     */
234    private void add(int number, int prevEntryPos) {
235
236        Entry entry;
237        Entry entryBefore = null;
238        Entry entryAfter = null;
239
240        boolean appended = false;
241        boolean prepended = false;
242
243        if (prevEntryPos != -1) {
244            entryBefore = list.get(prevEntryPos);
245
246            // Can this number simply be added to the previous interval?
247            if (number == (entryBefore.getEnd() + 1)) {
248                entryBefore.setEnd(number);
249                appended = true;
250            }
251        }
252
253        // Now check the interval that follows this number
254
255        int nextEntryPos = prevEntryPos + 1;
256        if ((nextEntryPos) < list.size()) {
257            entryAfter = list.get(nextEntryPos);
258
259            // Can this number simply be added to the next interval?
260            if (number == (entryAfter.getStart() - 1)) {
261                if (!appended) {
262                    entryAfter.setStart(number);
263                } else {
264                    // Merge the two entries
265                    entryAfter.setStart(entryBefore.getStart());
266                    list.remove(prevEntryPos);
267                    // Index of any entry following this gets decremented
268                    if (windowStartIndex > prevEntryPos)
269                        windowStartIndex--;
270                }
271                prepended = true;
272            }
273        }
274
275        if (prepended || appended)
276            return;
277
278        /*
279         * At this point we know that the number will start a new interval
280         * which needs to be added to the list. We might have to recyle an
281         * older entry in the list.
282         */
283
284        if (list.size() < MAX_INTERVALS) {
285            entry = new Entry(number);
286            if (prevEntryPos  < windowStartIndex)
287                windowStartIndex++; // due to the insertion which will happen
288        } else {
289            /*
290             * Delete the entry that marks the start of the current window.
291             * The marker will automatically point to the next entry in the
292             * list when this happens. If the current entry is at the end
293             * of the list then set the marker to the start of the list.
294             */
295            int oldWindowStartIndex = windowStartIndex;
296            if (windowStartIndex == (list.size() - 1))
297                windowStartIndex = 0;
298
299            entry = list.remove(oldWindowStartIndex);
300            windowStart = list.get(windowStartIndex).getStart();
301            entry.setStart(number);
302            entry.setEnd(number);
303
304            if (prevEntryPos >= oldWindowStartIndex) {
305                prevEntryPos--; // due to the deletion that just happened
306            } else {
307                /*
308                 * If the start of the current window just moved from the
309                 * end of the list to the front of the list, and if the new
310                 * entry will be added to the front of the list, then
311                 * the new entry is the actual window start.
312                 * eg, Consider { [-10, -8], ..., [-6, -3], [3, 9]}. In
313                 * this list, suppose the element [3, 9] is the start of
314                 * the window and has to be deleted to make place to add
315                 * [-12, -12]. The resultant list will be
316                 * {[-12, -12], [-10, -8], ..., [-6, -3]} and the new start
317                 * of the window should be the element [-12, -12], not
318                 * [-10, -8] which succeeded [3, 9] in the old list.
319                 */
320                if (oldWindowStartIndex != windowStartIndex) {
321                    // windowStartIndex is 0 at this point
322                    if (prevEntryPos == -1)
323                        // The new entry is going to the front
324                        windowStart = number;
325                } else {
326                    // due to the insertion which will happen:
327                    windowStartIndex++;
328                }
329            }
330        }
331
332        // Finally we are ready to actually add to the list at index
333        // 'prevEntryPos+1'
334
335        list.add(prevEntryPos+1, entry);
336    }
337
338    public String toString() {
339        StringBuilder sb = new StringBuilder("TokenTracker: ");
340        sb.append(" initNumber=").append(initNumber);
341        sb.append(" windowStart=").append(windowStart);
342        sb.append(" expectedNumber=").append(expectedNumber);
343        sb.append(" windowStartIndex=").append(windowStartIndex);
344        sb.append("\n\tIntervals are: {");
345        for (int i = 0; i < list.size(); i++) {
346            if (i != 0)
347                sb.append(", ");
348            sb.append(list.get(i).toString());
349        }
350        sb.append('}');
351        return sb.toString();
352    }
353
354    /**
355     * An entry in the list that represents the sequence of received
356     * tokens. Each entry is actaully an interval of numbers, all of which
357     * have been received.
358     */
359    class Entry {
360
361        private int start;
362        private int end;
363
364        Entry(int number) {
365            start = number;
366            end = number;
367        }
368
369        /**
370         * Returns -1 if this interval represented by this entry precedes
371         * the number, 0 if the number is contained in the interval,
372         * and -1 if the interval occurs after the number.
373         */
374        final int compareTo(int number) {
375            if (start > number)
376                return 1;
377            else if (end < number)
378                return -1;
379            else
380                return 0;
381        }
382
383        final boolean contains(int number) {
384            return (number >= start &&
385                    number <= end);
386        }
387
388        final void append(int number) {
389            if (number == (end + 1))
390                end = number;
391        }
392
393        final void setInterval(int start, int end) {
394            this.start = start;
395            this.end = end;
396        }
397
398        final void setEnd(int end) {
399            this.end = end;
400        }
401
402        final void setStart(int start) {
403            this.start = start;
404        }
405
406        final int getStart() {
407            return start;
408        }
409
410        final int getEnd() {
411            return end;
412        }
413
414        public String toString() {
415            return ("[" + start + ", " + end + "]");
416        }
417
418    }
419}
420