diff options
Diffstat (limited to 'service')
3 files changed, 222 insertions, 63 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java index 8ef75a600..bc724ceaf 100644 --- a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java +++ b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPFactory.java @@ -96,7 +96,7 @@ public class ANQPFactory { case ANQPNAIRealm: return new NAIRealmElement(infoID, payload); case ANQP3GPPNetwork: - return new ThreeGPPNetworkElement(infoID, payload); + return ThreeGPPNetworkElement.parse(payload); case ANQPDomName: return DomainNameElement.parse(payload); case ANQPVendorSpec: diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java b/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java index f8573e6ac..cc39b3f2b 100644 --- a/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java +++ b/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java @@ -1,70 +1,167 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.server.wifi.hotspot2.anqp; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + import java.net.ProtocolException; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Collections; import java.util.List; -import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK; +/** + * The IEI (Information Element Identity) contained in the Generic Container for the + * 3GPP Cellular Network ANQP element. + * + * Refer to Annex A of 3GPP TS 24.234 version 11.3.0 for information on the data format: + * (http://www.etsi.org/deliver/etsi_ts/124200_124299/124234/11.03.00_60/ts_124234v110300p.pdf) + */ +public class CellularNetwork { + private static final String TAG = "CellularNetwork"; -public class CellularNetwork implements Iterable<String> { - private static final int PLMNListType = 0; + /** + * IEI type for PLMN (Public Land Mobile Network) list. + */ + @VisibleForTesting + public static final int IEI_TYPE_PLMN_LIST = 0; - private final List<String> mMccMnc; + @VisibleForTesting + public static final int IEI_CONTENT_LENGTH_MASK = 0x7F; - private CellularNetwork(int plmnCount, ByteBuffer payload) throws ProtocolException { - mMccMnc = new ArrayList<>(plmnCount); + /** + * Number of bytes for each PLMN (Public Land Mobile Network). + */ + @VisibleForTesting + public static final int PLMN_DATA_BYTES = 3; - while (plmnCount > 0) { - if (payload.remaining() < 3) { - throw new ProtocolException("Truncated PLMN info"); - } - byte[] plmn = new byte[3]; - payload.get(plmn); + /** + * The value for comparing the third digit of MNC data with to determine if the MNC is + * two or three digits. + */ + private static final int MNC_2DIGIT_VALUE = 0xF; + + /** + * List of PLMN (Public Land Mobile Network) information. + */ + private final List<String> mPlmnList; + + @VisibleForTesting + public CellularNetwork(List<String> plmnList) { + mPlmnList = plmnList; + } + + /** + * Parse a CellularNetwork from the given buffer. + * + * @param payload The byte buffer to read from + * @return {@link CellularNetwork} + * @throws ProtocolException + * @throws BufferUnderflowException + */ + public static CellularNetwork parse(ByteBuffer payload) throws ProtocolException { + int ieiType = payload.get() & 0xFF; + int ieiSize = payload.get() & IEI_CONTENT_LENGTH_MASK; - int mcc = ((plmn[0] << 8) & 0xf00) | - (plmn[0] & 0x0f0) | - (plmn[1] & 0x00f); + // Skip this IEI if it is an unsupported type. + if (ieiType != IEI_TYPE_PLMN_LIST) { + Log.e(TAG, "Ignore unsupported IEI Type: " + ieiType); + // Advance the buffer position to the next IEI. + payload.position(payload.position() + ieiSize); + return null; + } - int mnc = ((plmn[2] << 4) & 0xf0) | - ((plmn[2] >> 4) & 0x0f); + // Get PLMN count. + int plmnCount = payload.get() & 0xFF; - int n2 = (plmn[1] >> 4) & 0x0f; - String mccMnc = n2 != 0xf ? - String.format("%03x%03x", mcc, (mnc << 4) | n2) : - String.format("%03x%02x", mcc, mnc); + // Verify IEI size with PLMN count. The IEI size contained the PLMN count field plus + // the bytes for the PLMNs. + if (ieiSize != (plmnCount * PLMN_DATA_BYTES + 1)) { + throw new ProtocolException("IEI size and PLMN count mismatched: IEI Size=" + ieiSize + + " PLMN Count=" + plmnCount); + } - mMccMnc.add(mccMnc); + // Process each PLMN. + List<String> plmnList = new ArrayList<>(); + while (plmnCount > 0) { + plmnList.add(parsePlmn(payload)); plmnCount--; } + return new CellularNetwork(plmnList); } - public static CellularNetwork buildCellularNetwork(ByteBuffer payload) - throws ProtocolException { - int iei = payload.get() & BYTE_MASK; - int plmnLen = payload.get() & 0x7f; + public List<String> getPlmns() { + return Collections.unmodifiableList(mPlmnList); + } - if (iei != PLMNListType) { - payload.position(payload.position() + plmnLen); - return null; + @Override + public boolean equals(Object thatObject) { + if (this == thatObject) { + return true; } - - int plmnCount = payload.get() & BYTE_MASK; - return new CellularNetwork(plmnCount, payload); + if (!(thatObject instanceof CellularNetwork)) { + return false; + } + CellularNetwork that = (CellularNetwork) thatObject; + return mPlmnList.equals(that.mPlmnList); } @Override - public Iterator<String> iterator() { - return mMccMnc.iterator(); + public int hashCode() { + return mPlmnList.hashCode(); } @Override public String toString() { - StringBuilder sb = new StringBuilder("PLMN:"); - for (String mccMnc : mMccMnc) { - sb.append(' ').append(mccMnc); - } - return sb.toString(); + return "CellularNetwork{mPlmnList=" + mPlmnList + "}"; + } + + /** + * Parse the PLMN information from the given buffer. A string representing a hex value + * of |MCC|MNC| will be returned. + * + * PLMN Coding Format: + * b7 b0 + * | MCC Digit 2 | MCC Digit 1 | + * | MNC Digit 3 | MCC Digit 3 | + * | MNC Digit 2 | MNC Digit 1 | + * + * @param payload The buffer to read from. + * @return {@Link String} + * @throws BufferUnderflowException + */ + private static String parsePlmn(ByteBuffer payload) { + byte[] plmn = new byte[PLMN_DATA_BYTES]; + payload.get(plmn); + + // Formatted as | MCC Digit 1 | MCC Digit 2 | MCC Digit 3 | + int mcc = ((plmn[0] << 8) & 0xF00) | (plmn[0] & 0x0F0) | (plmn[1] & 0x00F); + + // Formated as |MNC Digit 1 | MNC Digit 2 | + int mnc = ((plmn[2] << 4) & 0xF0) | ((plmn[2] >> 4) & 0x0F); + + // The digit 3 of MNC decides if the MNC is 2 or 3 digits number. When it is equal to + // 0xF, MNC is a 2 digit value. Otherwise, it is a 3 digit number. + int mncDigit3 = (plmn[1] >> 4) & 0x0F; + return (mncDigit3 != MNC_2DIGIT_VALUE) + ? String.format("%03x%03x", mcc, (mnc << 4) | mncDigit3) + : String.format("%03x%02x", mcc, mnc); } } diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java index 8436ae3ee..d9795c63d 100644 --- a/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java +++ b/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java @@ -1,53 +1,115 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.server.wifi.hotspot2.anqp; +import com.android.internal.annotations.VisibleForTesting; + import java.net.ProtocolException; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK; - /** - * The 3GPP Cellular Network ANQP Element, IEEE802.11-2012 section 8.4.4.11 + * The 3GPP Cellular Network ANQP Element, IEEE802.11-2012 section 8.4.4.11. + * The value is embedded in a Generic container User Data (GUD). + * Refer to Annex A of 3GPP TS 24.234 version 11.3.0 for more info: + * (http://www.etsi.org/deliver/etsi_ts/124200_124299/124234/11.03.00_60/ts_124234v110300p.pdf). + * + * Format: + * | GUD Version | Length | IEI 1 | ... | IEI N| + * 1 1 variable + * */ public class ThreeGPPNetworkElement extends ANQPElement { - private final int mUserData; - private final List<CellularNetwork> mPlmns; + /** + * The expected protocol version number of the Generic container User Data (GUD). + */ + @VisibleForTesting + public static final int GUD_VERSION_1 = 0; - public ThreeGPPNetworkElement(Constants.ANQPElementType infoID, ByteBuffer payload) + private final List<CellularNetwork> mNetworks; + + @VisibleForTesting + public ThreeGPPNetworkElement(List<CellularNetwork> networks) { + super(Constants.ANQPElementType.ANQP3GPPNetwork); + mNetworks = networks; + } + + /** + * Parse a ThreeGPPNetworkElement from the given buffer. + * + * @param payload The byte buffer to read from + * @return {@link ThreeGPPNetworkElement} + * @throws BufferUnderflowException + * @throws ProtocolException + */ + public static ThreeGPPNetworkElement parse(ByteBuffer payload) throws ProtocolException { - super(infoID); + // Verify version. + int gudVersion = payload.get() & 0xFF; + if (gudVersion != GUD_VERSION_1) { + throw new ProtocolException("Unsupported GUD version: " + gudVersion); + } - mPlmns = new ArrayList<CellularNetwork>(); - mUserData = payload.get() & BYTE_MASK; - int length = payload.get() & BYTE_MASK; - if (length > payload.remaining()) { - throw new ProtocolException("Runt payload"); + // Verify length. + int length = payload.get() & 0xFF; + if (length != payload.remaining()) { + throw new ProtocolException("Mismatch length and buffer size: length=" + length + + " bufferSize=" + payload.remaining()); } + // Parse each IEI (Information Element Identity) content. + List<CellularNetwork> networks = new ArrayList<>(); while (payload.hasRemaining()) { - CellularNetwork network = CellularNetwork.buildCellularNetwork(payload); + CellularNetwork network = CellularNetwork.parse(payload); if (network != null) { - mPlmns.add(network); + networks.add(network); } } + return new ThreeGPPNetworkElement(networks); } - public int getUserData() { - return mUserData; + public List<CellularNetwork> getNetworks() { + return Collections.unmodifiableList(mNetworks); } - public List<CellularNetwork> getPlmns() { - return Collections.unmodifiableList(mPlmns); + @Override + public boolean equals(Object thatObject) { + if (this == thatObject) { + return true; + } + if (!(thatObject instanceof ThreeGPPNetworkElement)) { + return false; + } + ThreeGPPNetworkElement that = (ThreeGPPNetworkElement) thatObject; + return mNetworks.equals(that.mNetworks); + + } + + @Override + public int hashCode() { + return mNetworks.hashCode(); } @Override public String toString() { - return "ThreeGPPNetwork{" + - "mUserData=" + mUserData + - ", mPlmns=" + mPlmns + - '}'; + return "ThreeGPPNetwork{mNetworks=" + mNetworks + "}"; } } |