blob: 5abf164a9a1210743206776099c479d22bf81806 [file] [log] [blame]
package com.android.server.wifi.hotspot2;
import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
import android.net.wifi.ScanResult;
import android.util.Log;
import com.android.server.wifi.anqp.ANQPElement;
import com.android.server.wifi.anqp.Constants;
import com.android.server.wifi.anqp.RawByteElement;
import com.android.server.wifi.anqp.VenueNameElement;
import com.android.server.wifi.util.InformationElementUtil;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class NetworkDetail {
private static final boolean DBG = false;
private static final String TAG = "NetworkDetail:";
public enum Ant {
Private,
PrivateWithGuest,
ChargeablePublic,
FreePublic,
Personal,
EmergencyOnly,
Resvd6,
Resvd7,
Resvd8,
Resvd9,
Resvd10,
Resvd11,
Resvd12,
Resvd13,
TestOrExperimental,
Wildcard
}
public enum HSRelease {
R1,
R2,
Unknown
}
// General identifiers:
private final String mSSID;
private final long mHESSID;
private final long mBSSID;
// BSS Load element:
private final int mStationCount;
private final int mChannelUtilization;
private final int mCapacity;
//channel detailed information
/*
* 0 -- 20 MHz
* 1 -- 40 MHz
* 2 -- 80 MHz
* 3 -- 160 MHz
* 4 -- 80 + 80 MHz
*/
private final int mChannelWidth;
private final int mPrimaryFreq;
private final int mCenterfreq0;
private final int mCenterfreq1;
/*
* 802.11 Standard (calculated from Capabilities and Supported Rates)
* 0 -- Unknown
* 1 -- 802.11a
* 2 -- 802.11b
* 3 -- 802.11g
* 4 -- 802.11n
* 7 -- 802.11ac
*/
private final int mWifiMode;
private final int mMaxRate;
/*
* From Interworking element:
* mAnt non null indicates the presence of Interworking, i.e. 802.11u
* mVenueGroup and mVenueType may be null if not present in the Interworking element.
*/
private final Ant mAnt;
private final boolean mInternet;
private final VenueNameElement.VenueGroup mVenueGroup;
private final VenueNameElement.VenueType mVenueType;
/*
* From HS20 Indication element:
* mHSRelease is null only if the HS20 Indication element was not present.
* mAnqpDomainID is set to -1 if not present in the element.
*/
private final HSRelease mHSRelease;
private final int mAnqpDomainID;
/*
* From beacon:
* mAnqpOICount is how many additional OIs are available through ANQP.
* mRoamingConsortiums is either null, if the element was not present, or is an array of
* 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
*/
private final int mAnqpOICount;
private final long[] mRoamingConsortiums;
private int mDtimInterval = -1;
private final InformationElementUtil.ExtendedCapabilities mExtendedCapabilities;
private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements,
List<String> anqpLines, int freq) {
if (infoElements == null) {
throw new IllegalArgumentException("Null information elements");
}
mBSSID = Utils.parseMac(bssid);
String ssid = null;
byte[] ssidOctets = null;
InformationElementUtil.BssLoad bssLoad = new InformationElementUtil.BssLoad();
InformationElementUtil.Interworking interworking =
new InformationElementUtil.Interworking();
InformationElementUtil.RoamingConsortium roamingConsortium =
new InformationElementUtil.RoamingConsortium();
InformationElementUtil.Vsa vsa = new InformationElementUtil.Vsa();
InformationElementUtil.HtOperation htOperation = new InformationElementUtil.HtOperation();
InformationElementUtil.VhtOperation vhtOperation =
new InformationElementUtil.VhtOperation();
InformationElementUtil.ExtendedCapabilities extendedCapabilities =
new InformationElementUtil.ExtendedCapabilities();
InformationElementUtil.TrafficIndicationMap trafficIndicationMap =
new InformationElementUtil.TrafficIndicationMap();
InformationElementUtil.SupportedRates supportedRates =
new InformationElementUtil.SupportedRates();
InformationElementUtil.SupportedRates extendedSupportedRates =
new InformationElementUtil.SupportedRates();
RuntimeException exception = null;
ArrayList<Integer> iesFound = new ArrayList<Integer>();
try {
for (ScanResult.InformationElement ie : infoElements) {
iesFound.add(ie.id);
switch (ie.id) {
case ScanResult.InformationElement.EID_SSID:
ssidOctets = ie.bytes;
break;
case ScanResult.InformationElement.EID_BSS_LOAD:
bssLoad.from(ie);
break;
case ScanResult.InformationElement.EID_HT_OPERATION:
htOperation.from(ie);
break;
case ScanResult.InformationElement.EID_VHT_OPERATION:
vhtOperation.from(ie);
break;
case ScanResult.InformationElement.EID_INTERWORKING:
interworking.from(ie);
break;
case ScanResult.InformationElement.EID_ROAMING_CONSORTIUM:
roamingConsortium.from(ie);
break;
case ScanResult.InformationElement.EID_VSA:
vsa.from(ie);
break;
case ScanResult.InformationElement.EID_EXTENDED_CAPS:
extendedCapabilities.from(ie);
break;
case ScanResult.InformationElement.EID_TIM:
trafficIndicationMap.from(ie);
break;
case ScanResult.InformationElement.EID_SUPPORTED_RATES:
supportedRates.from(ie);
break;
case ScanResult.InformationElement.EID_EXTENDED_SUPPORTED_RATES:
extendedSupportedRates.from(ie);
break;
default:
break;
}
}
}
catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) {
Log.d(Utils.hs2LogTag(getClass()), "Caught " + e);
if (ssidOctets == null) {
throw new IllegalArgumentException("Malformed IE string (no SSID)", e);
}
exception = e;
}
if (ssidOctets != null) {
/*
* Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the
* encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is
* therefore always made with a fall back to 8859-1 under normal circumstances.
* If, however, a previous exception was detected and the UTF-8 bit is set, failure to
* decode the SSID will be used as an indication that the whole frame is malformed and
* an exception will be triggered.
*/
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
try {
CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets));
ssid = decoded.toString();
}
catch (CharacterCodingException cce) {
ssid = null;
}
if (ssid == null) {
if (extendedCapabilities.isStrictUtf8() && exception != null) {
throw new IllegalArgumentException("Failed to decode SSID in dubious IE string");
}
else {
ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1);
}
}
}
mSSID = ssid;
mHESSID = interworking.hessid;
mStationCount = bssLoad.stationCount;
mChannelUtilization = bssLoad.channelUtilization;
mCapacity = bssLoad.capacity;
mAnt = interworking.ant;
mInternet = interworking.internet;
mVenueGroup = interworking.venueGroup;
mVenueType = interworking.venueType;
mHSRelease = vsa.hsRelease;
mAnqpDomainID = vsa.anqpDomainID;
mAnqpOICount = roamingConsortium.anqpOICount;
mRoamingConsortiums = roamingConsortium.roamingConsortiums;
mExtendedCapabilities = extendedCapabilities;
mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
//set up channel info
mPrimaryFreq = freq;
if (vhtOperation.isValid()) {
// 80 or 160 MHz
mChannelWidth = vhtOperation.getChannelWidth();
mCenterfreq0 = vhtOperation.getCenterFreq0();
mCenterfreq1 = vhtOperation.getCenterFreq1();
} else {
mChannelWidth = htOperation.getChannelWidth();
mCenterfreq0 = htOperation.getCenterFreq0(mPrimaryFreq);
mCenterfreq1 = 0;
}
// If trafficIndicationMap is not valid, mDtimPeriod will be negative
mDtimInterval = trafficIndicationMap.mDtimPeriod;
int maxRateA = 0;
int maxRateB = 0;
// If we got some Extended supported rates, consider them, if not default to 0
if (extendedSupportedRates.isValid()) {
// rates are sorted from smallest to largest in InformationElement
maxRateB = extendedSupportedRates.mRates.get(extendedSupportedRates.mRates.size() - 1);
}
// Only process the determination logic if we got a 'SupportedRates'
if (supportedRates.isValid()) {
maxRateA = supportedRates.mRates.get(supportedRates.mRates.size() - 1);
mMaxRate = maxRateA > maxRateB ? maxRateA : maxRateB;
mWifiMode = InformationElementUtil.WifiMode.determineMode(mPrimaryFreq, mMaxRate,
vhtOperation.isValid(),
iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION),
iesFound.contains(ScanResult.InformationElement.EID_ERP));
} else {
mWifiMode = 0;
mMaxRate = 0;
Log.w("WifiMode", mSSID + ", Invalid SupportedRates!!!");
}
if (DBG) {
Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
+ " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1
+ (extendedCapabilities.is80211McRTTResponder ? "Support RTT reponder"
: "Do not support RTT responder"));
Log.v("WifiMode", mSSID
+ ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode)
+ ", Freq: " + mPrimaryFreq
+ ", mMaxRate: " + mMaxRate
+ ", VHT: " + String.valueOf(vhtOperation.isValid())
+ ", HT: " + String.valueOf(
iesFound.contains(ScanResult.InformationElement.EID_HT_OPERATION))
+ ", ERP: " + String.valueOf(
iesFound.contains(ScanResult.InformationElement.EID_ERP))
+ ", SupportedRates: " + supportedRates.toString()
+ " ExtendedSupportedRates: " + extendedSupportedRates.toString());
}
}
private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
ByteBuffer payload = data.duplicate().order(data.order());
payload.limit(payload.position() + plLength);
data.position(data.position() + plLength);
return payload;
}
private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
mSSID = base.mSSID;
mBSSID = base.mBSSID;
mHESSID = base.mHESSID;
mStationCount = base.mStationCount;
mChannelUtilization = base.mChannelUtilization;
mCapacity = base.mCapacity;
mAnt = base.mAnt;
mInternet = base.mInternet;
mVenueGroup = base.mVenueGroup;
mVenueType = base.mVenueType;
mHSRelease = base.mHSRelease;
mAnqpDomainID = base.mAnqpDomainID;
mAnqpOICount = base.mAnqpOICount;
mRoamingConsortiums = base.mRoamingConsortiums;
mExtendedCapabilities =
new InformationElementUtil.ExtendedCapabilities(base.mExtendedCapabilities);
mANQPElements = anqpElements;
mChannelWidth = base.mChannelWidth;
mPrimaryFreq = base.mPrimaryFreq;
mCenterfreq0 = base.mCenterfreq0;
mCenterfreq1 = base.mCenterfreq1;
mDtimInterval = base.mDtimInterval;
mWifiMode = base.mWifiMode;
mMaxRate = base.mMaxRate;
}
public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
return new NetworkDetail(this, anqpElements);
}
public boolean queriable(List<Constants.ANQPElementType> queryElements) {
return mAnt != null &&
(Constants.hasBaseANQPElements(queryElements) ||
Constants.hasR2Elements(queryElements) && mHSRelease == HSRelease.R2);
}
public boolean has80211uInfo() {
return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
}
public boolean hasInterworking() {
return mAnt != null;
}
public String getSSID() {
return mSSID;
}
public String getTrimmedSSID() {
for (int n = 0; n < mSSID.length(); n++) {
if (mSSID.charAt(n) != 0) {
return mSSID;
}
}
return "";
}
public long getHESSID() {
return mHESSID;
}
public long getBSSID() {
return mBSSID;
}
public int getStationCount() {
return mStationCount;
}
public int getChannelUtilization() {
return mChannelUtilization;
}
public int getCapacity() {
return mCapacity;
}
public boolean isInterworking() {
return mAnt != null;
}
public Ant getAnt() {
return mAnt;
}
public boolean isInternet() {
return mInternet;
}
public VenueNameElement.VenueGroup getVenueGroup() {
return mVenueGroup;
}
public VenueNameElement.VenueType getVenueType() {
return mVenueType;
}
public HSRelease getHSRelease() {
return mHSRelease;
}
public int getAnqpDomainID() {
return mAnqpDomainID;
}
public byte[] getOsuProviders() {
if (mANQPElements == null) {
return null;
}
ANQPElement osuProviders = mANQPElements.get(Constants.ANQPElementType.HSOSUProviders);
return osuProviders != null ? ((RawByteElement) osuProviders).getPayload() : null;
}
public int getAnqpOICount() {
return mAnqpOICount;
}
public long[] getRoamingConsortiums() {
return mRoamingConsortiums;
}
public Long getExtendedCapabilities() {
return mExtendedCapabilities.extendedCapabilities;
}
public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
return mANQPElements;
}
public int getChannelWidth() {
return mChannelWidth;
}
public int getCenterfreq0() {
return mCenterfreq0;
}
public int getCenterfreq1() {
return mCenterfreq1;
}
public int getWifiMode() {
return mWifiMode;
}
public int getDtimInterval() {
return mDtimInterval;
}
public boolean is80211McResponderSupport() {
return mExtendedCapabilities.is80211McRTTResponder;
}
public boolean isSSID_UTF8() {
return mExtendedCapabilities.isStrictUtf8();
}
@Override
public boolean equals(Object thatObject) {
if (this == thatObject) {
return true;
}
if (thatObject == null || getClass() != thatObject.getClass()) {
return false;
}
NetworkDetail that = (NetworkDetail)thatObject;
return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
}
@Override
public int hashCode() {
return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
}
@Override
public String toString() {
return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " +
"ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " +
"VenueGroup=%s, VenueType=%s, HSRelease=%s, AnqpDomainID=%d, " +
"AnqpOICount=%d, RoamingConsortiums=%s}",
mSSID, mHESSID, mBSSID, mStationCount,
mChannelUtilization, mCapacity, mAnt, mInternet,
mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
}
public String toKeyString() {
return mHESSID != 0 ?
String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) :
String.format("'%s':%012x", mSSID, mBSSID);
}
public String getBSSIDString() {
return toMACString(mBSSID);
}
public static String toMACString(long mac) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
if (first) {
first = false;
} else {
sb.append(':');
}
sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
}
return sb.toString();
}
}