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;
18 
19 import android.text.TextUtils;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 
23 /**
24  * Class for storing an IMSI (International Mobile Subscriber Identity) parameter.  The IMSI
25  * contains number (up to 15) of numerical digits.  When an IMSI ends with a '*', the specified
26  * IMSI is a prefix.
27  */
28 public class IMSIParameter {
29     /**
30      * Per 2.2 of 3GPP TS 23.003
31      * MCC (Mobile Country Code) is a 3 digit number and MNC (Mobile Network Code) is a 2
32      * or 3 digit number;
33      * The max length of IMSI is 15;
34      */
35     public static final int MCC_MNC_LENGTH_5 = 5;
36     public static final int MCC_MNC_LENGTH_6 = 6;
37     private static final int MAX_IMSI_LENGTH = 15;
38 
39     private final String mImsi;
40     private final boolean mPrefix;
41 
42     @VisibleForTesting
IMSIParameter(String imsi, boolean prefix)43     public IMSIParameter(String imsi, boolean prefix) {
44         mImsi = imsi;
45         mPrefix = prefix;
46     }
47 
48     /**
49      * Build an IMSIParameter object from the given string.  A null will be returned for a
50      * malformed string.
51      *
52      * @param imsi The IMSI string
53      * @return {@link IMSIParameter}
54      */
build(String imsi)55     public static IMSIParameter build(String imsi) {
56         if (TextUtils.isEmpty(imsi)) {
57             return null;
58         }
59         if (imsi.length() > MAX_IMSI_LENGTH) {
60             return null;
61         }
62 
63         // Detect the first non-digit character.
64         int nonDigitIndex;
65         char stopChar = '\0';
66         for (nonDigitIndex = 0; nonDigitIndex < imsi.length(); nonDigitIndex++) {
67             stopChar = imsi.charAt(nonDigitIndex);
68             if (stopChar < '0' || stopChar > '9') {
69                 break;
70             }
71         }
72 
73         if (nonDigitIndex == imsi.length()) {
74             // Full IMSI.
75             return new IMSIParameter(imsi, false);
76         } else if (nonDigitIndex == imsi.length() - 1 && stopChar == '*'
77                 && (nonDigitIndex == MCC_MNC_LENGTH_5 || nonDigitIndex == MCC_MNC_LENGTH_6)) {
78             // IMSI prefix.
79             return new IMSIParameter(imsi.substring(0, nonDigitIndex), true);
80         }
81         return null;
82     }
83 
84     /**
85      * Perform matching against the given full IMSI.
86      *
87      * @param fullIMSI The full IMSI to match against
88      * @return true if matched
89      */
matchesImsi(String fullIMSI)90     public boolean matchesImsi(String fullIMSI) {
91         if (fullIMSI == null) {
92             return false;
93         }
94 
95         if (mPrefix) {
96             // Prefix matching.
97             return mImsi.regionMatches(false, 0, fullIMSI, 0, mImsi.length());
98         } else {
99             // Exact matching.
100             return mImsi.equals(fullIMSI);
101         }
102     }
103 
104     /**
105      * Perform matching against the given MCC-MNC (Mobile Country Code and Mobile Network
106      * Code) combination.
107      *
108      * @param mccMnc The MCC-MNC to match against
109      * @return true if matched
110      */
matchesMccMnc(String mccMnc)111     public boolean matchesMccMnc(String mccMnc) {
112         if (mccMnc == null) {
113             return false;
114         }
115         if (mccMnc.length() != MCC_MNC_LENGTH_5 && mccMnc.length() != MCC_MNC_LENGTH_6) {
116             return false;
117         }
118         if (mPrefix && mccMnc.length() != mImsi.length()) {
119             return false;
120         }
121 
122         return mImsi.startsWith(mccMnc);
123     }
124 
125     /**
126      * If the IMSI is full length.
127      *
128      * @return true If the length of IMSI is full, false otherwise.
129      */
isFullImsi()130     public boolean isFullImsi() {
131         return !mPrefix;
132     }
133 
134     @Override
equals(Object thatObject)135     public boolean equals(Object thatObject) {
136         if (this == thatObject) {
137             return true;
138         }
139         if (!(thatObject instanceof IMSIParameter)) {
140             return false;
141         }
142 
143         IMSIParameter that = (IMSIParameter) thatObject;
144         return mPrefix == that.mPrefix && TextUtils.equals(mImsi, that.mImsi);
145     }
146 
147     @Override
hashCode()148     public int hashCode() {
149         int result = mImsi != null ? mImsi.hashCode() : 0;
150         result = 31 * result + (mPrefix ? 1 : 0);
151         return result;
152     }
153 
154     @Override
toString()155     public String toString() {
156         if (mPrefix) {
157             return mImsi + '*';
158         }
159         else {
160             return mImsi;
161         }
162     }
163 }
164