package com.webpieces.http2engine.impl.shared;
import java.util.concurrent.CompletableFuture;
import org.webpieces.javasm.api.Memento;
import org.webpieces.javasm.api.NoTransitionListener;
import org.webpieces.javasm.api.State;
import org.webpieces.javasm.api.StateMachine;
import org.webpieces.javasm.api.StateMachineFactory;
import com.webpieces.hpack.api.dto.Http2Headers;
import com.webpieces.hpack.api.dto.Http2Push;
import com.webpieces.http2engine.impl.shared.Http2Event.Http2SendRecieve;
import com.webpieces.http2parser.api.ConnectionException;
import com.webpieces.http2parser.api.ParseFailReason;
import com.webpieces.http2parser.api.dto.DataFrame;
import com.webpieces.http2parser.api.dto.RstStreamFrame;
import com.webpieces.http2parser.api.dto.lib.PartialStream;
public abstract class Level5AbstractStateMachine {
private Level6RemoteFlowControl remoteFlowControl;
private Level6LocalFlowControl localFlowControl;
protected StateMachine stateMachine;
protected State idleState;
protected State closed;
protected State openState;
public Level5AbstractStateMachine(String id, Level6RemoteFlowControl remoteFlowControl, Level6LocalFlowControl localFlowControl) {
this.remoteFlowControl = remoteFlowControl;
this.localFlowControl = localFlowControl;
StateMachineFactory factory = StateMachineFactory.createFactory();
stateMachine = factory.createStateMachine(id);
idleState = stateMachine.createState("idle");
openState = stateMachine.createState("Open");
closed = stateMachine.createState("closed");
}
public CompletableFuture<Void> fireToSocket(Stream stream, PartialStream payload) {
Memento state = stream.getCurrentState();
Http2Event event = translate(Http2SendRecieve.SEND, payload);
CompletableFuture<State> result = stateMachine.fireEvent(state, event);
return result.thenCompose( s ->
//if no exceptions occurred, send it on to flow control layer
remoteFlowControl.sendPayloadToSocket(stream, payload)
);
}
public CompletableFuture<State> fireToClient(Stream stream, PartialStream payload, Runnable possiblyClose) {
Memento currentState = stream.getCurrentState();
Http2Event event = translate(Http2SendRecieve.RECEIVE, payload);
CompletableFuture<State> result = stateMachine.fireEvent(currentState, event);
return result.thenApply( s -> {
//closing the stream should be done BEFORE firing to client as if the stream is closed
//then this will prevent windowUpdateFrame with increment being sent to a closed stream
if(possiblyClose != null)
possiblyClose.run();
localFlowControl.fireToClient(stream, payload);
return s;
}).exceptionally(t -> {
if(t instanceof NoTransitionException) {
throw new ConnectionException(ParseFailReason.BAD_FRAME_RECEIVED_FOR_THIS_STATE, stream.getStreamId(), t.getMessage(), t);
}
throw new RuntimeException(t);
});
}
public boolean isInClosedState(Stream stream) {
State currentState = stream.getCurrentState().getCurrentState();
if(currentState == closed)
return true;
return false;
}
public Memento createStateMachine(String streamId) {
return stateMachine.createMementoFromState("stream"+streamId, idleState);
}
protected Http2Event translate(Http2SendRecieve sendRecv, PartialStream payload) {
Http2PayloadType payloadType;
if(payload instanceof Http2Headers) {
if(payload.isEndOfStream())
payloadType = Http2PayloadType.HEADERS_WITH_EOS;
else
payloadType = Http2PayloadType.HEADERS;
} else if(payload instanceof DataFrame) {
if(payload.isEndOfStream())
payloadType = Http2PayloadType.DATA_WITH_EOS;
else
payloadType = Http2PayloadType.DATA;
} else if(payload instanceof Http2Push) {
payloadType = Http2PayloadType.PUSH_PROMISE;
} else if(payload instanceof RstStreamFrame) {
payloadType = Http2PayloadType.RESET_STREAM;
} else
throw new IllegalArgumentException("unknown payload type for payload="+payload);
return new Http2Event(sendRecv, payloadType);
}
protected static class NoTransitionImpl implements NoTransitionListener {
public NoTransitionImpl() {
}
@Override
public void noTransitionFromEvent(State state, Object event) {
throw new NoTransitionException("No transition defined on statemachine for event="+event+" when in state="+state);
}
}
}