package org.eclipse.jetty.spdy; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.Handler; import org.eclipse.jetty.spdy.api.RstInfo; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.SessionFrameListener; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.spdy.api.StreamFrameListener; import org.eclipse.jetty.spdy.api.StreamStatus; import org.eclipse.jetty.spdy.api.StringDataInfo; import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; import org.junit.Test; public class ResetStreamTest extends AbstractTest { @Test public void testResetStreamIsRemoved() throws Exception { Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()),null); Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS); session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM)).get(5,TimeUnit.SECONDS); assertEquals("session expected to contain 0 streams",0,session.getStreams().size()); } @Test public void testRefusedStreamIsRemoved() throws Exception { final AtomicReference<Session> serverSessionRef = new AtomicReference<>(); final CountDownLatch synLatch = new CountDownLatch(1); final CountDownLatch rstLatch = new CountDownLatch(1); Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { Session serverSession = stream.getSession(); serverSessionRef.set(serverSession); serverSession.rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM)); synLatch.countDown(); return null; } }),new SessionFrameListener.Adapter() { @Override public void onRst(Session session, RstInfo rstInfo) { rstLatch.countDown(); } }); Stream stream = clientSession.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS); assertTrue("syncLatch didn't count down",synLatch.await(5,TimeUnit.SECONDS)); Session serverSession = serverSessionRef.get(); assertEquals("serverSession expected to contain 0 streams",0,serverSession.getStreams().size()); assertTrue("rstLatch didn't count down",rstLatch.await(5,TimeUnit.SECONDS)); // Need to sleep a while to give the chance to the implementation to remove the stream TimeUnit.SECONDS.sleep(1); assertTrue("stream is expected to be reset",stream.isReset()); assertEquals("clientSession expected to contain 0 streams",0,clientSession.getStreams().size()); } @Test public void testRefusedStreamIgnoresData() throws Exception { final CountDownLatch synLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); final CountDownLatch rstLatch = new CountDownLatch(1); Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { try { // Refuse the stream, we must ignore data frames assertTrue(synLatch.await(5,TimeUnit.SECONDS)); stream.getSession().rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM)); return new StreamFrameListener.Adapter() { @Override public void onData(Stream stream, DataInfo dataInfo) { dataLatch.countDown(); } }; } catch (InterruptedException x) { x.printStackTrace(); return null; } } }),new SessionFrameListener.Adapter() { @Override public void onRst(Session session, RstInfo rstInfo) { rstLatch.countDown(); } }); Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS); stream.data(new StringDataInfo("data",true),5,TimeUnit.SECONDS,new Handler.Adapter<Void>() { @Override public void completed(Void context) { synLatch.countDown(); } }); assertTrue("rstLatch didn't count down",rstLatch.await(5,TimeUnit.SECONDS)); assertTrue("stream is expected to be reset",stream.isReset()); assertFalse("dataLatch shouln't be count down",dataLatch.await(1,TimeUnit.SECONDS)); } @Test public void testResetAfterServerReceivedFirstDataFrameAndSecondDataFrameFails() throws Exception { final CountDownLatch synLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); final CountDownLatch rstLatch = new CountDownLatch(1); final CountDownLatch failLatch = new CountDownLatch(1); Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { synLatch.countDown(); return new StreamFrameListener.Adapter() { @Override public void onData(Stream stream, DataInfo dataInfo) { dataLatch.countDown(); stream.getSession().rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM)); } }; } }),new SessionFrameListener.Adapter() { @Override public void onRst(Session session, RstInfo rstInfo) { rstLatch.countDown(); } }); Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS); assertThat("syn is received by server", synLatch.await(5,TimeUnit.SECONDS),is(true)); stream.data(new StringDataInfo("data",false),5,TimeUnit.SECONDS,null); assertThat("stream is reset",rstLatch.await(5,TimeUnit.SECONDS),is(true)); stream.data(new StringDataInfo("2nd dataframe",false),5L,TimeUnit.SECONDS,new Handler.Adapter<Void>() { @Override public void failed(Throwable x) { failLatch.countDown(); } }); assertThat("2nd data call failed",failLatch.await(5,TimeUnit.SECONDS),is(true)); assertThat("stream is reset",stream.isReset(),is(true)); } // TODO: If server already received 2nd dataframe after it rst, it should ignore it. Not easy to do. }