1 package com.android.server.wifi.anqp; 2 3 import java.net.ProtocolException; 4 import 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 */ 38 public 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