package com.webpieces.http2engine.impl.shared;
import java.util.concurrent.CompletableFuture;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;
import com.webpieces.http2parser.api.ConnectionException;
import com.webpieces.http2parser.api.ParseFailReason;
import com.webpieces.http2parser.api.StreamException;
import com.webpieces.http2parser.api.dto.DataFrame;
import com.webpieces.http2parser.api.dto.WindowUpdateFrame;
import com.webpieces.http2parser.api.dto.lib.PartialStream;
public class Level6LocalFlowControl {
private static final Logger log = LoggerFactory.getLogger(Level6LocalFlowControl.class);
private Level7MarshalAndPing marshalLayer;
private long connectionLocalWindowSize;
private long totalSent = 0;
private long totalRecovered = 0;
private EngineResultListener notifyListener;
public Level6LocalFlowControl(
Level7MarshalAndPing marshalLayer,
EngineResultListener notifyListener,
HeaderSettings localSettings
) {
this.marshalLayer = marshalLayer;
this.notifyListener = notifyListener;
this.connectionLocalWindowSize = localSettings.getInitialWindowSize();
}
public CompletableFuture<Void> fireToClient(Stream stream, PartialStream payload) {
if(!(payload instanceof DataFrame)) {
return notifyListener.sendPieceToApp(stream, payload);
}
DataFrame f = (DataFrame) payload;
long frameLength = f.getTransmitFrameLength();
if(frameLength > connectionLocalWindowSize) {
throw new ConnectionException(ParseFailReason.FLOW_CONTROL_ERROR, f.getStreamId(),
"connectionLocalWindowSize too small="+connectionLocalWindowSize
+" frame len="+frameLength+" for frame="+f);
} else if(frameLength > stream.getLocalWindowSize()) {
throw new StreamException(ParseFailReason.FLOW_CONTROL_ERROR, f.getStreamId(),
"connectionLocalWindowSize too small="+connectionLocalWindowSize
+" frame len="+frameLength+" for frame="+f);
}
totalSent += frameLength;
connectionLocalWindowSize -= frameLength;
stream.incrementLocalWindow(-frameLength);
log.info("received framelen="+frameLength+" newConnectionWindowSize="
+connectionLocalWindowSize+" streamSize="+stream.getLocalWindowSize()+" totalSent="+totalSent);
return notifyListener.sendPieceToApp(stream, payload)
.thenApply(c -> updateFlowControl(frameLength, stream));
}
private Void updateFlowControl(long frameLength, Stream stream) {
if(frameLength == 0)
return null; //nothing to do if it is a 0 length frame.
//TODO: we could optimize this to send very large window updates and send less window updates instead of
//what we do currently sending many increase window by 13 byte updates and such.
connectionLocalWindowSize += frameLength;
stream.incrementLocalWindow(frameLength);
totalRecovered += frameLength;
int len = (int) frameLength;
WindowUpdateFrame w1 = new WindowUpdateFrame();
w1.setStreamId(0);
w1.setWindowSizeIncrement(len);
marshalLayer.sendFrameToSocket(w1);
if(!stream.isClosed()) {
//IF the stream is not closed, update flow control
WindowUpdateFrame w2 = new WindowUpdateFrame();
w2.setStreamId(stream.getStreamId());
w2.setWindowSizeIncrement(len);
log.info("sending BOTH WUF increments. framelen="+frameLength+" recovered="+totalRecovered );
marshalLayer.sendFrameToSocket(w2);
} else {
log.info("sending WUF increments. framelen="+frameLength+" recovered="+totalRecovered);
}
return null;
}
}