1/*
2 * Copyright (c) 2014, 2016, 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 */
25package jdk.incubator.http.internal.hpack;
26
27import java.nio.ByteBuffer;
28import java.util.Arrays;
29
30import static java.lang.String.format;
31
32final class IntegerReader {
33
34    private static final int NEW             = 0;
35    private static final int CONFIGURED      = 1;
36    private static final int FIRST_BYTE_READ = 2;
37    private static final int DONE            = 4;
38
39    private int state = NEW;
40
41    private int N;
42    private int maxValue;
43    private int value;
44    private long r;
45    private long b = 1;
46
47    public IntegerReader configure(int N) {
48        return configure(N, Integer.MAX_VALUE);
49    }
50
51    //
52    // Why is it important to configure 'maxValue' here. After all we can wait
53    // for the integer to be fully read and then check it. Can't we?
54    //
55    // Two reasons.
56    //
57    // 1. Value wraps around long won't be unnoticed.
58    // 2. It can spit out an exception as soon as it becomes clear there's
59    // an overflow. Therefore, no need to wait for the value to be fully read.
60    //
61    public IntegerReader configure(int N, int maxValue) {
62        if (state != NEW) {
63            throw new IllegalStateException("Already configured");
64        }
65        checkPrefix(N);
66        if (maxValue < 0) {
67            throw new IllegalArgumentException(
68                    "maxValue >= 0: maxValue=" + maxValue);
69        }
70        this.maxValue = maxValue;
71        this.N = N;
72        state = CONFIGURED;
73        return this;
74    }
75
76    public boolean read(ByteBuffer input) {
77        if (state == NEW) {
78            throw new IllegalStateException("Configure first");
79        }
80        if (state == DONE) {
81            return true;
82        }
83        if (!input.hasRemaining()) {
84            return false;
85        }
86        if (state == CONFIGURED) {
87            int max = (2 << (N - 1)) - 1;
88            int n = input.get() & max;
89            if (n != max) {
90                value = n;
91                state = DONE;
92                return true;
93            } else {
94                r = max;
95            }
96            state = FIRST_BYTE_READ;
97        }
98        if (state == FIRST_BYTE_READ) {
99            // variable-length quantity (VLQ)
100            byte i;
101            do {
102                if (!input.hasRemaining()) {
103                    return false;
104                }
105                i = input.get();
106                long increment = b * (i & 127);
107                if (r + increment > maxValue) {
108                    throw new IllegalArgumentException(format(
109                            "Integer overflow: maxValue=%,d, value=%,d",
110                            maxValue, r + increment));
111                }
112                r += increment;
113                b *= 128;
114            } while ((128 & i) == 128);
115
116            value = (int) r;
117            state = DONE;
118            return true;
119        }
120        throw new InternalError(Arrays.toString(
121                new Object[]{state, N, maxValue, value, r, b}));
122    }
123
124    public int get() throws IllegalStateException {
125        if (state != DONE) {
126            throw new IllegalStateException("Has not been fully read yet");
127        }
128        return value;
129    }
130
131    private static void checkPrefix(int N) {
132        if (N < 1 || N > 8) {
133            throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
134        }
135    }
136
137    public IntegerReader reset() {
138        b = 1;
139        state = NEW;
140        return this;
141    }
142}
143