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
17package com.android.server.wifi.hotspot2.anqp;
18
19import android.util.Log;
20
21import com.android.internal.annotations.VisibleForTesting;
22
23import java.net.ProtocolException;
24import java.nio.BufferUnderflowException;
25import java.nio.ByteBuffer;
26import java.util.ArrayList;
27import java.util.Collections;
28import 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 */
37public 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
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     */
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
110    public List<String> getPlmns() {
111        return Collections.unmodifiableList(mPlmnList);
112    }
113
114    @Override
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
127    public int hashCode() {
128        return mPlmnList.hashCode();
129    }
130
131    @Override
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     */
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