package com.android.server.wifi.anqp; import java.net.ProtocolException; import java.nio.ByteBuffer; /** * Holds an AP Geospatial Location ANQP Element, as specified in IEEE802.11-2012 section * 8.4.4.12. * <p/> * <p> * Section 8.4.2.24.10 of the IEEE802.11-2012 specification refers to RFC-3825 for the format of the * Geospatial location information. RFC-3825 has subsequently been obsoleted by RFC-6225 which * defines the same basic binary format for the DHCPv4 payload except that a few unused bits of the * Datum field have been reserved for other uses. * </p> * <p/> * <p> * RFC-3825 defines a resolution field for each of latitude, longitude and altitude as "the number * of significant bits" of precision in the respective values and implies through examples and * otherwise that the non-significant bits should be simply disregarded and the range of values are * calculated as the numeric interval obtained by varying the range of "insignificant bits" between * its extremes. As a simple example, consider the value 33 as a simple 8-bit number with three * significant bits: 33 is 00100001 binary and the leading 001 are the significant bits. With the * above definition, the range of numbers are [32,63] with 33 asymmetrically located at the low end * of the interval. In a more realistic setting an instrument, such as a GPS, would most likely * deliver measurements with a gaussian distribution around the exact value, meaning it is more * reasonable to assume the value as a "center" value with a symmetric uncertainty interval. * RFC-6225 redefines the "resolution" from RFC-3825 with an "uncertainty" value with these * properties, which is also the definition suggested here. * </p> * <p/> * <p> * The res fields provides the resolution as the exponent to a power of two, * e.g. 8 means 2^8 = +/- 256, 0 means 2^0 = +/- 1 and -7 means 2^-7 +/- 0.00781250. * Unknown resolution is indicated by not setting the respective resolution field in the RealValue. * </p> */ public class GEOLocationElement extends ANQPElement { public enum AltitudeType {Unknown, Meters, Floors} public enum Datum {Unknown, WGS84, NAD83Land, NAD83Water} private static final int ELEMENT_ID = 123; // ??? private static final int GEO_LOCATION_LENGTH = 16; private static final int LL_FRACTION_SIZE = 25; private static final int LL_WIDTH = 34; private static final int ALT_FRACTION_SIZE = 8; private static final int ALT_WIDTH = 30; private static final int RES_WIDTH = 6; private static final int ALT_TYPE_WIDTH = 4; private static final int DATUM_WIDTH = 8; private final RealValue mLatitude; private final RealValue mLongitude; private final RealValue mAltitude; private final AltitudeType mAltitudeType; private final Datum mDatum; public static class RealValue { private final double mValue; private final boolean mResolutionSet; private final int mResolution; public RealValue(double value) { mValue = value; mResolution = Integer.MIN_VALUE; mResolutionSet = false; } public RealValue(double value, int resolution) { mValue = value; mResolution = resolution; mResolutionSet = true; } public double getValue() { return mValue; } public boolean isResolutionSet() { return mResolutionSet; } public int getResolution() { return mResolution; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(String.format("%f", mValue)); if (mResolutionSet) { sb.append("+/-2^").append(mResolution); } return sb.toString(); } } public GEOLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload) throws ProtocolException { super(infoID); payload.get(); int locLength = payload.get() & Constants.BYTE_MASK; if (locLength != GEO_LOCATION_LENGTH) { throw new ProtocolException("GeoLocation length field value " + locLength + " incorrect, expected 16"); } if (payload.remaining() != GEO_LOCATION_LENGTH) { throw new ProtocolException("Bad buffer length " + payload.remaining() + ", expected 16"); } ReverseBitStream reverseBitStream = new ReverseBitStream(payload); int rawLatRes = (int) reverseBitStream.sliceOff(RES_WIDTH); double latitude = fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH); mLatitude = rawLatRes != 0 ? new RealValue(latitude, bitsToAbsResolution(rawLatRes, LL_WIDTH, LL_FRACTION_SIZE)) : new RealValue(latitude); int rawLonRes = (int) reverseBitStream.sliceOff(RES_WIDTH); double longitude = fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH); mLongitude = rawLonRes != 0 ? new RealValue(longitude, bitsToAbsResolution(rawLonRes, LL_WIDTH, LL_FRACTION_SIZE)) : new RealValue(longitude); int altType = (int) reverseBitStream.sliceOff(ALT_TYPE_WIDTH); mAltitudeType = altType < AltitudeType.values().length ? AltitudeType.values()[altType] : AltitudeType.Unknown; int rawAltRes = (int) reverseBitStream.sliceOff(RES_WIDTH); double altitude = fixToFloat(reverseBitStream.sliceOff(ALT_WIDTH), ALT_FRACTION_SIZE, ALT_WIDTH); mAltitude = rawAltRes != 0 ? new RealValue(altitude, bitsToAbsResolution(rawAltRes, ALT_WIDTH, ALT_FRACTION_SIZE)) : new RealValue(altitude); int datumValue = (int) reverseBitStream.sliceOff(DATUM_WIDTH); mDatum = datumValue < Datum.values().length ? Datum.values()[datumValue] : Datum.Unknown; } public RealValue getLatitude() { return mLatitude; } public RealValue getLongitude() { return mLongitude; } public RealValue getAltitude() { return mAltitude; } public AltitudeType getAltitudeType() { return mAltitudeType; } public Datum getDatum() { return mDatum; } @Override public String toString() { return "GEOLocation{" + "mLatitude=" + mLatitude + ", mLongitude=" + mLongitude + ", mAltitude=" + mAltitude + ", mAltitudeType=" + mAltitudeType + ", mDatum=" + mDatum + '}'; } private static class ReverseBitStream { private final byte[] mOctets; private int mBitoffset; private ReverseBitStream(ByteBuffer octets) { mOctets = new byte[octets.remaining()]; octets.get(mOctets); } private long sliceOff(int bits) { final int bn = mBitoffset + bits; int remaining = bits; long value = 0; while (mBitoffset < bn) { int sbit = mBitoffset & 0x7; // Bit #0 is MSB, inclusive int octet = mBitoffset >>> 3; // Copy the minimum of what's to the right of sbit // and how much more goes to the target int width = Math.min(Byte.SIZE - sbit, remaining); value = (value << width) | getBits(mOctets[octet], sbit, width); mBitoffset += width; remaining -= width; } return value; } private static int getBits(byte b, int b0, int width) { int mask = (1 << width) - 1; return (b >> (Byte.SIZE - b0 - width)) & mask; } } private static class BitStream { private final byte[] data; private int bitOffset; // bit 0 is MSB of data[0] private BitStream(int octets) { data = new byte[octets]; } private void append(long value, int width) { System.out.printf("Appending %x:%d\n", value, width); for (int sbit = width - 1; sbit >= 0; ) { int b0 = bitOffset >>> 3; int dbit = bitOffset & 0x7; int shr = sbit - 7 + dbit; int dmask = 0xff >>> dbit; if (shr >= 0) { data[b0] = (byte) ((data[b0] & ~dmask) | ((value >>> shr) & dmask)); bitOffset += Byte.SIZE - dbit; sbit -= Byte.SIZE - dbit; } else { data[b0] = (byte) ((data[b0] & ~dmask) | ((value << -shr) & dmask)); bitOffset += sbit + 1; sbit = -1; } } } private byte[] getOctets() { return data; } } static double fixToFloat(long value, int fractionSize, int width) { long sign = 1L << (width - 1); if ((value & sign) != 0) { value = -value; return -(double) (value & (sign - 1)) / (double) (1L << fractionSize); } else { return (double) (value & (sign - 1)) / (double) (1L << fractionSize); } } private static long floatToFix(double value, int fractionSize, int width) { return Math.round(value * (1L << fractionSize)) & ((1L << width) - 1); } private static final double LOG2_FACTOR = 1.0 / Math.log(2.0); /** * Convert an absolute variance value into absolute resolution representation, * where the variance = 2^resolution. * * @param variance The absolute variance * @return the absolute resolution. */ private static int getResolution(double variance) { return (int) Math.ceil(Math.log(variance) * LOG2_FACTOR); } /** * Convert an absolute resolution, into the "number of significant bits" for the given fixed * point notation as defined in RFC-3825 and refined in RFC-6225. * * @param resolution absolute resolution given as 2^resolution. * @param fieldWidth Full width of the fixed point number used to represent the value. * @param fractionBits Number of fraction bits in the fixed point number used to represent the * value. * @return The number of "significant bits". */ private static int absResolutionToBits(int resolution, int fieldWidth, int fractionBits) { return fieldWidth - fractionBits - 1 - resolution; } /** * Convert the protocol definition of "number of significant bits" into an absolute resolution. * * @param bits The number of "significant bits" from the binary protocol. * @param fieldWidth Full width of the fixed point number used to represent the value. * @param fractionBits Number of fraction bits in the fixed point number used to represent the * value. * @return The absolute resolution given as 2^resolution. */ private static int bitsToAbsResolution(long bits, int fieldWidth, int fractionBits) { return fieldWidth - fractionBits - 1 - (int) bits; } }