/* * Copyright (C) 2018 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 android.telephony.euicc; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.service.carrier.CarrierIdentifier; import android.service.euicc.EuiccProfileInfo; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.List; /** * This represents the RAT (Rules Authorisation Table) stored on eUICC. * @hide */ @SystemApi public final class EuiccRulesAuthTable implements Parcelable { /** * Profile policy rule flags * * @removed mistakenly exposed previously */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = { POLICY_RULE_FLAG_CONSENT_REQUIRED }) public @interface PolicyRuleFlag {} /** User consent is required to install the profile. */ public static final int POLICY_RULE_FLAG_CONSENT_REQUIRED = 1; private final int[] mPolicyRules; private final CarrierIdentifier[][] mCarrierIds; private final int[] mPolicyRuleFlags; /** This is used to build new {@link EuiccRulesAuthTable} instance. */ public static final class Builder { private int[] mPolicyRules; private CarrierIdentifier[][] mCarrierIds; private int[] mPolicyRuleFlags; private int mPosition; /** * Creates a new builder. * * @param ruleNum The number of authorisation rules in the table. */ public Builder(int ruleNum) { mPolicyRules = new int[ruleNum]; mCarrierIds = new CarrierIdentifier[ruleNum][]; mPolicyRuleFlags = new int[ruleNum]; } /** * Builds the RAT instance. This builder should not be used anymore after this method is * called, otherwise {@link NullPointerException} will be thrown. */ public EuiccRulesAuthTable build() { if (mPosition != mPolicyRules.length) { throw new IllegalStateException( "Not enough rules are added, expected: " + mPolicyRules.length + ", added: " + mPosition); } return new EuiccRulesAuthTable(mPolicyRules, mCarrierIds, mPolicyRuleFlags); } /** * Adds an authorisation rule. * * @throws ArrayIndexOutOfBoundsException If the {@code mPosition} is larger than the size * this table. */ public Builder add(int policyRules, List carrierId, int policyRuleFlags) { if (mPosition >= mPolicyRules.length) { throw new ArrayIndexOutOfBoundsException(mPosition); } mPolicyRules[mPosition] = policyRules; if (carrierId != null && carrierId.size() > 0) { mCarrierIds[mPosition] = carrierId.toArray(new CarrierIdentifier[carrierId.size()]); } mPolicyRuleFlags[mPosition] = policyRuleFlags; mPosition++; return this; } } /** * @param mccRule A 2-character or 3-character string which can be either MCC or MNC. The * character 'E' is used as a wild char to match any digit. * @param mcc A 2-character or 3-character string which can be either MCC or MNC. * @return Whether the {@code mccRule} matches {@code mcc}. * * @hide */ @VisibleForTesting public static boolean match(String mccRule, String mcc) { if (mccRule.length() < mcc.length()) { return false; } for (int i = 0; i < mccRule.length(); i++) { // 'E' is the wild char to match any digit. if (mccRule.charAt(i) == 'E' || (i < mcc.length() && mccRule.charAt(i) == mcc.charAt(i))) { continue; } return false; } return true; } private EuiccRulesAuthTable(int[] policyRules, CarrierIdentifier[][] carrierIds, int[] policyRuleFlags) { mPolicyRules = policyRules; mCarrierIds = carrierIds; mPolicyRuleFlags = policyRuleFlags; } /** * Finds the index of the first authorisation rule matching the given policy and carrier id. If * the returned index is not negative, the carrier is allowed to apply this policy to its * profile. * * @param policy The policy rule. * @param carrierId The carrier id. * @return The index of authorization rule. If no rule is found, -1 will be returned. */ public int findIndex(@EuiccProfileInfo.PolicyRule int policy, CarrierIdentifier carrierId) { for (int i = 0; i < mPolicyRules.length; i++) { if ((mPolicyRules[i] & policy) == 0) { continue; } CarrierIdentifier[] carrierIds = mCarrierIds[i]; if (carrierIds == null || carrierIds.length == 0) { continue; } for (int j = 0; j < carrierIds.length; j++) { CarrierIdentifier ruleCarrierId = carrierIds[j]; if (!match(ruleCarrierId.getMcc(), carrierId.getMcc()) || !match(ruleCarrierId.getMnc(), carrierId.getMnc())) { continue; } String gid = ruleCarrierId.getGid1(); if (!TextUtils.isEmpty(gid) && !gid.equals(carrierId.getGid1())) { continue; } gid = ruleCarrierId.getGid2(); if (!TextUtils.isEmpty(gid) && !gid.equals(carrierId.getGid2())) { continue; } return i; } } return -1; } /** * Tests if the entry in the table has the given policy rule flag. * * @param index The index of the entry. * @param flag The policy rule flag to be tested. * @throws ArrayIndexOutOfBoundsException If the {@code index} is negative or larger than the * size of this table. */ public boolean hasPolicyRuleFlag(int index, @PolicyRuleFlag int flag) { if (index < 0 || index >= mPolicyRules.length) { throw new ArrayIndexOutOfBoundsException(index); } return (mPolicyRuleFlags[index] & flag) != 0; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeIntArray(mPolicyRules); for (CarrierIdentifier[] ids : mCarrierIds) { dest.writeTypedArray(ids, flags); } dest.writeIntArray(mPolicyRuleFlags); } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } EuiccRulesAuthTable that = (EuiccRulesAuthTable) obj; if (mCarrierIds.length != that.mCarrierIds.length) { return false; } for (int i = 0; i < mCarrierIds.length; i++) { CarrierIdentifier[] carrierIds = mCarrierIds[i]; CarrierIdentifier[] thatCarrierIds = that.mCarrierIds[i]; if (carrierIds != null && thatCarrierIds != null) { if (carrierIds.length != thatCarrierIds.length) { return false; } for (int j = 0; j < carrierIds.length; j++) { if (!carrierIds[j].equals(thatCarrierIds[j])) { return false; } } continue; } else if (carrierIds == null && thatCarrierIds == null) { continue; } return false; } return Arrays.equals(mPolicyRules, that.mPolicyRules) && Arrays.equals(mPolicyRuleFlags, that.mPolicyRuleFlags); } private EuiccRulesAuthTable(Parcel source) { mPolicyRules = source.createIntArray(); int len = mPolicyRules.length; mCarrierIds = new CarrierIdentifier[len][]; for (int i = 0; i < len; i++) { mCarrierIds[i] = source.createTypedArray(CarrierIdentifier.CREATOR); } mPolicyRuleFlags = source.createIntArray(); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public EuiccRulesAuthTable createFromParcel(Parcel source) { return new EuiccRulesAuthTable(source); } @Override public EuiccRulesAuthTable[] newArray(int size) { return new EuiccRulesAuthTable[size]; } }; }