blob: cc39b3f2b7d0adbf8ce07429f0b6c6a4747fc7f8 [file] [log] [blame]
/*
* 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.Collections;
import java.util.List;
/**
* 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";
/**
* IEI type for PLMN (Public Land Mobile Network) list.
*/
@VisibleForTesting
public static final int IEI_TYPE_PLMN_LIST = 0;
@VisibleForTesting
public static final int IEI_CONTENT_LENGTH_MASK = 0x7F;
/**
* Number of bytes for each PLMN (Public Land Mobile Network).
*/
@VisibleForTesting
public static final int PLMN_DATA_BYTES = 3;
/**
* 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;
// 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;
}
// Get PLMN count.
int plmnCount = payload.get() & 0xFF;
// 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);
}
// Process each PLMN.
List<String> plmnList = new ArrayList<>();
while (plmnCount > 0) {
plmnList.add(parsePlmn(payload));
plmnCount--;
}
return new CellularNetwork(plmnList);
}
public List<String> getPlmns() {
return Collections.unmodifiableList(mPlmnList);
}
@Override
public boolean equals(Object thatObject) {
if (this == thatObject) {
return true;
}
if (!(thatObject instanceof CellularNetwork)) {
return false;
}
CellularNetwork that = (CellularNetwork) thatObject;
return mPlmnList.equals(that.mPlmnList);
}
@Override
public int hashCode() {
return mPlmnList.hashCode();
}
@Override
public String 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);
}
}