package com.webpieces.http2engine.impl.shared; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import org.webpieces.data.api.DataWrapper; import org.webpieces.data.api.DataWrapperGenerator; import org.webpieces.data.api.DataWrapperGeneratorFactory; import org.webpieces.util.logging.Logger; import org.webpieces.util.logging.LoggerFactory; import com.webpieces.hpack.api.HpackParser; import com.webpieces.hpack.api.MarshalState; import com.webpieces.http2parser.api.Http2Exception; import com.webpieces.http2parser.api.ParseFailReason; import com.webpieces.http2parser.api.dto.GoAwayFrame; import com.webpieces.http2parser.api.dto.PingFrame; import com.webpieces.http2parser.api.dto.lib.Http2Msg; public class Level7MarshalAndPing { private static final Logger log = LoggerFactory.getLogger(Level7MarshalAndPing.class); private static final DataWrapperGenerator dataGen = DataWrapperGeneratorFactory.createDataWrapperGenerator(); private HpackParser parser; private EngineResultListener finalLayer; private HeaderSettings remoteSettings; private MarshalState marshalState; private AtomicReference<CompletableFuture<Void>> pingFutureRef; public Level7MarshalAndPing(HpackParser parser, HeaderSettings remoteSettings, EngineResultListener finalLayer) { this.parser = parser; this.remoteSettings = remoteSettings; this.finalLayer = finalLayer; this.remoteSettings = remoteSettings; marshalState = parser.prepareToMarshal(remoteSettings.getHeaderTableSize(), remoteSettings.getMaxFrameSize()); } public CompletableFuture<Void> sendControlFrameToClient(Http2Msg msg) { return finalLayer.sendControlFrameToClient(msg); } public CompletableFuture<Void> sendPing() { PingFrame ping = new PingFrame(); CompletableFuture<Void> newFuture = new CompletableFuture<>(); boolean wasSet = pingFutureRef.compareAndSet(null, newFuture); if(!wasSet) { throw new IllegalStateException("You must wait until the first ping you sent is complete. 2nd ping="+ping); } return sendFrameToSocket(ping) .thenCompose(c -> newFuture); } public CompletableFuture<Void> processPing(PingFrame ping) { if(!ping.isPingResponse()) { PingFrame pingAck = new PingFrame(); pingAck.setIsPingResponse(true); return sendFrameToSocket(pingAck); } CompletableFuture<Void> future = pingFutureRef.get(); if(future == null) throw new IllegalStateException("bug, this should not be possible"); pingFutureRef.compareAndSet(future, null); //clear the value future.complete(null); return CompletableFuture.completedFuture(null); } public void setEncoderMaxTableSize(int value) { remoteSettings.setHeaderTableSize(value); marshalState.setOutgoingMaxTableSize(value); } public CompletableFuture<Void> goAway(Http2Exception e) { ParseFailReason reason = e.getReason(); byte[] bytes = e.getMessage().getBytes(StandardCharsets.UTF_8); DataWrapper debug = dataGen.wrapByteArray(bytes); GoAwayFrame frame = new GoAwayFrame(); frame.setDebugData(debug); frame.setKnownErrorCode(reason.getErrorCode()); CompletableFuture<Void> future1 = sendControlDataToSocket(frame); finalLayer.closeSocket(e); return future1; } public CompletableFuture<Void> sendControlDataToSocket(Http2Msg msg) { int streamId = msg.getStreamId(); if(streamId != 0) throw new IllegalArgumentException("control frame is not stream 0. streamId="+streamId+" frame type="+msg.getClass()); return sendFrameToSocket(msg); } public CompletableFuture<Void> sendFrameToSocket(Http2Msg msg) { log.info("sending frame down to socket(from client)=\n"+msg); DataWrapper data = parser.marshal(marshalState, msg); ByteBuffer buffer = ByteBuffer.wrap(data.createByteArray()); return sendToSocket(buffer); } public CompletableFuture<Void> sendToSocket(ByteBuffer buffer) { return finalLayer.sendToSocket(buffer); } }