blob: 3cfcb94485607b6ff31aa092d7944d6ce246d85f [file] [log] [blame]
/*
* Copyright (C) 2019 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 com.android.internal.net.ipsec.ike;
import static android.net.ipsec.ike.IkeManager.getIkeLog;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD;
import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD;
import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD;
import static com.android.internal.net.ipsec.ike.AbstractSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE;
import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE;
import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE;
import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_DPD;
import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_INFO;
import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_MOBIKE;
import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE;
import android.annotation.IntDef;
import android.content.Context;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionParams;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* IkeLocalRequestScheduler caches all local requests scheduled by an IKE Session and notify the IKE
* Session to process the request when it is allowed.
*
* <p>LocalRequestScheduler is running on the IkeSessionStateMachine thread.
*/
public final class IkeLocalRequestScheduler {
private static final String TAG = "IkeLocalRequestScheduler";
@VisibleForTesting static final String LOCAL_REQUEST_WAKE_LOCK_TAG = "LocalRequestWakeLock";
private static final int DEFAULT_REQUEST_QUEUE_SIZE = 1;
private static final int REQUEST_ID_NOT_ASSIGNED = -1;
// Local request that must be handled immediately. Ex: CMD_LOCAL_REQUEST_DELETE_IKE
@VisibleForTesting static final int REQUEST_PRIORITY_URGENT = 0;
// Local request that must be handled soon, but not necessarily immediately.
// Ex: CMD_LOCAL_REQUEST_MOBIKE
@VisibleForTesting static final int REQUEST_PRIORITY_HIGH = 1;
// Local request that should be handled once nothing more urgent requires handling. Most
// LocalRequests will have this priority.
@VisibleForTesting static final int REQUEST_PRIORITY_NORMAL = 2;
// Local request that has an unknown priority. This shouldn't happen in normal processing.
@VisibleForTesting static final int REQUEST_PRIORITY_UNKNOWN = Integer.MAX_VALUE;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
REQUEST_PRIORITY_URGENT,
REQUEST_PRIORITY_HIGH,
REQUEST_PRIORITY_NORMAL,
REQUEST_PRIORITY_UNKNOWN
})
@interface RequestPriority {}
public static int SPI_NOT_INCLUDED = 0;
private final PowerManager mPowerManager;
private final PriorityQueue<LocalRequest> mRequestQueue =
new PriorityQueue<>(DEFAULT_REQUEST_QUEUE_SIZE, new LocalRequestComparator());
private final IProcedureConsumer mConsumer;
private int mNextRequestId;
/**
* Construct an instance of IkeLocalRequestScheduler
*
* @param consumer the interface to initiate new procedure.
*/
public IkeLocalRequestScheduler(IProcedureConsumer consumer, Context context) {
mConsumer = consumer;
mPowerManager = context.getSystemService(PowerManager.class);
mNextRequestId = 0;
}
/** Add a new local request to the queue. */
public void addRequest(LocalRequest request) {
request.acquireWakeLock(mPowerManager);
request.setRequestId(mNextRequestId++);
mRequestQueue.offer(request);
}
/**
* Notifies the scheduler that the caller is ready for a new procedure
*
* <p>Synchronously triggers the call to onNewProcedureReady.
*
* @return whether or not a new procedure was scheduled.
*/
public boolean readyForNextProcedure() {
if (!mRequestQueue.isEmpty()) {
mConsumer.onNewProcedureReady(mRequestQueue.poll());
return true;
}
return false;
}
/** Release WakeLocks of all LocalRequests in the queue */
public void releaseAllLocalRequestWakeLocks() {
for (LocalRequest req : mRequestQueue) {
req.releaseWakeLock();
}
mRequestQueue.clear();
}
/**
* This class represents the common information of procedures that will be locally initiated.
*/
public abstract static class LocalRequest {
public final int procedureType;
// Priority of this LocalRequest. Note that a lower 'priority' means higher urgency.
@RequestPriority private final int mPriority;
// ID used to preserve insertion-order between requests in IkeLocalRequestScheduler with the
// same priority. Set when the LocalRequest is added to the IkeLocalRequestScheduler.
private int mRequestId = REQUEST_ID_NOT_ASSIGNED;
private WakeLock mWakeLock;
LocalRequest(int type, int priority) {
validateTypeOrThrow(type);
procedureType = type;
mPriority = priority;
}
@VisibleForTesting
int getPriority() {
return mPriority;
}
private void setRequestId(int requestId) {
mRequestId = requestId;
}
@VisibleForTesting
int getRequestId() {
return mRequestId;
}
/**
* Acquire a WakeLock for the LocalRequest.
*
* <p>This method will only be called from IkeLocalRequestScheduler#addRequest or
* IkeLocalRequestScheduler#addRequestAtFront
*/
private void acquireWakeLock(PowerManager powerManager) {
if (mWakeLock != null && mWakeLock.isHeld()) {
getIkeLog().wtf(TAG, "This LocalRequest already acquired a WakeLock");
return;
}
mWakeLock =
powerManager.newWakeLock(
PARTIAL_WAKE_LOCK,
TAG + LOCAL_REQUEST_WAKE_LOCK_TAG + "_" + procedureType);
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
}
/** Release WakeLock of the LocalRequest */
public void releaseWakeLock() {
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
}
protected abstract void validateTypeOrThrow(int type);
protected abstract boolean isChildRequest();
}
/** LocalRequestComparator is a comparator for comparing LocalRequest instances. */
private class LocalRequestComparator implements Comparator<LocalRequest> {
@Override
public int compare(LocalRequest requestA, LocalRequest requestB) {
int relativePriorities =
Integer.compare(requestA.getPriority(), requestB.getPriority());
if (relativePriorities != 0) return relativePriorities;
return Integer.compare(requestA.getRequestId(), requestB.getRequestId());
}
}
/**
* This class represents a user requested or internally scheduled IKE procedure that will be
* initiated locally.
*/
public static class IkeLocalRequest extends LocalRequest {
public long remoteSpi;
/** Schedule a request for an IKE SA that is identified by the remoteIkeSpi */
private IkeLocalRequest(int type, long remoteIkeSpi, int priority) {
super(type, priority);
remoteSpi = remoteIkeSpi;
}
@Override
protected void validateTypeOrThrow(int type) {
if (type >= CMD_LOCAL_REQUEST_CREATE_IKE && type <= CMD_LOCAL_REQUEST_MOBIKE) return;
throw new IllegalArgumentException("Invalid IKE procedure type: " + type);
}
@Override
protected boolean isChildRequest() {
return false;
}
}
/**
* This class represents a user requested or internally scheduled Child procedure that will be
* initiated locally.
*/
public static class ChildLocalRequest extends LocalRequest {
public int remoteSpi;
public final ChildSessionCallback childSessionCallback;
public final ChildSessionParams childSessionParams;
private ChildLocalRequest(
int type,
int remoteChildSpi,
ChildSessionCallback childCallback,
ChildSessionParams childParams,
int priority) {
super(type, priority);
childSessionParams = childParams;
childSessionCallback = childCallback;
remoteSpi = remoteChildSpi;
}
@Override
protected void validateTypeOrThrow(int type) {
if (type >= CMD_LOCAL_REQUEST_CREATE_CHILD
&& type <= CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE) {
return;
}
throw new IllegalArgumentException("Invalid Child procedure type: " + type);
}
@Override
protected boolean isChildRequest() {
return true;
}
}
/** Interface to initiate a new IKE procedure */
public interface IProcedureConsumer {
/**
* Called when a new IKE procedure can be initiated.
*
* @param localRequest the request to be initiated.
*/
void onNewProcedureReady(LocalRequest localRequest);
}
/** package-protected */
static class LocalRequestFactory {
/** Create a request for the IKE Session */
IkeLocalRequest getIkeLocalRequest(int type) {
return getIkeLocalRequest(type, SPI_NOT_INCLUDED);
}
/** Create a request for an IKE SA that is identified by the remoteIkeSpi */
IkeLocalRequest getIkeLocalRequest(int type, long remoteIkeSpi) {
return new IkeLocalRequest(type, remoteIkeSpi, procedureTypeToPriority(type));
}
/** Create a request for a Child Session that is identified by the childCallback */
ChildLocalRequest getChildLocalRequest(
int type, ChildSessionCallback childCallback, ChildSessionParams childParams) {
return new ChildLocalRequest(
type,
SPI_NOT_INCLUDED,
childCallback,
childParams,
procedureTypeToPriority(type));
}
/** Create a request for a Child SA that is identified by the remoteChildSpi */
ChildLocalRequest getChildLocalRequest(int type, int remoteChildSpi) {
return new ChildLocalRequest(
type,
remoteChildSpi,
null /*childCallback*/,
null /*childParams*/,
procedureTypeToPriority(type));
}
/** Returns the request priority for the specified procedure type. */
@VisibleForTesting
@RequestPriority
static int procedureTypeToPriority(int procedureType) {
switch (procedureType) {
case CMD_LOCAL_REQUEST_DELETE_IKE:
return REQUEST_PRIORITY_URGENT;
case CMD_LOCAL_REQUEST_MOBIKE:
case CMD_LOCAL_REQUEST_REKEY_CHILD_MOBIKE:
return REQUEST_PRIORITY_HIGH;
case CMD_LOCAL_REQUEST_CREATE_IKE: // Fallthrough
case CMD_LOCAL_REQUEST_REKEY_IKE: // Fallthrough
case CMD_LOCAL_REQUEST_INFO: // Fallthrough
case CMD_LOCAL_REQUEST_DPD: // Fallthrough
case CMD_LOCAL_REQUEST_CREATE_CHILD: // Fallthrough
case CMD_LOCAL_REQUEST_DELETE_CHILD: // Fallthrough
case CMD_LOCAL_REQUEST_REKEY_CHILD:
return REQUEST_PRIORITY_NORMAL;
default:
// unknown procedure type - assign it the lowest priority
getIkeLog().wtf(TAG, "Unknown procedureType: " + procedureType);
return REQUEST_PRIORITY_UNKNOWN;
}
}
}
}