package org.webpieces.http2client.mock; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.xml.bind.DatatypeConverter; import org.webpieces.data.api.BufferCreationPool; import org.webpieces.data.api.BufferPool; import org.webpieces.data.api.DataWrapper; import org.webpieces.data.api.DataWrapperGenerator; import org.webpieces.data.api.DataWrapperGeneratorFactory; import org.webpieces.mock.MethodEnum; import org.webpieces.mock.MockSuperclass; import org.webpieces.mock.ParametersPassedIn; import org.webpieces.nio.api.channels.Channel; import org.webpieces.nio.api.channels.ChannelSession; import org.webpieces.nio.api.channels.TCPChannel; import org.webpieces.nio.api.handlers.DataListener; import com.webpieces.hpack.api.HpackParser; import com.webpieces.hpack.api.HpackParserFactory; import com.webpieces.hpack.api.MarshalState; import com.webpieces.hpack.api.UnmarshalState; import com.webpieces.http2parser.api.Http2Parser; import com.webpieces.http2parser.api.Http2ParserFactory; import com.webpieces.http2parser.api.dto.lib.Http2Frame; import com.webpieces.http2parser.api.dto.lib.Http2Msg; public class MockHttp2Channel extends MockSuperclass implements TCPChannel { private static final DataWrapperGenerator dataGen = DataWrapperGeneratorFactory.createDataWrapperGenerator(); private enum Method implements MethodEnum { INCOMING_FRAME } private boolean prefaceReceived; private HpackParser parser; private Http2Parser frameParser; private UnmarshalState unmarshalState; private boolean connected; private MarshalState marshalState; private DataListener listener; private boolean isClosed; public MockHttp2Channel() { BufferPool bufferPool = new BufferCreationPool(); parser = HpackParserFactory.createParser(bufferPool, false); unmarshalState = parser.prepareToUnmarshal(4096, 4096, 4096); BufferPool pool = new BufferCreationPool(); frameParser = Http2ParserFactory.createParser(pool); } @Override public CompletableFuture<Channel> connect(SocketAddress addr, DataListener listener) { if(connected) throw new IllegalStateException("already connected"); else if(listener == null) throw new IllegalArgumentException("listener can't be null"); connected = true; this.marshalState = parser.prepareToMarshal(4096, 4096); this.listener = listener; return CompletableFuture.completedFuture(this); } public void writeHexBack(String hex) { byte[] bytes = DatatypeConverter.parseHexBinary(hex.replaceAll("\\s+","")); ByteBuffer buf = ByteBuffer.wrap(bytes); listener.incomingData(this, buf); } public void write(Http2Msg msg) { if(listener == null) throw new IllegalStateException("Not connected so we cannot write back"); DataWrapper data = parser.marshal(marshalState, msg); byte[] bytes = data.createByteArray(); if(bytes.length == 0) throw new IllegalArgumentException("how do you marshal to 0 bytes...WTF"); ByteBuffer buf = ByteBuffer.wrap(bytes); listener.incomingData(this, buf); } public void writeFrame(Http2Frame frame) { DataWrapper data = frameParser.marshal(frame); byte[] bytes = data.createByteArray(); if(bytes.length == 0) throw new IllegalArgumentException("how do you marshal to 0 bytes...WTF"); ByteBuffer buf = ByteBuffer.wrap(bytes); listener.incomingData(this, buf); } @SuppressWarnings("unchecked") @Override public CompletableFuture<Channel> write(ByteBuffer b) { if(!prefaceReceived) { //copy and store preface ByteBuffer prefaceBuffer = ByteBuffer.allocate(b.remaining()); prefaceBuffer.put(b); prefaceReceived = true; Preface preface = new Preface(prefaceBuffer); List<Http2Msg> msgs = new ArrayList<>(); msgs.add(preface); return (CompletableFuture<Channel>) super.calledMethod(Method.INCOMING_FRAME, msgs); } return processData(b); } @SuppressWarnings("unchecked") private CompletableFuture<Channel> processData(ByteBuffer b) { DataWrapper data = dataGen.wrapByteBuffer(b); parser.unmarshal(unmarshalState, data); List<Http2Msg> parsedFrames = unmarshalState.getParsedFrames(); return (CompletableFuture<Channel>) super.calledMethod(Method.INCOMING_FRAME, parsedFrames); } public Http2Msg getFrameAndClear() { List<Http2Msg> msgs = getFramesAndClear(); if(msgs.size() != 1) throw new IllegalStateException("not correct number of responses. number="+msgs.size()+" but expected 1. list="+msgs); return msgs.get(0); } @SuppressWarnings("unchecked") public List<Http2Msg> getFramesAndClear() { Stream<ParametersPassedIn> calledMethodList = super.getCalledMethods(Method.INCOMING_FRAME); Stream<Http2Msg> retVal = calledMethodList.map(p -> (List<Http2Msg>)p.getArgs()[0]) .flatMap(Collection::stream); //clear out read values this.calledMethods.remove(Method.INCOMING_FRAME); return retVal.collect(Collectors.toList()); } public void setIncomingFrameDefaultReturnValue(CompletableFuture<Channel> future) { super.setDefaultReturnValue(Method.INCOMING_FRAME, future); } @Override public CompletableFuture<Channel> close() { isClosed = true; return null; } @Override public CompletableFuture<Channel> registerForReads() { // TODO Auto-generated method stub return null; } @Override public CompletableFuture<Channel> unregisterForReads() { // TODO Auto-generated method stub return null; } @Override public boolean isRegisteredForReads() { // TODO Auto-generated method stub return false; } @Override public InetSocketAddress getRemoteAddress() { // TODO Auto-generated method stub return null; } @Override public boolean isConnected() { // TODO Auto-generated method stub return false; } @Override public ChannelSession getSession() { // TODO Auto-generated method stub return null; } @Override public void setMaxBytesWriteBackupSize(int maxBytesBackup) { // TODO Auto-generated method stub } @Override public int getMaxBytesBackupSize() { // TODO Auto-generated method stub return 0; } @Override public boolean isSslChannel() { // TODO Auto-generated method stub return false; } @Override public void setReuseAddress(boolean b) { // TODO Auto-generated method stub } @Override public void setName(String string) { // TODO Auto-generated method stub } @Override public String getChannelId() { // TODO Auto-generated method stub return null; } @Override public String getName() { // TODO Auto-generated method stub return null; } @Override public void bind(SocketAddress addr) { // TODO Auto-generated method stub } @Override public boolean isBlocking() { // TODO Auto-generated method stub return false; } @Override public boolean isClosed() { return isClosed; } @Override public boolean isBound() { // TODO Auto-generated method stub return false; } @Override public InetSocketAddress getLocalAddress() { // TODO Auto-generated method stub return null; } @Override public boolean getKeepAlive() { // TODO Auto-generated method stub return false; } @Override public void setKeepAlive(boolean b) { // TODO Auto-generated method stub } public void assertNoIncomingMessages() { List<ParametersPassedIn> list = this.calledMethods.get(Method.INCOMING_FRAME); if(list == null) return; else if(list.size() != 0) throw new IllegalStateException("expected no method calls but method was called "+list.size()+" times. list="+list); } }