• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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.google.android.iwlan;
18 
19 import android.content.Context;
20 import android.net.ipsec.ike.exceptions.IkeProtocolException;
21 import android.os.Handler;
22 import android.os.HandlerThread;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.support.annotation.IntDef;
26 import android.support.annotation.NonNull;
27 import android.support.annotation.Nullable;
28 import android.telephony.DataFailCause;
29 import android.telephony.TelephonyManager;
30 import android.telephony.data.DataCallResponse;
31 import android.telephony.data.DataService;
32 import android.text.TextUtils;
33 import android.util.Log;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import com.google.auto.value.AutoValue;
38 
39 import org.json.JSONArray;
40 import org.json.JSONException;
41 import org.json.JSONObject;
42 
43 import java.io.PrintWriter;
44 import java.time.Duration;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Calendar;
48 import java.util.Date;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Objects;
54 import java.util.Optional;
55 import java.util.Set;
56 import java.util.concurrent.ConcurrentHashMap;
57 import java.util.concurrent.TimeUnit;
58 
59 public class ErrorPolicyManager {
60 
61     /**
62      * This type is not to be used in config. This is only used internally to catch errors in
63      * parsing the error type.
64      */
65     private static final int UNKNOWN_ERROR_TYPE = -1;
66 
67     /**
68      * This value represents that the error tye is to be used as a fallback to represent all the
69      * errors.
70      */
71     private static final int FALLBACK_ERROR_TYPE = 1;
72 
73     /**
74      * This value represents rest of the errors that are not defined above. ErrorDetails should
75      * mention the specific error. If it doesn't - the policy will be used as a fallback global
76      * policy. Currently, Supported ErrorDetails "IO_EXCEPTION" "TIMEOUT_EXCEPTION"
77      * "SERVER_SELECTION_FAILED" "TUNNEL_TRANSFORM_FAILED"
78      */
79     private static final int GENERIC_ERROR_TYPE = 2;
80 
81     /**
82      * This value represents IKE Protocol Error/Notify Error.
83      *
84      * @see <a href="https://tools.ietf.org/html/rfc4306#section-3.10.1">RFC 4306,Internet Key
85      *     Exchange (IKEv2) Protocol </a> for global errors and carrier specific requirements for
86      *     other carrier specific error codes. ErrorDetails defined for this type is always in
87      *     numeric form representing the error codes. Examples: "24", "9000-9050"
88      */
89     private static final int IKE_PROTOCOL_ERROR_TYPE = 3;
90 
builder()91     static ErrorPolicy.Builder builder() {
92         return new AutoValue_ErrorPolicyManager_ErrorPolicy.Builder()
93                 .setInfiniteRetriesWithLastRetryTime(false);
94     }
95 
96     @IntDef({UNKNOWN_ERROR_TYPE, FALLBACK_ERROR_TYPE, GENERIC_ERROR_TYPE, IKE_PROTOCOL_ERROR_TYPE})
97     @interface ErrorPolicyErrorType {}
98 
99     private static final String[] GENERIC_ERROR_DETAIL_STRINGS = {
100         "*",
101         "IO_EXCEPTION",
102         "TIMEOUT_EXCEPTION",
103         "SERVER_SELECTION_FAILED",
104         "TUNNEL_TRANSFORM_FAILED"
105     };
106 
107     /** Private IKEv2 notify message types. As defined in TS 124 302 (section 8.1.2.2) */
108     private static final int IKE_PROTOCOL_ERROR_PDN_CONNECTION_REJECTION = 8192;
109 
110     private static final int IKE_PROTOCOL_ERROR_MAX_CONNECTION_REACHED = 8193;
111     private static final int IKE_PROTOCOL_ERROR_SEMANTIC_ERROR_IN_THE_TFT_OPERATION = 8241;
112     private static final int IKE_PROTOCOL_ERROR_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION = 8242;
113     private static final int IKE_PROTOCOL_ERROR_SEMANTIC_ERRORS_IN_PACKET_FILTERS = 8244;
114     private static final int IKE_PROTOCOL_ERROR_SYNTACTICAL_ERRORS_IN_PACKET_FILTERS = 8245;
115     private static final int IKE_PROTOCOL_ERROR_NON_3GPP_ACCESS_TO_EPC_NOT_ALLOWED = 9000;
116     private static final int IKE_PROTOCOL_ERROR_USER_UNKNOWN = 9001;
117     private static final int IKE_PROTOCOL_ERROR_NO_APN_SUBSCRIPTION = 9002;
118     private static final int IKE_PROTOCOL_ERROR_AUTHORIZATION_REJECTED = 9003;
119     private static final int IKE_PROTOCOL_ERROR_ILLEGAL_ME = 9006;
120     private static final int IKE_PROTOCOL_ERROR_NETWORK_FAILURE = 10500;
121     private static final int IKE_PROTOCOL_ERROR_RAT_TYPE_NOT_ALLOWED = 11001;
122     private static final int IKE_PROTOCOL_ERROR_IMEI_NOT_ACCEPTED = 11005;
123     private static final int IKE_PROTOCOL_ERROR_PLMN_NOT_ALLOWED = 11011;
124     private static final int IKE_PROTOCOL_ERROR_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED = 11055;
125 
126     /**
127      * Represents the retry backoff duration is unspecified, see {@link
128      * android.telephony.data.DataCallResponse#RETRY_DURATION_UNDEFINED}
129      */
130     public static final Duration UNSPECIFIED_RETRY_DURATION =
131             Duration.ofMillis(DataCallResponse.RETRY_DURATION_UNDEFINED);
132 
133     /** Private IKEv2 notify message types, as defined in TS 124 502 (section 9.2.4.1) */
134     private static final int IKE_PROTOCOL_ERROR_CONGESTION = 15500;
135 
136     private static final ErrorPolicy FALLBACK_ERROR_POLICY =
137             builder()
138                     .setErrorType(FALLBACK_ERROR_TYPE)
139                     .setRetryArray(List.of(5, -1))
140                     .setErrorDetails(List.of("*"))
141                     .setUnthrottlingEvents(List.of())
142                     .build();
143 
144     private final String LOG_TAG;
145 
146     private static final Map<Integer, ErrorPolicyManager> mInstances = new ConcurrentHashMap<>();
147     private final Context mContext;
148     private final int mSlotId;
149 
150     // Policies read from defaultiwlanerrorconfig.json
151     // String APN as key to identify the ErrorPolicies associated with it.
152     private final Map<String, List<ErrorPolicy>> mDefaultPolicies = new HashMap<>();
153 
154     // Policies read from CarrierConfig
155     // String APN as key to identify the ErrorPolicies associated with it.
156     private final Map<String, List<ErrorPolicy>> mCarrierConfigPolicies = new HashMap<>();
157 
158     /** String APN as key to identify the {@link ApnRetryActionStore} associated with that APN */
159     private final Map<String, ApnRetryActionStore> mRetryActionStoreByApn =
160             new ConcurrentHashMap<>();
161 
162     // Records the most recently reported IwlanError (including NO_ERROR), and the corresponding
163     // APN.
164     private ApnWithIwlanError mMostRecentError;
165 
166     // List of current Unthrottling events registered with IwlanEventListener
167     private Set<Integer> mUnthrottlingEvents;
168 
169     private final ErrorStats mErrorStats = new ErrorStats();
170 
171     private HandlerThread mHandlerThread;
172     @VisibleForTesting Handler mHandler;
173 
174     private int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
175 
176     private String mCarrierConfigErrorPolicyString;
177 
178     /**
179      * Returns ErrorPolicyManager instance for the subId
180      *
181      * @param context the context to be used by the ErrorPolicyManager
182      * @param slotId the slot ID for which the ErrorPolicyManager instance is required
183      */
getInstance(@onNull Context context, int slotId)184     public static ErrorPolicyManager getInstance(@NonNull Context context, int slotId) {
185         return mInstances.computeIfAbsent(slotId, k -> new ErrorPolicyManager(context, slotId));
186     }
187 
188     @VisibleForTesting
resetAllInstances()189     public static void resetAllInstances() {
190         mInstances.clear();
191     }
192 
193     /** Release or reset the instance. */
releaseInstance()194     public void releaseInstance() {
195         Log.d(LOG_TAG, "Release Instance with slotId: " + mSlotId);
196         IwlanEventListener.getInstance(mContext, mSlotId).removeEventListener(mHandler);
197         mHandlerThread.quit();
198         mInstances.remove(mSlotId);
199     }
200 
201     /**
202      * Updates the last error details and returns the retry time. Return value is -1, which should
203      * be ignored, when the error is IwlanError.NO_ERROR.
204      *
205      * @param apn apn name for which the error happened
206      * @param iwlanError Error
207      * @return retry time. 0 = immediate retry, -1 = fail and n = retry after n seconds
208      */
reportIwlanError(String apn, IwlanError iwlanError)209     public synchronized long reportIwlanError(String apn, IwlanError iwlanError) {
210         // Fail by default
211         mMostRecentError = new ApnWithIwlanError(apn, iwlanError);
212 
213         if (iwlanError.getErrorType() == IwlanError.NO_ERROR) {
214             Log.d(LOG_TAG, "reportIwlanError: NO_ERROR");
215             mRetryActionStoreByApn.remove(apn);
216             return DataCallResponse.RETRY_DURATION_UNDEFINED;
217         }
218         mErrorStats.update(apn, iwlanError);
219 
220         PolicyDerivedRetryAction newRetryAction =
221                 mRetryActionStoreByApn
222                         .computeIfAbsent(apn, ApnRetryActionStore::new)
223                         .generateRetryAction(iwlanError);
224 
225         Log.d(
226                 LOG_TAG,
227                 "Current RetryAction index: "
228                         + newRetryAction.currentRetryIndex()
229                         + " and time: "
230                         + newRetryAction.totalBackoffDuration());
231         return newRetryAction.totalBackoffDuration().toSeconds();
232     }
233 
234     /**
235      * Updates the last error details with backoff time.
236      *
237      * @param apn apn name for which the error happened
238      * @param iwlanError Error
239      * @param backoffTime in seconds
240      * @return retry time which is the backoff time. -1 if it is {@link IwlanError#NO_ERROR}
241      */
reportIwlanError(String apn, IwlanError iwlanError, long backoffTime)242     public synchronized long reportIwlanError(String apn, IwlanError iwlanError, long backoffTime) {
243         // Fail by default
244         if (iwlanError.getErrorType() == IwlanError.NO_ERROR) {
245             Log.d(LOG_TAG, "reportIwlanError: NO_ERROR");
246             mRetryActionStoreByApn.remove(apn);
247             return DataCallResponse.RETRY_DURATION_UNDEFINED;
248         }
249         mErrorStats.update(apn, iwlanError);
250 
251         IkeBackoffNotifyRetryAction newRetryAction =
252                 mRetryActionStoreByApn
253                         .computeIfAbsent(apn, ApnRetryActionStore::new)
254                         .generateRetryAction(iwlanError, Duration.ofSeconds(backoffTime));
255         Log.d(LOG_TAG, "Current configured backoff time: " + newRetryAction.totalBackoffDuration);
256 
257         return newRetryAction.totalBackoffDuration.toSeconds();
258     }
259 
260     /**
261      * Checks whether we can bring up Epdg Tunnel - Based on lastErrorForApn
262      *
263      * @param apn apn for which tunnel bring up needs to be checked
264      * @return true if tunnel can be brought up, false otherwise
265      */
canBringUpTunnel(String apn)266     public synchronized boolean canBringUpTunnel(String apn) {
267         RetryAction lastRetryAction = getLastRetryAction(apn);
268         boolean canBringUp =
269                 lastRetryAction == null
270                         || getRemainingBackoffDuration(lastRetryAction).isNegative()
271                         || getRemainingBackoffDuration(lastRetryAction).isZero();
272         Log.d(LOG_TAG, "canBringUpTunnel: " + canBringUp);
273         return canBringUp;
274     }
275 
276     // TODO: Modify framework/base/Android.bp to get access to Annotation.java to use
277     // @DataFailureCause
278     // annotation as return type here. (after moving to aosp?)
279     /**
280      * Returns the DataFailCause based on the lastErrorForApn
281      *
282      * @param apn apn name for which DataFailCause is needed
283      * @return DataFailCause corresponding to the error for the apn
284      */
getDataFailCause(String apn)285     public synchronized int getDataFailCause(String apn) {
286         RetryAction lastRetryAction = getLastRetryAction(apn);
287         return lastRetryAction == null
288                 ? DataFailCause.NONE
289                 : getDataFailCause(lastRetryAction.error());
290     }
291 
getDataFailCause(IwlanError error)292     private int getDataFailCause(IwlanError error) {
293         int errorType = error.getErrorType();
294         return switch (errorType) {
295             case IwlanError.NO_ERROR -> DataFailCause.NONE;
296             case IwlanError.IKE_PROTOCOL_EXCEPTION ->
297                     getDataFailCauseForIkeProtocolException(error.getException());
298             case IwlanError.IKE_INTERNAL_IO_EXCEPTION -> DataFailCause.IWLAN_IKEV2_MSG_TIMEOUT;
299             case IwlanError.IKE_GENERIC_EXCEPTION -> DataFailCause.ERROR_UNSPECIFIED;
300             case IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED ->
301                     DataFailCause.IWLAN_DNS_RESOLUTION_NAME_FAILURE;
302             case IwlanError.TUNNEL_TRANSFORM_FAILED -> DataFailCause.IWLAN_TUNNEL_TRANSFORM_FAILED;
303             case IwlanError.SIM_NOT_READY_EXCEPTION -> DataFailCause.SIM_CARD_CHANGED;
304             case IwlanError.IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED ->
305                     DataFailCause.IWLAN_IKE_SESSION_CLOSED_BEFORE_CHILD_SESSION_OPENED;
306             case IwlanError.IKE_NETWORK_LOST_EXCEPTION ->
307                     DataFailCause.IWLAN_IKE_NETWORK_LOST_EXCEPTION;
308             case IwlanError.TUNNEL_NOT_FOUND -> DataFailCause.IWLAN_TUNNEL_NOT_FOUND;
309             case IwlanError.EPDG_ADDRESS_ONLY_IPV4_ALLOWED -> DataFailCause.ONLY_IPV4_ALLOWED;
310             case IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED -> DataFailCause.ONLY_IPV6_ALLOWED;
311             case IwlanError.IKE_INIT_TIMEOUT -> DataFailCause.IWLAN_IKE_INIT_TIMEOUT;
312             case IwlanError.IKE_MOBILITY_TIMEOUT -> DataFailCause.IWLAN_IKE_MOBILITY_TIMEOUT;
313             case IwlanError.IKE_DPD_TIMEOUT -> DataFailCause.IWLAN_IKE_DPD_TIMEOUT;
314             default -> DataFailCause.ERROR_UNSPECIFIED;
315         };
316     }
317 
318     // TODO: create DFC for all IkeProtocolExceptions and assign here.
getDataFailCauseForIkeProtocolException(Exception exception)319     private int getDataFailCauseForIkeProtocolException(Exception exception) {
320         if (!(exception instanceof IkeProtocolException ikeProtocolException)) {
321             return DataFailCause.IWLAN_IKE_PRIVATE_PROTOCOL_ERROR;
322         }
323 
324         int protocolErrorType = ikeProtocolException.getErrorType();
325         return switch (protocolErrorType) {
326             case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED ->
327                     DataFailCause.IWLAN_IKEV2_AUTH_FAILURE;
328             case IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE ->
329                     DataFailCause.IWLAN_EPDG_INTERNAL_ADDRESS_FAILURE;
330             case IKE_PROTOCOL_ERROR_PDN_CONNECTION_REJECTION ->
331                     DataFailCause.IWLAN_PDN_CONNECTION_REJECTION;
332             case IKE_PROTOCOL_ERROR_MAX_CONNECTION_REACHED ->
333                     DataFailCause.IWLAN_MAX_CONNECTION_REACHED;
334             case IKE_PROTOCOL_ERROR_SEMANTIC_ERROR_IN_THE_TFT_OPERATION ->
335                     DataFailCause.IWLAN_SEMANTIC_ERROR_IN_THE_TFT_OPERATION;
336             case IKE_PROTOCOL_ERROR_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION ->
337                     DataFailCause.IWLAN_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION;
338             case IKE_PROTOCOL_ERROR_SEMANTIC_ERRORS_IN_PACKET_FILTERS ->
339                     DataFailCause.IWLAN_SEMANTIC_ERRORS_IN_PACKET_FILTERS;
340             case IKE_PROTOCOL_ERROR_SYNTACTICAL_ERRORS_IN_PACKET_FILTERS ->
341                     DataFailCause.IWLAN_SYNTACTICAL_ERRORS_IN_PACKET_FILTERS;
342             case IKE_PROTOCOL_ERROR_NON_3GPP_ACCESS_TO_EPC_NOT_ALLOWED ->
343                     DataFailCause.IWLAN_NON_3GPP_ACCESS_TO_EPC_NOT_ALLOWED;
344             case IKE_PROTOCOL_ERROR_USER_UNKNOWN -> DataFailCause.IWLAN_USER_UNKNOWN;
345             case IKE_PROTOCOL_ERROR_NO_APN_SUBSCRIPTION -> DataFailCause.IWLAN_NO_APN_SUBSCRIPTION;
346             case IKE_PROTOCOL_ERROR_AUTHORIZATION_REJECTED ->
347                     DataFailCause.IWLAN_AUTHORIZATION_REJECTED;
348             case IKE_PROTOCOL_ERROR_ILLEGAL_ME -> DataFailCause.IWLAN_ILLEGAL_ME;
349             case IKE_PROTOCOL_ERROR_NETWORK_FAILURE -> DataFailCause.IWLAN_NETWORK_FAILURE;
350             case IKE_PROTOCOL_ERROR_RAT_TYPE_NOT_ALLOWED ->
351                     DataFailCause.IWLAN_RAT_TYPE_NOT_ALLOWED;
352             case IKE_PROTOCOL_ERROR_IMEI_NOT_ACCEPTED -> DataFailCause.IWLAN_IMEI_NOT_ACCEPTED;
353             case IKE_PROTOCOL_ERROR_PLMN_NOT_ALLOWED -> DataFailCause.IWLAN_PLMN_NOT_ALLOWED;
354             case IKE_PROTOCOL_ERROR_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED ->
355                     DataFailCause.IWLAN_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED;
356             case IKE_PROTOCOL_ERROR_CONGESTION -> DataFailCause.IWLAN_CONGESTION;
357             default -> DataFailCause.IWLAN_IKE_PRIVATE_PROTOCOL_ERROR;
358         };
359     }
360 
361     public synchronized int getMostRecentDataFailCause() {
362         if (mMostRecentError != null) {
363             return getDataFailCause(mMostRecentError.mIwlanError);
364         }
365         return DataFailCause.NONE;
366     }
367 
368     /**
369      * Returns the current remaining backoff duration of the APN
370      *
371      * @param apn APN name for which current backoff duration is needed
372      * @return long current retry time in milliseconds
373      */
374     public synchronized Duration getRemainingBackoffDuration(String apn) {
375         RetryAction lastRetryAction = getLastRetryAction(apn);
376         return lastRetryAction == null
377                 ? UNSPECIFIED_RETRY_DURATION
378                 : getRemainingBackoffDuration(lastRetryAction);
379     }
380 
381     /**
382      * Returns the current remaining backoff duration based on the last retryAction time
383      *
384      * @param retryAction the last error
385      */
386     private static Duration getRemainingBackoffDuration(RetryAction retryAction) {
387         Duration totalBackoffDuration = retryAction.totalBackoffDuration();
388         long errorTime = retryAction.lastErrorTime();
389         long currentTime = IwlanHelper.elapsedRealtime();
390         Duration sinceLastErrorDuration = Duration.ofMillis(currentTime - errorTime);
391         Duration remainingBackupDuration = totalBackoffDuration.minus(sinceLastErrorDuration);
392         return remainingBackupDuration.isNegative() ? Duration.ZERO : remainingBackupDuration;
393     }
394 
395     /**
396      * Gets the last error count of the APN
397      *
398      * @param apn the APN
399      * @return the error count for the last error cause of the APN, 0 if no error or unthrottled
400      */
401     public synchronized int getLastErrorCountOfSameCause(String apn) {
402         RetryAction retryAction = getLastRetryAction(apn);
403         return retryAction != null ? retryAction.errorCountOfSameCause() : 0;
404     }
405 
406     /**
407      * Returns the index of the FQDN to use for ePDG server selection, based on how many FQDNs are
408      * available, the position of the RetryArray index, and configuration of 'NumAttemptsPerFqdn'.
409      * This method assumes backoff time is not configured.
410      *
411      * @param numFqdns number of FQDNs discovered during ePDG server selection.
412      * @return int index of the FQDN to use for ePDG server selection. -1 (invalid) if RetryArray or
413      *     'NumAttemptsPerFqdn' is not specified in the ErrorPolicy.
414      */
415     public synchronized int getCurrentFqdnIndex(int numFqdns) {
416         String apn = mMostRecentError.mApn;
417         RetryAction lastRetryAction = getLastRetryAction(apn);
418         return lastRetryAction == null ? -1 : lastRetryAction.getCurrentFqdnIndex(numFqdns);
419     }
420 
421     @Nullable
422     private synchronized RetryAction getLastRetryAction(String apn) {
423         ApnRetryActionStore retryActionStore = mRetryActionStoreByApn.get(apn);
424         return retryActionStore == null ? null : retryActionStore.getLastRetryAction();
425     }
426 
427     /**
428      * Returns the last error for that apn
429      *
430      * @param apn apn name
431      * @return IwlanError or null if there is no error
432      */
433     public synchronized IwlanError getLastError(String apn) {
434         RetryAction lastRetryAction = getLastRetryAction(apn);
435         return lastRetryAction == null
436                 ? new IwlanError(IwlanError.NO_ERROR)
437                 : lastRetryAction.error();
438     }
439 
440     /**
441      * Returns whether framework should retry tunnel setup with initial PDN bringup request when
442      * handover request fails.
443      *
444      * @param apn apn name
445      * @return boolean result of whether framework should retry tunnel setup with initial PDN
446      *     bringup request when handover request fails
447      */
448     public synchronized boolean shouldRetryWithInitialAttach(String apn) {
449         RetryAction retryAction = getLastRetryAction(apn);
450         return retryAction != null && retryAction.shouldRetryWithInitialAttach();
451     }
452 
453     public void logErrorPolicies() {
454         Log.d(LOG_TAG, "mCarrierConfigPolicies:");
455         for (Map.Entry<String, List<ErrorPolicy>> entry : mCarrierConfigPolicies.entrySet()) {
456             Log.d(LOG_TAG, "Apn: " + entry.getKey());
457             for (ErrorPolicy policy : entry.getValue()) {
458                 policy.log();
459             }
460         }
461         Log.d(LOG_TAG, "mDefaultPolicies:");
462         for (Map.Entry<String, List<ErrorPolicy>> entry : mDefaultPolicies.entrySet()) {
463             Log.d(LOG_TAG, "Apn: " + entry.getKey());
464             for (ErrorPolicy policy : entry.getValue()) {
465                 policy.log();
466             }
467         }
468     }
469 
470     public synchronized void dump(PrintWriter pw) {
471         pw.println("---- ErrorPolicyManager ----");
472         mRetryActionStoreByApn.forEach(
473                 (apn, retryActionStore) -> {
474                     pw.println("APN: " + apn);
475                     pw.println("Last RetryAction: " + retryActionStore.getLastRetryAction());
476                     retryActionStore.mLastRetryActionByCause.forEach(
477                             (cause, retryAction) -> {
478                                 pw.println(cause);
479                                 pw.println(retryAction);
480                             });
481                 });
482         pw.println(mErrorStats);
483         pw.println("----------------------------");
484     }
485 
486     private ErrorPolicyManager(Context context, int slotId) {
487         mContext = context;
488         mSlotId = slotId;
489         LOG_TAG = ErrorPolicyManager.class.getSimpleName() + "[" + slotId + "]";
490 
491         initHandler();
492 
493         // read from default error policy config
494         try {
495             mDefaultPolicies.putAll(
496                     readErrorPolicies(
497                             new JSONArray(
498                                     IwlanCarrierConfig.getDefaultConfigString(
499                                             IwlanCarrierConfig.KEY_ERROR_POLICY_CONFIG_STRING))));
500         } catch (JSONException | IllegalArgumentException e) {
501             throw new AssertionError(e);
502         }
503 
504         mCarrierConfigErrorPolicyString = null;
505         readFromCarrierConfig(IwlanHelper.getCarrierId(mContext, mSlotId));
506         updateUnthrottlingEvents();
507     }
508 
509     private ErrorPolicy findErrorPolicy(String apn, IwlanError iwlanError) {
510         ErrorPolicy policy = null;
511 
512         if (mCarrierConfigPolicies.containsKey(apn)) {
513             policy = getPreferredErrorPolicy(mCarrierConfigPolicies.get(apn), iwlanError);
514         }
515         if (policy == null && mCarrierConfigPolicies.containsKey("*")) {
516             policy = getPreferredErrorPolicy(mCarrierConfigPolicies.get("*"), iwlanError);
517         }
518         if (policy == null && mDefaultPolicies.containsKey(apn)) {
519             policy = getPreferredErrorPolicy(mDefaultPolicies.get(apn), iwlanError);
520         }
521         if (policy == null && mDefaultPolicies.containsKey("*")) {
522             policy = getPreferredErrorPolicy(mDefaultPolicies.get("*"), iwlanError);
523         }
524 
525         if (policy == null) {
526             // there should at least be one default policy defined in Default config
527             // that will apply to all errors.
528             // should not reach here in any situation, default config should be configured in
529             // defaultiwlanerrorconfig.json. here is just for prevent runtime exception
530             logErrorPolicies();
531             Log.e(LOG_TAG, "No matched error policy");
532             policy = FALLBACK_ERROR_POLICY;
533         }
534         return policy;
535     }
536 
537     private ErrorPolicy getPreferredErrorPolicy(
538             List<ErrorPolicy> errorPolicies, IwlanError iwlanError) {
539 
540         ErrorPolicy selectedPolicy = null;
541         for (ErrorPolicy policy : errorPolicies) {
542             if (policy.match(iwlanError)) {
543                 if (!policy.isFallback()) {
544                     selectedPolicy = policy;
545                     break;
546                 }
547                 if (selectedPolicy == null || policy.getErrorType() != GENERIC_ERROR_TYPE) {
548                     selectedPolicy = policy;
549                 }
550             }
551         }
552         return selectedPolicy;
553     }
554 
555     @VisibleForTesting
556     void initHandler() {
557         mHandler = new EpmHandler(getLooper());
558     }
559 
560     @VisibleForTesting
561     Looper getLooper() {
562         mHandlerThread = new HandlerThread("ErrorPolicyManagerThread");
563         mHandlerThread.start();
564         return mHandlerThread.getLooper();
565     }
566 
567     @VisibleForTesting
568     Map<String, List<ErrorPolicy>> readErrorPolicies(JSONArray apnArray)
569             throws JSONException, IllegalArgumentException {
570         Map<String, List<ErrorPolicy>> errorPolicies = new HashMap<>();
571         for (int i = 0; i < apnArray.length(); i++) {
572             JSONObject apnDetails = apnArray.getJSONObject(i);
573 
574             String apnName = ((String) apnDetails.get("ApnName")).trim();
575             JSONArray errorTypeArray = (JSONArray) apnDetails.get("ErrorTypes");
576 
577             for (int j = 0; j < errorTypeArray.length(); j++) {
578                 JSONObject errorTypeObject = errorTypeArray.getJSONObject(j);
579 
580                 String errorTypeStr = ((String) errorTypeObject.get("ErrorType")).trim();
581                 JSONArray errorDetailArray = (JSONArray) errorTypeObject.get("ErrorDetails");
582                 int errorType;
583 
584                 if ((errorType = getErrorPolicyErrorType(errorTypeStr)) == UNKNOWN_ERROR_TYPE) {
585                     throw new IllegalArgumentException("Unknown error type in the parsing");
586                 }
587 
588                 List<Integer> retryArray =
589                         parseRetryArray((JSONArray) errorTypeObject.get("RetryArray"));
590 
591                 ErrorPolicy.Builder errorPolicyBuilder =
592                         builder()
593                                 .setErrorType(errorType)
594                                 .setErrorDetails(parseErrorDetails(errorType, errorDetailArray))
595                                 .setRetryArray(retryArray)
596                                 .setUnthrottlingEvents(
597                                         parseUnthrottlingEvents(
598                                                 (JSONArray)
599                                                         errorTypeObject.get("UnthrottlingEvents")));
600 
601                 if (!retryArray.isEmpty() && retryArray.get(retryArray.size() - 1) == -1L) {
602                     errorPolicyBuilder.setInfiniteRetriesWithLastRetryTime(true);
603                 }
604 
605                 if (errorTypeObject.has("NumAttemptsPerFqdn")) {
606                     errorPolicyBuilder.setNumAttemptsPerFqdn(
607                             errorTypeObject.getInt("NumAttemptsPerFqdn"));
608                 }
609 
610                 if (errorTypeObject.has("HandoverAttemptCount")) {
611                     if (errorType != IKE_PROTOCOL_ERROR_TYPE) {
612                         throw new IllegalArgumentException(
613                                 "Handover attempt count should not be applied when errorType is not"
614                                         + " explicitly defined as IKE_PROTOCOL_ERROR_TYPE");
615                     }
616                     errorPolicyBuilder.setHandoverAttemptCount(
617                             errorTypeObject.getInt("HandoverAttemptCount"));
618                 }
619 
620                 ErrorPolicy errorPolicy = errorPolicyBuilder.build();
621 
622                 errorPolicies.putIfAbsent(apnName, new ArrayList<>());
623                 errorPolicies.get(apnName).add(errorPolicy);
624             }
625         }
626         return errorPolicies;
627     }
628 
629     private List<Integer> parseRetryArray(JSONArray retryArray)
630             throws JSONException, IllegalArgumentException {
631         List<Integer> ret = new ArrayList<>();
632         for (int i = 0; i < retryArray.length(); i++) {
633             String retryTime = retryArray.getString(i).trim();
634 
635             // catch misplaced -1 retry times in the array.
636             // 1. if it is not placed at the last position in the array
637             // 2. if it is placed in the first position (catches the case where it is
638             //    the only element).
639             if (retryTime.equals("-1") && (i != retryArray.length() - 1 || i == 0)) {
640                 throw new IllegalArgumentException("Misplaced -1 in retry array");
641             }
642             if (TextUtils.isDigitsOnly(retryTime) || retryTime.equals("-1")) {
643                 ret.add(Integer.parseInt(retryTime));
644             } else if (retryTime.contains("+r")) {
645                 // randomized retry time
646                 String[] times = retryTime.split("\\+r");
647                 if (times.length == 2
648                         && TextUtils.isDigitsOnly(times[0])
649                         && TextUtils.isDigitsOnly(times[1])) {
650                     ret.add(
651                             Integer.parseInt(times[0])
652                                     + (int) (Math.random() * Long.parseLong(times[1])));
653                 } else {
654                     throw new IllegalArgumentException(
655                             "Randomized Retry time is not in acceptable format");
656                 }
657             } else {
658                 throw new IllegalArgumentException("Retry time is not in acceptable format");
659             }
660         }
661         return ret;
662     }
663 
664     private List<Integer> parseUnthrottlingEvents(JSONArray unthrottlingEvents)
665             throws JSONException, IllegalArgumentException {
666         List<Integer> ret = new ArrayList<>();
667         for (int i = 0; i < unthrottlingEvents.length(); i++) {
668             int event =
669                     IwlanEventListener.getUnthrottlingEvent(unthrottlingEvents.getString(i).trim());
670             if (event == IwlanEventListener.UNKNOWN_EVENT) {
671                 throw new IllegalArgumentException(
672                         "Unexpected unthrottlingEvent " + unthrottlingEvents.getString(i));
673             }
674             ret.add(event);
675         }
676         return ret;
677     }
678 
679     private List<String> parseErrorDetails(int errorType, JSONArray errorDetailArray)
680             throws JSONException, IllegalArgumentException {
681         List<String> ret = new ArrayList<>();
682 
683         for (int i = 0; i < errorDetailArray.length(); i++) {
684             String errorDetail = errorDetailArray.getString(i).trim();
685             boolean isValidErrorDetail =
686                     switch (errorType) {
687                         case IKE_PROTOCOL_ERROR_TYPE -> verifyIkeProtocolErrorDetail(errorDetail);
688                         case GENERIC_ERROR_TYPE -> verifyGenericErrorDetail(errorDetail);
689                         default -> true;
690                     };
691             if (!isValidErrorDetail) {
692                 throw new IllegalArgumentException(
693                         "Invalid ErrorDetail: " + errorDetail + " for ErrorType: " + errorType);
694             }
695             ret.add(errorDetail);
696         }
697         return ret;
698     }
699 
700     /** Allowed formats are: number(Integer), range(Integers separated by -) and "*" */
701     private boolean verifyIkeProtocolErrorDetail(String errorDetailStr) {
702         boolean ret = true;
703         if (errorDetailStr.contains("-")) {
704             // verify range format
705             String[] rangeNumbers = errorDetailStr.split("-");
706             if (rangeNumbers.length == 2) {
707                 for (String range : rangeNumbers) {
708                     if (!TextUtils.isDigitsOnly(range)) {
709                         ret = false;
710                     }
711                 }
712             } else {
713                 ret = false;
714             }
715         } else if (!errorDetailStr.equals("*") && !TextUtils.isDigitsOnly(errorDetailStr)) {
716             ret = false;
717         }
718         return ret;
719     }
720 
721     /**
722      * Allowed strings are: "IO_EXCEPTION", "TIMEOUT_EXCEPTION", "SERVER_SELECTION_FAILED",
723      * "TUNNEL_TRANSFORM_FAILED" and "*"
724      */
725     private boolean verifyGenericErrorDetail(String errorDetailStr) {
726         boolean ret = false;
727         for (String str : GENERIC_ERROR_DETAIL_STRINGS) {
728             if (errorDetailStr.equals(str)) {
729                 ret = true;
730                 break;
731             }
732         }
733         return ret;
734     }
735 
736     private @ErrorPolicyErrorType int getErrorPolicyErrorType(String errorType) {
737         return switch (errorType) {
738             case "IKE_PROTOCOL_ERROR_TYPE" -> IKE_PROTOCOL_ERROR_TYPE;
739             case "GENERIC_ERROR_TYPE" -> GENERIC_ERROR_TYPE;
740             case "*" -> FALLBACK_ERROR_TYPE;
741             default -> UNKNOWN_ERROR_TYPE;
742         };
743     }
744 
745     private synchronized Set<Integer> getAllUnthrottlingEvents() {
746         Set<Integer> events = new HashSet<>();
747         for (Map.Entry<String, List<ErrorPolicy>> entry : mCarrierConfigPolicies.entrySet()) {
748             List<ErrorPolicy> errorPolicies = entry.getValue();
749             for (ErrorPolicy errorPolicy : errorPolicies) {
750                 events.addAll(errorPolicy.unthrottlingEvents());
751             }
752         }
753         for (Map.Entry<String, List<ErrorPolicy>> entry : mDefaultPolicies.entrySet()) {
754             List<ErrorPolicy> errorPolicies = entry.getValue();
755             for (ErrorPolicy errorPolicy : errorPolicies) {
756                 events.addAll(errorPolicy.unthrottlingEvents());
757             }
758         }
759         events.add(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT);
760         return events;
761     }
762 
763     /**
764      * This method is called once on initialization of this class And is also called from handler on
765      * CARRIER_CONFIG_CHANGED event. There is no race condition between both as we register for the
766      * events after the calling this method.
767      */
768     private synchronized void readFromCarrierConfig(int currentCarrierId) {
769         String carrierConfigErrorPolicy =
770                 IwlanCarrierConfig.getConfigString(
771                         mContext, mSlotId, IwlanCarrierConfig.KEY_ERROR_POLICY_CONFIG_STRING);
772         if (carrierConfigErrorPolicy == null) {
773             Log.e(LOG_TAG, "ErrorPolicy from Carrier Config is NULL");
774             mCarrierConfigPolicies.clear();
775             mCarrierConfigErrorPolicyString = null;
776             return;
777         }
778         try {
779             Map<String, List<ErrorPolicy>> errorPolicies =
780                     readErrorPolicies(new JSONArray(carrierConfigErrorPolicy));
781             if (!errorPolicies.isEmpty()) {
782                 mCarrierConfigErrorPolicyString = carrierConfigErrorPolicy;
783                 carrierId = currentCarrierId;
784                 mCarrierConfigPolicies.clear();
785                 mCarrierConfigPolicies.putAll(errorPolicies);
786             }
787         } catch (JSONException | IllegalArgumentException e) {
788             Log.e(
789                     LOG_TAG,
790                     "Unable to parse the ErrorPolicy from CarrierConfig\n"
791                             + carrierConfigErrorPolicy);
792             mCarrierConfigPolicies.clear();
793             mCarrierConfigErrorPolicyString = null;
794             e.printStackTrace();
795         }
796     }
797 
798     private void updateUnthrottlingEvents() {
799         Set<Integer> registerEvents, unregisterEvents;
800         unregisterEvents = mUnthrottlingEvents;
801         registerEvents = getAllUnthrottlingEvents();
802         mUnthrottlingEvents = getAllUnthrottlingEvents();
803 
804         if (unregisterEvents != null) {
805             registerEvents.removeAll(unregisterEvents);
806             unregisterEvents.removeAll(mUnthrottlingEvents);
807         }
808 
809         IwlanEventListener.getInstance(mContext, mSlotId)
810                 .addEventListener(new ArrayList<>(registerEvents), mHandler);
811         if (unregisterEvents != null) {
812             IwlanEventListener.getInstance(mContext, mSlotId)
813                     .removeEventListener(new ArrayList<>(unregisterEvents), mHandler);
814         }
815         Log.d(
816                 LOG_TAG,
817                 "UnthrottlingEvents: "
818                         + (mUnthrottlingEvents != null
819                                 ? Arrays.toString(mUnthrottlingEvents.toArray())
820                                 : "null"));
821     }
822 
823     private synchronized void unthrottleLastErrorOnEvent(int event) {
824         Log.d(LOG_TAG, "unthrottleLastErrorOnEvent: " + event);
825         // Pass the other events to RetryActionStore to check if can unthrottle
826         mRetryActionStoreByApn.forEach(
827                 (apn, retryActionStore) -> retryActionStore.handleUnthrottlingEvent(event));
828         // Carrier Config Changed should clear all RetryActionStore
829         if (event == IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) {
830             mRetryActionStoreByApn.clear();
831         }
832     }
833 
834     @VisibleForTesting
835     ErrorStats getErrorStats() {
836         return mErrorStats;
837     }
838 
839     @AutoValue
840     abstract static class ErrorPolicy {
841         private static final String LOG_TAG = ErrorPolicyManager.class.getSimpleName();
842 
843         abstract @ErrorPolicyErrorType int errorType();
844 
845         abstract List<String> errorDetails();
846 
847         abstract List<Integer> retryArray();
848 
849         abstract boolean infiniteRetriesWithLastRetryTime();
850 
851         abstract List<Integer> unthrottlingEvents();
852 
853         abstract Optional<Integer> numAttemptsPerFqdn();
854 
855         abstract Optional<Integer> handoverAttemptCount();
856 
857         @AutoValue.Builder
858         abstract static class Builder {
859             abstract Builder setErrorType(int errorType);
860 
861             abstract Builder setErrorDetails(List<String> errorDetails);
862 
863             abstract Builder setRetryArray(List<Integer> retryArray);
864 
865             abstract Builder setInfiniteRetriesWithLastRetryTime(
866                     boolean infiniteRetriesWithLastRetryTime);
867 
868             abstract Builder setUnthrottlingEvents(List<Integer> unthrottlingEvents);
869 
870             abstract Builder setNumAttemptsPerFqdn(Integer numAttemptsPerFqdn);
871 
872             abstract Builder setHandoverAttemptCount(Integer handoverAttemptCount);
873 
874             abstract ErrorPolicy build();
875         }
876 
877         long getRetryTime(int index) {
878             long retryTime = -1;
879             if (retryArray().size() > 0) {
880                 // If the index is greater than or equal to the last element's index
881                 // and if the last item in the retryArray is "-1" use the retryTime
882                 // of the element before the last element to repeat the element.
883                 if (infiniteRetriesWithLastRetryTime()) {
884                     index = Math.min(index, retryArray().size() - 2);
885                 }
886                 if (index >= 0 && index < retryArray().size()) {
887                     retryTime = retryArray().get(index);
888                 }
889             }
890 
891             // retryTime -1 represents indefinite failure. In that case
892             // return time that represents 1 day to not retry for that day.
893             if (retryTime == -1L) {
894                 retryTime = TimeUnit.DAYS.toSeconds(1);
895             }
896             return retryTime;
897         }
898 
899         int getCurrentFqdnIndex(int retryIndex, int numFqdns) {
900             int result = -1;
901             if (numAttemptsPerFqdn().isEmpty() || retryArray().size() <= 0) {
902                 return result;
903             }
904             // Cycles between 0 and (numFqdns - 1), based on the current attempt count and size of
905             // mRetryArray.
906             return (retryIndex + 1) / numAttemptsPerFqdn().get() % numFqdns;
907         }
908 
909         @ErrorPolicyErrorType
910         int getErrorType() {
911             return errorType();
912         }
913 
914         int getHandoverAttemptCount() {
915             return handoverAttemptCount().orElse(Integer.MAX_VALUE);
916         }
917 
918         synchronized boolean canUnthrottle(int event) {
919             return unthrottlingEvents().contains(event);
920         }
921 
922         boolean match(IwlanError iwlanError) {
923             // Generic by default to match to generic policy.
924             String iwlanErrorDetail;
925             if (errorType() == FALLBACK_ERROR_TYPE) {
926                 return true;
927             } else if (errorType() == IKE_PROTOCOL_ERROR_TYPE
928                     && iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION) {
929                 IkeProtocolException exception = (IkeProtocolException) iwlanError.getException();
930                 iwlanErrorDetail = String.valueOf(exception.getErrorType());
931             } else if (errorType() == GENERIC_ERROR_TYPE) {
932                 iwlanErrorDetail = getGenericErrorDetailString(iwlanError);
933                 if (iwlanErrorDetail.equals("UNKNOWN")) {
934                     return false;
935                 }
936             } else {
937                 return false;
938             }
939 
940             boolean ret = false;
941             for (String errorDetail : errorDetails()) {
942                 if (errorType() == IKE_PROTOCOL_ERROR_TYPE
943                         && iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION
944                         && errorDetail.contains("-")) {
945                     // error detail is stored in range format.
946                     // ErrorPolicyManager#verifyIkeProtocolErrorDetail will make sure that
947                     // this is stored correctly in "min-max" format.
948                     String[] range = errorDetail.split("-");
949                     int min = Integer.parseInt(range[0]);
950                     int max = Integer.parseInt(range[1]);
951                     int error = Integer.parseInt(iwlanErrorDetail);
952                     if (error >= min && error <= max) {
953                         ret = true;
954                         break;
955                     }
956                 } else if (errorDetail.equals(iwlanErrorDetail) || errorDetail.equals("*")) {
957                     ret = true;
958                     break;
959                 }
960             }
961             return ret;
962         }
963 
964         void log() {
965             Log.d(LOG_TAG, "ErrorType: " + errorType());
966             Log.d(LOG_TAG, "ErrorDetail: " + Arrays.toString(errorDetails().toArray()));
967             Log.d(LOG_TAG, "RetryArray: " + Arrays.toString(retryArray().toArray()));
968             Log.d(
969                     LOG_TAG,
970                     "InfiniteRetriesWithLastRetryTime: " + infiniteRetriesWithLastRetryTime());
971             Log.d(
972                     LOG_TAG,
973                     "UnthrottlingEvents: " + Arrays.toString(unthrottlingEvents().toArray()));
974             Log.d(LOG_TAG, "NumAttemptsPerFqdn: " + numAttemptsPerFqdn());
975             Log.d(LOG_TAG, "handoverAttemptCount: " + handoverAttemptCount());
976         }
977 
978         boolean isFallback() {
979             return (errorType() == FALLBACK_ERROR_TYPE)
980                     || (errorDetails().size() == 1 && errorDetails().get(0).equals("*"));
981         }
982 
983         String getGenericErrorDetailString(IwlanError iwlanError) {
984             return switch (iwlanError.getErrorType()) {
985                 case IwlanError.IKE_INTERNAL_IO_EXCEPTION -> "IO_EXCEPTION";
986                 case IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED -> "SERVER_SELECTION_FAILED";
987                 case IwlanError.TUNNEL_TRANSFORM_FAILED -> "TUNNEL_TRANSFORM_FAILED";
988                 case IwlanError.IKE_NETWORK_LOST_EXCEPTION -> "IKE_NETWORK_LOST_EXCEPTION";
989                 case IwlanError.EPDG_ADDRESS_ONLY_IPV4_ALLOWED -> "EPDG_ADDRESS_ONLY_IPV4_ALLOWED";
990                 case IwlanError.EPDG_ADDRESS_ONLY_IPV6_ALLOWED -> "EPDG_ADDRESS_ONLY_IPV6_ALLOWED";
991                 // TODO: Add TIMEOUT_EXCEPTION processing
992                 // TODO: Add all the missing error detail string
993                 case IwlanError.IKE_INIT_TIMEOUT -> "IKE_INIT_TIMEOUT";
994                 case IwlanError.IKE_MOBILITY_TIMEOUT -> "IKE_MOBILITY_TIMEOUT";
995                 case IwlanError.IKE_DPD_TIMEOUT -> "IKE_DPD_TIMEOUT";
996                 default -> "UNKNOWN";
997             };
998         }
999     }
1000 
1001     /**
1002      * A data class to store the error cause and the applied error policy. This class is responsible
1003      * to calculate the retry time base on the error policy / config.
1004      */
1005     interface RetryAction {
1006         IwlanError error();
1007 
1008         ErrorPolicy errorPolicy();
1009 
1010         long lastErrorTime();
1011 
1012         /** The total time should be waited between lastErrorTime and next retry. */
1013         Duration totalBackoffDuration();
1014 
1015         /** The number of same cause error observed since last success / unthrottle event. */
1016         int errorCountOfSameCause();
1017 
1018         boolean shouldRetryWithInitialAttach();
1019 
1020         int getCurrentFqdnIndex(int numFqdns);
1021     }
1022 
1023     /** RetryAction with retry time defined by retry index and error policy */
1024     record PolicyDerivedRetryAction(
1025             @Override IwlanError error,
1026             @Override ErrorPolicy errorPolicy,
1027             @Override long lastErrorTime,
1028             @Override int errorCountOfSameCause,
1029             int currentRetryIndex)
1030             implements RetryAction {
1031         @Override
1032         public Duration totalBackoffDuration() {
1033             return Duration.ofSeconds(errorPolicy().getRetryTime(currentRetryIndex()));
1034         }
1035 
1036         @Override
1037         public int getCurrentFqdnIndex(int numFqdns) {
1038             ErrorPolicy errorPolicy = errorPolicy();
1039             return errorPolicy.getCurrentFqdnIndex(currentRetryIndex(), numFqdns);
1040         }
1041 
1042         @Override
1043         public boolean shouldRetryWithInitialAttach() {
1044             // UE should only uses initial attach to reset network failure, not for UE internal or
1045             // DNS errors. When the number of handover failures due to network issues exceeds the
1046             // configured threshold, UE should request network with initial attach instead of
1047             // handover request.
1048             ErrorPolicy errorPolicy = errorPolicy();
1049             return errorPolicy.getErrorType() == IKE_PROTOCOL_ERROR_TYPE
1050                     && currentRetryIndex() + 1 >= errorPolicy.getHandoverAttemptCount();
1051         }
1052     }
1053 
1054     /** RetryAction with retry time defined by backoff time in tunnel config */
1055     record IkeBackoffNotifyRetryAction(
1056             @Override IwlanError error,
1057             @Override ErrorPolicy errorPolicy,
1058             @Override long lastErrorTime,
1059             @Override int errorCountOfSameCause,
1060             @Override Duration totalBackoffDuration)
1061             implements RetryAction {
1062 
1063         @Override
1064         public int getCurrentFqdnIndex(int numFqdns) {
1065             // Not applicable for backoff time configured case, therefore returning 0 here
1066             return 0;
1067         }
1068 
1069         @Override
1070         public boolean shouldRetryWithInitialAttach() {
1071             // TODO(b/308745683): Initial attach condition is undefined for backoff config case
1072             ErrorPolicy errorPolicy = errorPolicy();
1073             return errorPolicy.getErrorType() == IKE_PROTOCOL_ERROR_TYPE
1074                     && errorPolicy.getHandoverAttemptCount() == 0;
1075         }
1076     }
1077 
1078     interface ErrorCause {
1079         @IwlanError.IwlanErrorType
1080         int iwlanErrorType();
1081 
1082         static ErrorCause fromIwlanError(IwlanError iwlanError) {
1083             if (iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION) {
1084                 return new AutoValue_ErrorPolicyManager_IkeProtocolErrorCause(
1085                         /* ikeProtocolErrorType= */ ((IkeProtocolException)
1086                                         iwlanError.getException())
1087                                 .getErrorType());
1088             }
1089             return new AutoValue_ErrorPolicyManager_NonIkeProtocolErrorCause(
1090                     /* iwlanErrorType= */ iwlanError.getErrorType());
1091         }
1092     }
1093 
1094     @AutoValue
1095     abstract static class NonIkeProtocolErrorCause implements ErrorCause {}
1096 
1097     /**
1098      * An IkeProtocolErrorCause will carry the ike protocol error type, so that different protocol
1099      * error will be treated as different error cause
1100      */
1101     @AutoValue
1102     abstract static class IkeProtocolErrorCause implements ErrorCause {
1103         @Override
1104         @IwlanError.IwlanErrorType
1105         public int iwlanErrorType() {
1106             return IwlanError.IKE_PROTOCOL_EXCEPTION;
1107         }
1108 
1109         // @IkeProtocolException.ErrorType is hidden API
1110         abstract int ikeProtocolErrorType();
1111     }
1112 
1113     /**
1114      * This class manage and store the RetryAction of the APN, and responsible to create RetryAction
1115      * when IwlanError received.
1116      */
1117     class ApnRetryActionStore {
1118         final String mApn;
1119         final ConcurrentHashMap<ErrorCause, RetryAction> mLastRetryActionByCause;
1120         @Nullable RetryAction mLastRetryAction;
1121 
1122         ApnRetryActionStore(String apn) {
1123             mApn = apn;
1124             mLastRetryActionByCause = new ConcurrentHashMap<>();
1125         }
1126 
1127         /**
1128          * Determines whether the new {@link RetryAction} should accumulate the retry index from
1129          * {@code prevRetryAction}.
1130          *
1131          * @param prevRetryAction the previous RetryAction (can be null).
1132          * @param newIwlanError the new IwlanError.
1133          * @return true if {@code prevRetryAction} is an instance of {@link
1134          *     PolicyDerivedRetryAction} and is the same {@link ErrorCause} as {@code
1135          *     newIwlanError}, false otherwise.
1136          */
1137         private boolean shouldAccumulateRetryIndex(
1138                 @Nullable RetryAction prevRetryAction, IwlanError newIwlanError) {
1139             if (!(prevRetryAction instanceof PolicyDerivedRetryAction)) {
1140                 return false;
1141             }
1142 
1143             boolean isSameIwlanError = prevRetryAction.error().equals(newIwlanError);
1144             // If prev and current error are both IKE_PROTOCOL_EXCEPTION, keep the retry index
1145             // TODO: b/292312000 - Workaround for RetryIndex lost
1146             boolean areBothIkeProtocolException =
1147                     (newIwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION
1148                             && prevRetryAction.error().getErrorType()
1149                                     == IwlanError.IKE_PROTOCOL_EXCEPTION);
1150             boolean shouldAccumulateRetryIndex = isSameIwlanError || areBothIkeProtocolException;
1151 
1152             if (!shouldAccumulateRetryIndex) {
1153                 Log.d(LOG_TAG, "Doesn't match to the previous error" + newIwlanError);
1154             }
1155 
1156             return shouldAccumulateRetryIndex;
1157         }
1158 
1159         private PolicyDerivedRetryAction generateRetryAction(IwlanError iwlanError) {
1160             ErrorCause errorCause = ErrorCause.fromIwlanError(iwlanError);
1161 
1162             @Nullable RetryAction prevRetryAction = mLastRetryActionByCause.get(errorCause);
1163             int newErrorCount =
1164                     prevRetryAction != null ? prevRetryAction.errorCountOfSameCause() + 1 : 1;
1165             boolean shouldAccumulateRetryIndex =
1166                     shouldAccumulateRetryIndex(prevRetryAction, iwlanError);
1167             int newRetryIndex =
1168                     shouldAccumulateRetryIndex
1169                             ? ((PolicyDerivedRetryAction) prevRetryAction).currentRetryIndex() + 1
1170                             : 0;
1171 
1172             ErrorPolicy policy = findErrorPolicy(mApn, iwlanError);
1173             PolicyDerivedRetryAction newRetryAction =
1174                     new PolicyDerivedRetryAction(
1175                             iwlanError,
1176                             policy,
1177                             IwlanHelper.elapsedRealtime(),
1178                             newErrorCount,
1179                             newRetryIndex);
1180             mLastRetryActionByCause.put(errorCause, newRetryAction);
1181             mLastRetryAction = newRetryAction;
1182 
1183             return newRetryAction;
1184         }
1185 
1186         private IkeBackoffNotifyRetryAction generateRetryAction(
1187                 IwlanError iwlanError, Duration backoffDuration) {
1188             ErrorCause errorCause = ErrorCause.fromIwlanError(iwlanError);
1189             @Nullable RetryAction prevRetryAction = mLastRetryActionByCause.get(errorCause);
1190             int newErrorCount =
1191                     prevRetryAction != null ? prevRetryAction.errorCountOfSameCause() + 1 : 1;
1192             ErrorPolicy policy = findErrorPolicy(mApn, iwlanError);
1193             // For configured back off time case, simply create new RetryAction, nothing need to
1194             // keep
1195             IkeBackoffNotifyRetryAction newRetryAction =
1196                     new IkeBackoffNotifyRetryAction(
1197                             iwlanError,
1198                             policy,
1199                             IwlanHelper.elapsedRealtime(),
1200                             newErrorCount,
1201                             backoffDuration);
1202             mLastRetryActionByCause.put(errorCause, newRetryAction);
1203             mLastRetryAction = newRetryAction;
1204 
1205             return newRetryAction;
1206         }
1207 
1208         /**
1209          * Set {@code lastRetryAction} to null if {@code lastRetryAction} can be unthrottled by the
1210          * event. Clear those reserved retry index and the {@link RetryAction} if any {@link
1211          * RetryAction} in {@code mLastRetryActionByCause} can be unthrottled by the event.
1212          *
1213          * @param event the handling event
1214          */
1215         private void handleUnthrottlingEvent(int event) {
1216             if (event == IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT) {
1217                 mLastRetryActionByCause.clear();
1218             } else {
1219                 // Check all stored RetryAction, remove from the store if it can be unthrottle.
1220                 // By removing it, the retry index (for PolicyDerived) will reset as 0
1221                 mLastRetryActionByCause
1222                         .entrySet()
1223                         .removeIf(it -> it.getValue().errorPolicy().canUnthrottle(event));
1224             }
1225 
1226             DataService.DataServiceProvider provider =
1227                     IwlanDataService.getDataServiceProvider(mSlotId);
1228 
1229             boolean isCarrierConfigChanged =
1230                     event == IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT;
1231             boolean isLastRetryActionCanUnthrottle =
1232                     mLastRetryAction != null && mLastRetryAction.errorPolicy().canUnthrottle(event);
1233             if (isCarrierConfigChanged || isLastRetryActionCanUnthrottle) {
1234                 mLastRetryAction = null;
1235 
1236                 if (provider == null) {
1237                     Log.w(LOG_TAG, "DataServiceProvider not found for slot: " + mSlotId);
1238                 } else {
1239                     provider.notifyApnUnthrottled(mApn);
1240                     Log.d(LOG_TAG, "unthrottled error for: " + mApn);
1241                 }
1242             }
1243         }
1244 
1245         @Nullable
1246         private RetryAction getLastRetryAction() {
1247             return mLastRetryAction;
1248         }
1249     }
1250 
1251     static class ApnWithIwlanError {
1252         @NonNull final String mApn;
1253         @NonNull final IwlanError mIwlanError;
1254 
1255         ApnWithIwlanError(@NonNull String apn, @NonNull IwlanError iwlanError) {
1256             mApn = apn;
1257             mIwlanError = iwlanError;
1258         }
1259     }
1260 
1261     private boolean isValidCarrierConfigChangedEvent(int currentCarrierId) {
1262         String errorPolicyConfig =
1263                 IwlanCarrierConfig.getConfigString(
1264                         mContext, mSlotId, IwlanCarrierConfig.KEY_ERROR_POLICY_CONFIG_STRING);
1265         return (currentCarrierId != carrierId)
1266                 || (mCarrierConfigErrorPolicyString == null)
1267                 || (errorPolicyConfig != null
1268                         && !Objects.equals(mCarrierConfigErrorPolicyString, errorPolicyConfig));
1269     }
1270 
1271     private final class EpmHandler extends Handler {
1272         private final String TAG = EpmHandler.class.getSimpleName();
1273 
1274         @Override
1275         public void handleMessage(Message msg) {
1276             Log.d(TAG, "msg.what = " + msg.what);
1277             switch (msg.what) {
1278                 case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT:
1279                     Log.d(TAG, "On CARRIER_CONFIG_CHANGED_EVENT");
1280                     int currentCarrierId = IwlanHelper.getCarrierId(mContext, mSlotId);
1281                     if (isValidCarrierConfigChangedEvent(currentCarrierId)) {
1282                         Log.d(TAG, "Unthrottle last error and read from carrier config");
1283                         unthrottleLastErrorOnEvent(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT);
1284                         readFromCarrierConfig(currentCarrierId);
1285                         updateUnthrottlingEvents();
1286                     }
1287                     break;
1288                 case IwlanEventListener.APM_ENABLE_EVENT:
1289                 case IwlanEventListener.APM_DISABLE_EVENT:
1290                 case IwlanEventListener.WIFI_DISABLE_EVENT:
1291                 case IwlanEventListener.WIFI_CALLING_DISABLE_EVENT:
1292                 case IwlanEventListener.WIFI_AP_CHANGED_EVENT:
1293                     unthrottleLastErrorOnEvent(msg.what);
1294                     break;
1295                 default:
1296                     Log.d(TAG, "Unknown message received!");
1297                     break;
1298             }
1299         }
1300 
1301         EpmHandler(Looper looper) {
1302             super(looper);
1303         }
1304     }
1305 
1306     @VisibleForTesting
1307     static class ErrorStats {
1308         @VisibleForTesting Map<String, Map<String, Long>> mStats = new HashMap<>();
1309         private Date mStartTime;
1310         private int mStatCount;
1311         private static final int APN_COUNT_MAX = 10;
1312         private static final int ERROR_COUNT_MAX = 1000;
1313 
1314         ErrorStats() {
1315             mStartTime = Calendar.getInstance().getTime();
1316             mStatCount = 0;
1317         }
1318 
1319         void update(String apn, IwlanError error) {
1320             if (mStats.size() >= APN_COUNT_MAX || mStatCount >= ERROR_COUNT_MAX) {
1321                 reset();
1322             }
1323             if (!mStats.containsKey(apn)) {
1324                 mStats.put(apn, new HashMap<>());
1325             }
1326             Map<String, Long> errorMap = mStats.get(apn);
1327             String errorString = error.toString();
1328             if (!errorMap.containsKey(errorString)) {
1329                 errorMap.put(errorString, 0L);
1330             }
1331             long count = errorMap.get(errorString);
1332             errorMap.put(errorString, ++count);
1333             mStats.put(apn, errorMap);
1334             mStatCount++;
1335         }
1336 
1337         void reset() {
1338             mStartTime = Calendar.getInstance().getTime();
1339             mStats = new HashMap<>();
1340             mStatCount = 0;
1341         }
1342 
1343         @Override
1344         public String toString() {
1345             StringBuilder sb = new StringBuilder();
1346             sb.append("mStartTime: ").append(mStartTime);
1347             sb.append("\nErrorStats");
1348             for (Map.Entry<String, Map<String, Long>> entry : mStats.entrySet()) {
1349                 sb.append("\n\tApn: ").append(entry.getKey());
1350                 for (Map.Entry<String, Long> errorEntry : entry.getValue().entrySet()) {
1351                     sb.append("\n\t  ")
1352                             .append(errorEntry.getKey())
1353                             .append(" : ")
1354                             .append(errorEntry.getValue());
1355                 }
1356             }
1357             return sb.toString();
1358         }
1359     }
1360 }
1361