blob: 81822d5c65e3df15b4b837d1cca7f0e8e156257e [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.ike.ikev2;
import android.os.Looper;
import android.os.Message;
import com.android.ike.ikev2.IkeSessionStateMachine.IChildSessionCallback;
import com.android.ike.ikev2.SaRecord.ChildSaRecord;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.message.IkePayload;
import com.android.ike.ikev2.message.IkeSaPayload;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.util.List;
/**
* ChildSessionStateMachine tracks states and manages exchanges of this Child Session.
*
* <p>ChildSessionStateMachine has two types of states. One type are states where there is no
* ongoing procedure affecting Child Session (non-procedure state), including Initial, Closed, Idle
* and Receiving. All other states are "procedure" states which are named as follows:
*
* <pre>
* State Name = [Procedure Type] + [Exchange Initiator] + [Exchange Type].
* - An IKE procedure consists of one or two IKE exchanges:
* Procedure Type = {CreateChild | DeleteChild | Info | RekeyChild | SimulRekeyChild}.
* - Exchange Initiator indicates whether local or remote peer is the exchange initiator:
* Exchange Initiator = {Local | Remote}
* - Exchange type defines the function of this exchange.
* Exchange Type = {Create | Delete}
* </pre>
*/
public class ChildSessionStateMachine extends StateMachine {
private static final String TAG = "ChildSessionStateMachine";
/** Receive request for negotiating first Child SA. */
private static final int CMD_HANDLE_FIRST_CHILD_EXCHANGE = 1;
private final ChildSessionOptions mChildSessionOptions;
/** Package private */
@VisibleForTesting ChildSaRecord mCurrentChildSaRecord;
private final State mInitial = new Initial();
private final State mClosed = new Closed();
private final State mIdle = new Idle();
/** Package private */
ChildSessionStateMachine(String name, Looper looper, ChildSessionOptions sessionOptions) {
super(name, looper);
mChildSessionOptions = sessionOptions;
addState(mInitial);
addState(mClosed);
addState(mIdle);
setInitialState(mInitial);
}
private void validateCreateChildResp(
List<IkePayload> reqPayloads, List<IkePayload> respPayloads) throws IkeException {
// TODO: Validate SA reponse against request and set negotiated SA in mChildSessionOptions.
return;
}
/**
* Receive requesting and responding payloads for negotiating first Child SA.
*
* <p>This method is called synchronously from IkeStateMachine. It proxies the synchronous call
* as an asynchronous job to the ChildStateMachine handler.
*
* @param reqPayloads SA negotiation related payloads in IKE_AUTH request.
* @param respPayloads SA negotiation related payloads in IKE_AUTH response.
* @param callback callback for notifying IkeSessionStateMachine the negotiation result.
*/
public void handleFirstChildExchange(
List<IkePayload> reqPayloads,
List<IkePayload> respPayloads,
IChildSessionCallback callback) {
registerProvisionalChildSession(respPayloads, callback);
sendMessage(
CMD_HANDLE_FIRST_CHILD_EXCHANGE,
new FirstChildNegotiationData(reqPayloads, respPayloads, callback));
}
/**
* Register provisioning ChildSessionStateMachine in IChildSessionCallback
*
* <p>This method is for avoiding CHILD_SA_NOT_FOUND error in IkeSessionStateMachine when remote
* peer sends request for delete/rekey this Child SA before ChildSessionStateMachine sends
* FirstChildNegotiationData to itself.
*/
private void registerProvisionalChildSession(
List<IkePayload> respPayloads, IChildSessionCallback callback) {
// When decoding responding IkeSaPayload in IkeSessionStateMachine, it is validated that
// IkeSaPayload has exactly one IkeSaPayload.Proposal.
IkeSaPayload saPayload = null;
for (IkePayload payload : respPayloads) {
if (payload.payloadType == IkePayload.PAYLOAD_TYPE_SA) {
saPayload = (IkeSaPayload) payload;
break;
}
}
if (saPayload == null) {
throw new IllegalArgumentException(
"Receive no SA payload for first Child SA negotiation.");
}
// IkeSaPayload.Proposal stores SPI in long type so as to be applied to both 8-byte IKE SPI
// and 4-byte Child SPI. Here we cast the stored SPI to int to represent a Child SPI.
int remoteGenSpi = (int) (saPayload.proposalList.get(0).spi);
callback.onCreateChildSa(remoteGenSpi, this);
}
/**
* FirstChildNegotiationData contains payloads for negotiating first Child SA in IKE_AUTH
* request and IKE_AUTH response and callback to notify IkeSessionStateMachine the SA
* negotiation result.
*/
private static class FirstChildNegotiationData {
public final List<IkePayload> requestPayloads;
public final List<IkePayload> responsePayloads;
public final IChildSessionCallback childCallback;
FirstChildNegotiationData(
List<IkePayload> reqPayloads,
List<IkePayload> respPayloads,
IChildSessionCallback callback) {
requestPayloads = reqPayloads;
responsePayloads = respPayloads;
childCallback = callback;
}
}
/** Initial state of ChildSessionStateMachine. */
class Initial extends State {
@Override
public boolean processMessage(Message message) {
switch (message.what) {
// TODO: Handle local request for creating Child SA.
case CMD_HANDLE_FIRST_CHILD_EXCHANGE:
FirstChildNegotiationData childNegotiationData =
(FirstChildNegotiationData) message.obj;
try {
List<IkePayload> reqPayloads = childNegotiationData.requestPayloads;
List<IkePayload> respPayloads = childNegotiationData.responsePayloads;
validateCreateChildResp(reqPayloads, respPayloads);
mCurrentChildSaRecord =
ChildSaRecord.makeChildSaRecord(reqPayloads, respPayloads);
// TODO: Add mCurrentChildSaRecord in mSpiToSaRecordMap.
transitionTo(mIdle);
} catch (IkeException e) {
// TODO: Unregister remotely generated SPI and handle Child SA negotiation
// failure.
}
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
/**
* Closed represents the state when this ChildSessionStateMachine is closed, and no further
* actions can be performed on it.
*/
class Closed extends State {
// TODO: Implement it.
}
/**
* Idle represents a state when there is no ongoing IKE exchange affecting established Child SA.
*/
class Idle extends State {
// TODO: Implement it.
}
// TODO: Add states to support creating additional Child SA, deleting Child SA and rekeying
// Child SA.
}