1 /*
2  * Copyright (C) 2023 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 package com.android.internal.net.ipsec.ike.utils;
17 
18 import static android.net.ipsec.ike.IkeManager.getIkeLog;
19 
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.net.ipsec.ike.IkeSessionCallback;
23 
24 import java.lang.annotation.Retention;
25 import java.lang.annotation.RetentionPolicy;
26 import java.util.concurrent.Executor;
27 
28 /**
29  * The LivenessAssister helps the process from requesting liveness check to delivering a result, and
30  * helps to handle status callbacks according to the results.
31  *
32  * <p>The liveness check has a simple process of receiving a request from a client, checking a
33  * peer's liveness, and then reporting the results. Results can only be reported upon request, and
34  * the LivenessAssister also provides callbacks for the results.
35  *
36  * <p>A process of state change as follows. Failure will be reported before closing the session.
37  *
38  * <ul>
39  *   <li>Requested -> report (on-demand or background) Started -> report Success -> Clear Requested
40  *   <li>Requested -> report (on-demand or background) Started -> report failure -> no longer used
41  *   <li>Requested -> report (on-demand) Started -> if requested again -> report (on-demand) ongoing
42  *       -> report Success -> Clear Requested
43  *   <li>Requested -> report (on-demand) Started -> if requested again -> report (on-demand) ongoing
44  *       -> report failure -> no longer used
45  *   <li>Requested -> report (background) Started -> if requested again -> report (background)
46  *       ongoing -> report Success -> NotRequested
47  *   <li>Requested -> report (background) Started -> if requested again -> report (background)
48  *       ongoing -> report failure -> no longer used
49  * </ul>
50  */
51 public class LivenessAssister {
52 
53     private static final String TAG = LivenessAssister.class.getSimpleName();
54 
55     /** Initial. */
56     public static final int REQ_TYPE_INITIAL = 0;
57 
58     /** A liveness check request is performed as an on-demand task. */
59     public static final int REQ_TYPE_ON_DEMAND = 1;
60 
61     /** A liveness check request is performed in the background. */
62     public static final int REQ_TYPE_BACKGROUND = 2;
63 
64     @Retention(RetentionPolicy.SOURCE)
65     @IntDef({
66         REQ_TYPE_INITIAL,
67         REQ_TYPE_ON_DEMAND,
68         REQ_TYPE_BACKGROUND,
69     })
70     @interface LivenessRequestType {}
71 
72     private final IkeSessionCallback mCallback;
73     private final Executor mUserCbExecutor;
74 
75     private int mLivenessCheckRequested;
76 
77     private LivenessMetricHelper mLivenessMetricHelper;
78 
LivenessAssister( @onNull IkeSessionCallback callback, @NonNull Executor executor, @NonNull IIkeMetricsCallback metricsCallback)79     public LivenessAssister(
80             @NonNull IkeSessionCallback callback,
81             @NonNull Executor executor,
82             @NonNull IIkeMetricsCallback metricsCallback) {
83         mCallback = callback;
84         mUserCbExecutor = executor;
85         mLivenessCheckRequested = REQ_TYPE_INITIAL;
86         mLivenessMetricHelper = new LivenessMetricHelper(metricsCallback);
87     }
88 
89     /**
90      * Set the status as requested for the liveness check and notify the associated liveness status.
91      *
92      * <p>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_ON_DEMAND_STARTED}: This status
93      * is notified when liveness checking is started with a new on-demand DPD task.
94      *
95      * <p>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_ON_DEMAND_ONGOING}: This status
96      * is notified when liveness checking is already running in an on-demand DPD task.
97      *
98      * <p>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_BACKGROUND_STARTED}: This status
99      * is notified when liveness checking is started with joining an existing running task.
100      *
101      * <p>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_BACKGROUND_ONGOING}: This status
102      * is notified when liveness checking is already running with joining an existing running task.
103      *
104      * @param requestType request type of {@link LivenessRequestType#REQ_TYPE_ON_DEMAND} or {@link
105      *     LivenessRequestType#REQ_TYPE_BACKGROUND}
106      */
livenessCheckRequested(@ivenessRequestType int requestType)107     public void livenessCheckRequested(@LivenessRequestType int requestType) {
108         switch (mLivenessCheckRequested) {
109             case REQ_TYPE_INITIAL:
110                 if (requestType == REQ_TYPE_ON_DEMAND) {
111                     mLivenessCheckRequested = REQ_TYPE_ON_DEMAND;
112                     invokeUserCallback(IkeSessionCallback.LIVENESS_STATUS_ON_DEMAND_STARTED);
113                 } else if (requestType == REQ_TYPE_BACKGROUND) {
114                     mLivenessCheckRequested = REQ_TYPE_BACKGROUND;
115                     invokeUserCallback(IkeSessionCallback.LIVENESS_STATUS_BACKGROUND_STARTED);
116                 }
117                 break;
118             case REQ_TYPE_ON_DEMAND:
119                 invokeUserCallback(IkeSessionCallback.LIVENESS_STATUS_ON_DEMAND_ONGOING);
120                 break;
121             case REQ_TYPE_BACKGROUND:
122                 invokeUserCallback(IkeSessionCallback.LIVENESS_STATUS_BACKGROUND_ONGOING);
123                 break;
124         }
125     }
126 
127     /**
128      * Mark that the liveness check was successful.
129      *
130      * <p>and notifies a {@link IkeSessionCallback#LIVENESS_STATUS_SUCCESS}.
131      */
markPeerAsAlive()132     public void markPeerAsAlive() {
133         if (mLivenessCheckRequested == REQ_TYPE_INITIAL) {
134             return;
135         }
136 
137         mLivenessCheckRequested = REQ_TYPE_INITIAL;
138         invokeUserCallback(IkeSessionCallback.LIVENESS_STATUS_SUCCESS);
139     }
140 
141     /**
142      * Mark that the liveness check was failed.
143      *
144      * <p>and notifies a {@link IkeSessionCallback#LIVENESS_STATUS_FAILURE}.
145      */
markPeerAsDead()146     public void markPeerAsDead() {
147         if (mLivenessCheckRequested == REQ_TYPE_INITIAL) {
148             return;
149         }
150 
151         mLivenessCheckRequested = REQ_TYPE_INITIAL;
152         invokeUserCallback(IkeSessionCallback.LIVENESS_STATUS_FAILURE);
153     }
154 
155     /**
156      * Returns whether the request has been received and the results have not yet been reported.
157      *
158      * @return {@code true} if the liveness check has been requested.
159      */
isLivenessCheckRequested()160     public boolean isLivenessCheckRequested() {
161         return mLivenessCheckRequested != REQ_TYPE_INITIAL;
162     }
163 
164     /**
165      * Callbacks liveness status to clients.
166      *
167      * @param status {@link IkeSessionCallback.LivenessStatus} to be notified.
168      */
invokeUserCallback(@keSessionCallback.LivenessStatus int status)169     private void invokeUserCallback(@IkeSessionCallback.LivenessStatus int status) {
170         try {
171             mUserCbExecutor.execute(() -> mCallback.onLivenessStatusChanged(status));
172             mLivenessMetricHelper.recordLivenessStatus(status);
173         } catch (Exception e) {
174             getIkeLog().e(TAG, "onLivenessStatusChanged execution failed", e);
175         }
176     }
177 
178     /** Interface for receiving values that make up atoms */
179     public interface IIkeMetricsCallback {
180         /** Notifies that the liveness check has been completed. */
onLivenessCheckCompleted( int elapsedTimeInMillis, int numberOfOnGoing, boolean resultSuccess)181         void onLivenessCheckCompleted(
182                 int elapsedTimeInMillis, int numberOfOnGoing, boolean resultSuccess);
183     }
184 
185     private static class LivenessMetricHelper {
186 
187         /** To log metric information, call the function when ready to send it. */
188         private final IIkeMetricsCallback mMetricsCallback;
189 
190         private long mTimeInMillisStartedStatus;
191         private int mNumberOfOnGoing;
192 
LivenessMetricHelper(IIkeMetricsCallback metricsCallback)193         LivenessMetricHelper(IIkeMetricsCallback metricsCallback) {
194             clearVariables();
195             mMetricsCallback = metricsCallback;
196         }
197 
clearVariables()198         private void clearVariables() {
199             mTimeInMillisStartedStatus = 0L;
200             mNumberOfOnGoing = 0;
201         }
202 
recordLivenessStatus(@keSessionCallback.LivenessStatus int status)203         public void recordLivenessStatus(@IkeSessionCallback.LivenessStatus int status) {
204             switch (status) {
205                 case IkeSessionCallback.LIVENESS_STATUS_ON_DEMAND_STARTED: // fallthrough
206                 case IkeSessionCallback.LIVENESS_STATUS_BACKGROUND_STARTED:
207                     clearVariables();
208                     mTimeInMillisStartedStatus = System.currentTimeMillis();
209                     break;
210                 case IkeSessionCallback.LIVENESS_STATUS_ON_DEMAND_ONGOING: // fallthrough
211                 case IkeSessionCallback.LIVENESS_STATUS_BACKGROUND_ONGOING:
212                     mNumberOfOnGoing++;
213                     break;
214                 case IkeSessionCallback.LIVENESS_STATUS_SUCCESS:
215                     onLivenessCheckCompleted(true);
216                     break;
217                 case IkeSessionCallback.LIVENESS_STATUS_FAILURE:
218                     onLivenessCheckCompleted(false);
219                     break;
220             }
221         }
222 
onLivenessCheckCompleted(boolean resultSuccess)223         private void onLivenessCheckCompleted(boolean resultSuccess) {
224             long elapsedTimeInMillis = System.currentTimeMillis() - mTimeInMillisStartedStatus;
225             if (elapsedTimeInMillis < 0L || elapsedTimeInMillis > Integer.MAX_VALUE) {
226                 getIkeLog()
227                         .e(
228                                 TAG,
229                                 "onLivenessCheckCompleted, time exceeded failed. timeInMillies:"
230                                         + elapsedTimeInMillis);
231                 clearVariables();
232                 return;
233             }
234 
235             mMetricsCallback.onLivenessCheckCompleted(
236                     (int) elapsedTimeInMillis, mNumberOfOnGoing, resultSuccess);
237             clearVariables();
238         }
239     }
240 }
241