Home | History | Annotate | Download | only in anqp
      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