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;
}
}