GEOLocationElement.java revision 77f2b82a2e80af8da52c22d69a76def6d4209757
1package com.android.server.wifi.anqp;
2
3import java.net.ProtocolException;
4import java.nio.ByteBuffer;
5
6/**
7 * Holds an AP Geospatial Location ANQP Element, as specified in IEEE802.11-2012 section
8 * 8.4.4.12.
9 * <p/>
10 * <p>
11 * Section 8.4.2.24.10 of the IEEE802.11-2012 specification refers to RFC-3825 for the format of the
12 * Geospatial location information. RFC-3825 has subsequently been obsoleted by RFC-6225 which
13 * defines the same basic binary format for the DHCPv4 payload except that a few unused bits of the
14 * Datum field have been reserved for other uses.
15 * </p>
16 * <p/>
17 * <p>
18 * RFC-3825 defines a resolution field for each of latitude, longitude and altitude as "the number
19 * of significant bits" of precision in the respective values and implies through examples and
20 * otherwise that the non-significant bits should be simply disregarded and the range of values are
21 * calculated as the numeric interval obtained by varying the range of "insignificant bits" between
22 * its extremes. As a simple example, consider the value 33 as a simple 8-bit number with three
23 * significant bits: 33 is 00100001 binary and the leading 001 are the significant bits. With the
24 * above definition, the range of numbers are [32,63] with 33 asymmetrically located at the low end
25 * of the interval. In a more realistic setting an instrument, such as a GPS, would most likely
26 * deliver measurements with a gaussian distribution around the exact value, meaning it is more
27 * reasonable to assume the value as a "center" value with a symmetric uncertainty interval.
28 * RFC-6225 redefines the "resolution" from RFC-3825 with an "uncertainty" value with these
29 * properties, which is also the definition suggested here.
30 * </p>
31 * <p/>
32 * <p>
33 * The res fields provides the resolution as the exponent to a power of two,
34 * e.g. 8 means 2^8 = +/- 256, 0 means 2^0 = +/- 1 and -7 means 2^-7 +/- 0.00781250.
35 * Unknown resolution is indicated by not setting the respective resolution field in the RealValue.
36 * </p>
37 */
38public class GEOLocationElement extends ANQPElement {
39    public enum AltitudeType {Unknown, Meters, Floors}
40
41    public enum Datum {Unknown, WGS84, NAD83Land, NAD83Water}
42
43    private static final int ELEMENT_ID = 123;       // ???
44    private static final int GEO_LOCATION_LENGTH = 16;
45
46    private static final int LL_FRACTION_SIZE = 25;
47    private static final int LL_WIDTH = 34;
48    private static final int ALT_FRACTION_SIZE = 8;
49    private static final int ALT_WIDTH = 30;
50    private static final int RES_WIDTH = 6;
51    private static final int ALT_TYPE_WIDTH = 4;
52    private static final int DATUM_WIDTH = 8;
53
54    private final RealValue mLatitude;
55    private final RealValue mLongitude;
56    private final RealValue mAltitude;
57    private final AltitudeType mAltitudeType;
58    private final Datum mDatum;
59
60    public static class RealValue {
61        private final double mValue;
62        private final boolean mResolutionSet;
63        private final int mResolution;
64
65        public RealValue(double value) {
66            mValue = value;
67            mResolution = Integer.MIN_VALUE;
68            mResolutionSet = false;
69        }
70
71        public RealValue(double value, int resolution) {
72            mValue = value;
73            mResolution = resolution;
74            mResolutionSet = true;
75        }
76
77        public double getValue() {
78            return mValue;
79        }
80
81        public boolean isResolutionSet() {
82            return mResolutionSet;
83        }
84
85        public int getResolution() {
86            return mResolution;
87        }
88
89        @Override
90        public String toString() {
91            StringBuilder sb = new StringBuilder();
92            sb.append(String.format("%f", mValue));
93            if (mResolutionSet) {
94                sb.append("+/-2^").append(mResolution);
95            }
96            return sb.toString();
97        }
98    }
99
100    public GEOLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
101            throws ProtocolException {
102        super(infoID);
103
104        payload.get();
105        int locLength = payload.get() & Constants.BYTE_MASK;
106
107        if (locLength != GEO_LOCATION_LENGTH) {
108            throw new ProtocolException("GeoLocation length field value " + locLength +
109                    " incorrect, expected 16");
110        }
111        if (payload.remaining() != GEO_LOCATION_LENGTH) {
112            throw new ProtocolException("Bad buffer length " + payload.remaining() +
113                    ", expected 16");
114        }
115
116        ReverseBitStream reverseBitStream = new ReverseBitStream(payload);
117
118        int rawLatRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
119        double latitude =
120                fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
121
122        mLatitude = rawLatRes != 0 ?
123                new RealValue(latitude, bitsToAbsResolution(rawLatRes, LL_WIDTH,
124                        LL_FRACTION_SIZE)) :
125                new RealValue(latitude);
126
127        int rawLonRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
128        double longitude =
129                fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
130
131        mLongitude = rawLonRes != 0 ?
132                new RealValue(longitude, bitsToAbsResolution(rawLonRes, LL_WIDTH,
133                        LL_FRACTION_SIZE)) :
134                new RealValue(longitude);
135
136        int altType = (int) reverseBitStream.sliceOff(ALT_TYPE_WIDTH);
137        mAltitudeType = altType < AltitudeType.values().length ?
138                AltitudeType.values()[altType] :
139                AltitudeType.Unknown;
140
141        int rawAltRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
142        double altitude = fixToFloat(reverseBitStream.sliceOff(ALT_WIDTH), ALT_FRACTION_SIZE,
143                ALT_WIDTH);
144
145        mAltitude = rawAltRes != 0 ?
146                new RealValue(altitude, bitsToAbsResolution(rawAltRes, ALT_WIDTH,
147                        ALT_FRACTION_SIZE)) :
148                new RealValue(altitude);
149
150        int datumValue = (int) reverseBitStream.sliceOff(DATUM_WIDTH);
151        mDatum = datumValue < Datum.values().length ? Datum.values()[datumValue] : Datum.Unknown;
152    }
153
154    public RealValue getLatitude() {
155        return mLatitude;
156    }
157
158    public RealValue getLongitude() {
159        return mLongitude;
160    }
161
162    public RealValue getAltitude() {
163        return mAltitude;
164    }
165
166    public AltitudeType getAltitudeType() {
167        return mAltitudeType;
168    }
169
170    public Datum getDatum() {
171        return mDatum;
172    }
173
174    @Override
175    public String toString() {
176        return "GEOLocation{" +
177                "mLatitude=" + mLatitude +
178                ", mLongitude=" + mLongitude +
179                ", mAltitude=" + mAltitude +
180                ", mAltitudeType=" + mAltitudeType +
181                ", mDatum=" + mDatum +
182                '}';
183    }
184
185    private static class ReverseBitStream {
186
187        private final byte[] mOctets;
188        private int mBitoffset;
189
190        private ReverseBitStream(ByteBuffer octets) {
191            mOctets = new byte[octets.remaining()];
192            octets.get(mOctets);
193        }
194
195        private long sliceOff(int bits) {
196            final int bn = mBitoffset + bits;
197            int remaining = bits;
198            long value = 0;
199
200            while (mBitoffset < bn) {
201                int sbit = mBitoffset & 0x7;        // Bit #0 is MSB, inclusive
202                int octet = mBitoffset >>> 3;
203
204                // Copy the minimum of what's to the right of sbit
205                // and how much more goes to the target
206                int width = Math.min(Byte.SIZE - sbit, remaining);
207
208                value = (value << width) | getBits(mOctets[octet], sbit, width);
209
210                mBitoffset += width;
211                remaining -= width;
212            }
213
214            return value;
215        }
216
217        private static int getBits(byte b, int b0, int width) {
218            int mask = (1 << width) - 1;
219            return (b >> (Byte.SIZE - b0 - width)) & mask;
220        }
221    }
222
223    private static class BitStream {
224
225        private final byte[] data;
226        private int bitOffset;              // bit 0 is MSB of data[0]
227
228        private BitStream(int octets) {
229            data = new byte[octets];
230        }
231
232        private void append(long value, int width) {
233            System.out.printf("Appending %x:%d\n", value, width);
234            for (int sbit = width - 1; sbit >= 0; ) {
235                int b0 = bitOffset >>> 3;
236                int dbit = bitOffset & 0x7;
237
238                int shr = sbit - 7 + dbit;
239                int dmask = 0xff >>> dbit;
240
241                if (shr >= 0) {
242                    data[b0] = (byte) ((data[b0] & ~dmask) | ((value >>> shr) & dmask));
243                    bitOffset += Byte.SIZE - dbit;
244                    sbit -= Byte.SIZE - dbit;
245                } else {
246                    data[b0] = (byte) ((data[b0] & ~dmask) | ((value << -shr) & dmask));
247                    bitOffset += sbit + 1;
248                    sbit = -1;
249                }
250            }
251        }
252
253        private byte[] getOctets() {
254            return data;
255        }
256    }
257
258    static double fixToFloat(long value, int fractionSize, int width) {
259        long sign = 1L << (width - 1);
260        if ((value & sign) != 0) {
261            value = -value;
262            return -(double) (value & (sign - 1)) / (double) (1L << fractionSize);
263        } else {
264            return (double) (value & (sign - 1)) / (double) (1L << fractionSize);
265        }
266    }
267
268    private static long floatToFix(double value, int fractionSize, int width) {
269        return Math.round(value * (1L << fractionSize)) & ((1L << width) - 1);
270    }
271
272    private static final double LOG2_FACTOR = 1.0 / Math.log(2.0);
273
274    /**
275     * Convert an absolute variance value into absolute resolution representation,
276     * where the variance = 2^resolution.
277     *
278     * @param variance The absolute variance
279     * @return the absolute resolution.
280     */
281    private static int getResolution(double variance) {
282        return (int) Math.ceil(Math.log(variance) * LOG2_FACTOR);
283    }
284
285    /**
286     * Convert an absolute resolution, into the "number of significant bits" for the given fixed
287     * point notation as defined in RFC-3825 and refined in RFC-6225.
288     *
289     * @param resolution   absolute resolution given as 2^resolution.
290     * @param fieldWidth   Full width of the fixed point number used to represent the value.
291     * @param fractionBits Number of fraction bits in the fixed point number used to represent the
292     *                     value.
293     * @return The number of "significant bits".
294     */
295    private static int absResolutionToBits(int resolution, int fieldWidth, int fractionBits) {
296        return fieldWidth - fractionBits - 1 - resolution;
297    }
298
299    /**
300     * Convert the protocol definition of "number of significant bits" into an absolute resolution.
301     *
302     * @param bits         The number of "significant bits" from the binary protocol.
303     * @param fieldWidth   Full width of the fixed point number used to represent the value.
304     * @param fractionBits Number of fraction bits in the fixed point number used to represent the
305     *                     value.
306     * @return The absolute resolution given as 2^resolution.
307     */
308    private static int bitsToAbsResolution(long bits, int fieldWidth, int fractionBits) {
309        return fieldWidth - fractionBits - 1 - (int) bits;
310    }
311}
312