1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.hotspot2.anqp;
18 
19 import android.util.Log;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 
23 import java.net.ProtocolException;
24 import java.nio.BufferUnderflowException;
25 import java.nio.ByteBuffer;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 
30 /**
31  * The IEI (Information Element Identity) contained in the Generic Container for the
32  * 3GPP Cellular Network ANQP element.
33  *
34  * Refer to Annex A of 3GPP TS 24.234 version 11.3.0 for information on the data format:
35  * (http://www.etsi.org/deliver/etsi_ts/124200_124299/124234/11.03.00_60/ts_124234v110300p.pdf)
36  */
37 public class CellularNetwork {
38     private static final String TAG = "CellularNetwork";
39 
40     /**
41      * IEI type for PLMN (Public Land Mobile Network) list.
42      */
43     @VisibleForTesting
44     public static final int IEI_TYPE_PLMN_LIST = 0;
45 
46     @VisibleForTesting
47     public static final int IEI_CONTENT_LENGTH_MASK = 0x7F;
48 
49     /**
50      * Number of bytes for each PLMN (Public Land Mobile Network).
51      */
52     @VisibleForTesting
53     public static final int PLMN_DATA_BYTES = 3;
54 
55     /**
56      * The value for comparing the third digit of MNC data with to determine if the MNC is
57      * two or three digits.
58      */
59     private static final int MNC_2DIGIT_VALUE = 0xF;
60 
61     /**
62      * List of PLMN (Public Land Mobile Network) information.
63      */
64     private final List<String> mPlmnList;
65 
66     @VisibleForTesting
CellularNetwork(List<String> plmnList)67     public CellularNetwork(List<String> plmnList) {
68         mPlmnList = plmnList;
69     }
70 
71     /**
72      * Parse a CellularNetwork from the given buffer.
73      *
74      * @param payload The byte buffer to read from
75      * @return {@link CellularNetwork}
76      * @throws ProtocolException
77      * @throws BufferUnderflowException
78      */
parse(ByteBuffer payload)79     public static CellularNetwork parse(ByteBuffer payload) throws ProtocolException {
80         int ieiType = payload.get() & 0xFF;
81         int ieiSize = payload.get() & IEI_CONTENT_LENGTH_MASK;
82 
83         // Skip this IEI if it is an unsupported type.
84         if (ieiType != IEI_TYPE_PLMN_LIST) {
85             Log.e(TAG, "Ignore unsupported IEI Type: " + ieiType);
86             // Advance the buffer position to the next IEI.
87             payload.position(payload.position() + ieiSize);
88             return null;
89         }
90 
91         // Get PLMN count.
92         int plmnCount = payload.get() & 0xFF;
93 
94         // Verify IEI size with PLMN count.  The IEI size contained the PLMN count field plus
95         // the bytes for the PLMNs.
96         if (ieiSize != (plmnCount * PLMN_DATA_BYTES + 1)) {
97             throw new ProtocolException("IEI size and PLMN count mismatched: IEI Size=" + ieiSize
98                     + " PLMN Count=" + plmnCount);
99         }
100 
101         // Process each PLMN.
102         List<String> plmnList = new ArrayList<>();
103         while (plmnCount > 0) {
104             plmnList.add(parsePlmn(payload));
105             plmnCount--;
106         }
107         return new CellularNetwork(plmnList);
108     }
109 
getPlmns()110     public List<String> getPlmns() {
111         return Collections.unmodifiableList(mPlmnList);
112     }
113 
114     @Override
equals(Object thatObject)115     public boolean equals(Object thatObject) {
116         if (this == thatObject) {
117             return true;
118         }
119         if (!(thatObject instanceof CellularNetwork)) {
120             return false;
121         }
122         CellularNetwork that = (CellularNetwork) thatObject;
123         return mPlmnList.equals(that.mPlmnList);
124     }
125 
126     @Override
hashCode()127     public int hashCode() {
128         return mPlmnList.hashCode();
129     }
130 
131     @Override
toString()132     public String toString() {
133         return "CellularNetwork{mPlmnList=" + mPlmnList + "}";
134     }
135 
136     /**
137      * Parse the PLMN information from the given buffer.  A string representing a hex value
138      * of |MCC|MNC| will be returned.
139      *
140      * PLMN Coding Format:
141      * b7                         b0
142      * | MCC Digit 2 | MCC Digit 1 |
143      * | MNC Digit 3 | MCC Digit 3 |
144      * | MNC Digit 2 | MNC Digit 1 |
145      *
146      * @param payload The buffer to read from.
147      * @return {@Link String}
148      * @throws BufferUnderflowException
149      */
parsePlmn(ByteBuffer payload)150     private static String parsePlmn(ByteBuffer payload) {
151         byte[] plmn = new byte[PLMN_DATA_BYTES];
152         payload.get(plmn);
153 
154         // Formatted as | MCC Digit 1 | MCC Digit 2 | MCC Digit 3 |
155         int mcc = ((plmn[0] << 8) & 0xF00) | (plmn[0] & 0x0F0) | (plmn[1] & 0x00F);
156 
157         // Formated as |MNC Digit 1 | MNC Digit 2 |
158         int mnc = ((plmn[2] << 4) & 0xF0) | ((plmn[2] >> 4) & 0x0F);
159 
160         // The digit 3 of MNC decides if the MNC is 2 or 3 digits number.  When it is equal to
161         // 0xF, MNC is a 2 digit value. Otherwise, it is a 3 digit number.
162         int mncDigit3 = (plmn[1] >> 4) & 0x0F;
163         return (mncDigit3 != MNC_2DIGIT_VALUE)
164                 ? String.format("%03x%03x", mcc, (mnc << 4) | mncDigit3)
165                 : String.format("%03x%02x", mcc, mnc);
166     }
167 }
168