1package com.android.server.wifi.hotspot2;
2
3import com.android.server.wifi.Clock;
4import com.android.server.wifi.anqp.ANQPElement;
5import com.android.server.wifi.anqp.Constants;
6
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12
13public class ANQPData {
14    /**
15     * The regular cache time for entries with a non-zero domain id.
16     */
17    private static final long ANQP_QUALIFIED_CACHE_TIMEOUT = 3600000L;
18    /**
19     * The cache time for entries with a zero domain id. The zero domain id indicates that ANQP
20     * data from the AP may change at any time, thus a relatively short cache time is given to
21     * such data, but still long enough to avoid excessive querying.
22     */
23    private static final long ANQP_UNQUALIFIED_CACHE_TIMEOUT = 300000L;
24    /**
25     * This is the hold off time for pending queries, i.e. the time during which subsequent queries
26     * are squelched.
27     */
28    private static final long ANQP_HOLDOFF_TIME = 10000L;
29
30    /**
31     * Max value for the retry counter for unanswered queries. This limits the maximum time-out to
32     * ANQP_HOLDOFF_TIME * 2^MAX_RETRY. With current values this results in 640s.
33     */
34    private static final int MAX_RETRY = 6;
35
36    private final NetworkDetail mNetwork;
37    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
38    private final long mCtime;
39    private final long mExpiry;
40    private final int mRetry;
41    private final Clock mClock;
42
43    public ANQPData(Clock clock, NetworkDetail network,
44                    Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
45
46        mClock = clock;
47        mNetwork = network;
48        mANQPElements = anqpElements != null ? new HashMap<>(anqpElements) : null;
49        mCtime = mClock.currentTimeMillis();
50        mRetry = 0;
51        if (anqpElements == null) {
52            mExpiry = mCtime + ANQP_HOLDOFF_TIME;
53        }
54        else if (network.getAnqpDomainID() == 0) {
55            mExpiry = mCtime + ANQP_UNQUALIFIED_CACHE_TIMEOUT;
56        }
57        else {
58            mExpiry = mCtime + ANQP_QUALIFIED_CACHE_TIMEOUT;
59        }
60    }
61
62    public ANQPData(Clock clock, NetworkDetail network, ANQPData existing) {
63        mClock = clock;
64        mNetwork = network;
65        mANQPElements = null;
66        mCtime = mClock.currentTimeMillis();
67        if (existing == null) {
68            mRetry = 0;
69            mExpiry = mCtime + ANQP_HOLDOFF_TIME;
70        }
71        else {
72            mRetry = Math.max(existing.getRetry() + 1, MAX_RETRY);
73            mExpiry = ANQP_HOLDOFF_TIME * (1<<mRetry);
74        }
75    }
76
77    public List<Constants.ANQPElementType> disjoint(List<Constants.ANQPElementType> querySet) {
78        if (mANQPElements == null) {
79            // Ignore the query set for pending responses, it has minimal probability to happen
80            // and a new query will be reissued on the next round anyway.
81            return null;
82        }
83        else {
84            List<Constants.ANQPElementType> additions = new ArrayList<>();
85            for (Constants.ANQPElementType element : querySet) {
86                if (!mANQPElements.containsKey(element)) {
87                    additions.add(element);
88                }
89            }
90            return additions.isEmpty() ? null : additions;
91        }
92    }
93
94    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
95        return Collections.unmodifiableMap(mANQPElements);
96    }
97
98    public NetworkDetail getNetwork() {
99        return mNetwork;
100    }
101
102    public boolean expired() {
103        return expired(mClock.currentTimeMillis());
104    }
105
106    public boolean expired(long at) {
107        return mExpiry <= at;
108    }
109
110    protected boolean hasData() {
111        return mANQPElements != null;
112    }
113
114    protected void merge(Map<Constants.ANQPElementType, ANQPElement> data) {
115        if (data != null) {
116            mANQPElements.putAll(data);
117        }
118    }
119
120    protected boolean isValid(NetworkDetail nwk) {
121        return mANQPElements != null &&
122                nwk.getAnqpDomainID() == mNetwork.getAnqpDomainID() &&
123                mExpiry > mClock.currentTimeMillis();
124    }
125
126    private int getRetry() {
127        return mRetry;
128    }
129
130    public String toString(boolean brief) {
131        StringBuilder sb = new StringBuilder();
132        sb.append(mNetwork.toKeyString()).append(", domid ").append(mNetwork.getAnqpDomainID());
133        if (mANQPElements == null) {
134            sb.append(", unresolved, ");
135        }
136        else {
137            sb.append(", ").append(mANQPElements.size()).append(" elements, ");
138        }
139        long now = mClock.currentTimeMillis();
140        sb.append(Utils.toHMS(now-mCtime)).append(" old, expires in ").
141                append(Utils.toHMS(mExpiry-now)).append(' ');
142        if (brief) {
143            sb.append(expired(now) ? 'x' : '-');
144            sb.append(mANQPElements == null ? 'u' : '-');
145        }
146        else if (mANQPElements != null) {
147            sb.append(" data=").append(mANQPElements);
148        }
149        return sb.toString();
150    }
151
152    @Override
153    public String toString() {
154        return toString(true);
155    }
156}
157