View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.testsuite.transport.socket;
17  
18  import io.netty.bootstrap.Bootstrap;
19  import io.netty.bootstrap.ServerBootstrap;
20  import io.netty.buffer.ByteBuf;
21  import io.netty.buffer.Unpooled;
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelInboundHandlerAdapter;
25  import io.netty.channel.ChannelInitializer;
26  import io.netty.channel.ChannelOption;
27  import io.netty.channel.SimpleChannelInboundHandler;
28  import io.netty.channel.socket.SocketChannel;
29  import io.netty.handler.codec.spdy.SpdyFrameCodec;
30  import io.netty.handler.codec.spdy.SpdyVersion;
31  import org.junit.jupiter.api.Test;
32  import org.junit.jupiter.api.TestInfo;
33  import org.junit.jupiter.api.Timeout;
34  
35  import java.io.IOException;
36  import java.util.Random;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.atomic.AtomicReference;
39  
40  import static org.junit.jupiter.api.Assertions.assertEquals;
41  
42  public class SocketSpdyEchoTest extends AbstractSocketTest {
43  
44      private static final Random random = new Random();
45      static final int ignoredBytes = 20;
46  
47      private static ByteBuf createFrames(int version) {
48          ByteBuf frames = Unpooled.buffer(1174);
49  
50          // SPDY UNKNOWN Frame
51          frames.writeByte(0x80);
52          frames.writeByte(version);
53          frames.writeShort(0xFFFF);
54          frames.writeByte(0xFF);
55          frames.writeMedium(4);
56          frames.writeInt(random.nextInt());
57  
58          // SPDY NOOP Frame
59          frames.writeByte(0x80);
60          frames.writeByte(version);
61          frames.writeShort(5);
62          frames.writeInt(0);
63  
64          // SPDY Data Frame
65          frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
66          frames.writeByte(0x01);
67          frames.writeMedium(1024);
68          for (int i = 0; i < 256; i ++) {
69              frames.writeInt(random.nextInt());
70          }
71  
72          // SPDY SYN_STREAM Frame
73          frames.writeByte(0x80);
74          frames.writeByte(version);
75          frames.writeShort(1);
76          frames.writeByte(0x03);
77          frames.writeMedium(10);
78          frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
79          frames.writeInt(random.nextInt() & 0x7FFFFFFF);
80          frames.writeShort(0x8000);
81          if (version < 3) {
82              frames.writeShort(0);
83          }
84  
85          // SPDY SYN_REPLY Frame
86          frames.writeByte(0x80);
87          frames.writeByte(version);
88          frames.writeShort(2);
89          frames.writeByte(0x01);
90          frames.writeMedium(4);
91          frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
92          if (version < 3) {
93              frames.writeInt(0);
94          }
95  
96          // SPDY RST_STREAM Frame
97          frames.writeByte(0x80);
98          frames.writeByte(version);
99          frames.writeShort(3);
100         frames.writeInt(8);
101         frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
102         frames.writeInt(random.nextInt() | 0x01);
103 
104         // SPDY SETTINGS Frame
105         frames.writeByte(0x80);
106         frames.writeByte(version);
107         frames.writeShort(4);
108         frames.writeByte(0x01);
109         frames.writeMedium(12);
110         frames.writeInt(1);
111         frames.writeByte(0x03);
112         frames.writeMedium(random.nextInt());
113         frames.writeInt(random.nextInt());
114 
115         // SPDY PING Frame
116         frames.writeByte(0x80);
117         frames.writeByte(version);
118         frames.writeShort(6);
119         frames.writeInt(4);
120         frames.writeInt(random.nextInt());
121 
122         // SPDY GOAWAY Frame
123         frames.writeByte(0x80);
124         frames.writeByte(version);
125         frames.writeShort(7);
126         frames.writeInt(8);
127         frames.writeInt(random.nextInt() & 0x7FFFFFFF);
128         frames.writeInt(random.nextInt() | 0x01);
129 
130         // SPDY HEADERS Frame
131         frames.writeByte(0x80);
132         frames.writeByte(version);
133         frames.writeShort(8);
134         frames.writeByte(0x01);
135         frames.writeMedium(4);
136         frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
137 
138         // SPDY WINDOW_UPDATE Frame
139         frames.writeByte(0x80);
140         frames.writeByte(version);
141         frames.writeShort(9);
142         frames.writeInt(8);
143         frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
144         frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01);
145 
146         return frames;
147     }
148 
149     @Test
150     @Timeout(value = 15000, unit = TimeUnit.MILLISECONDS)
151     public void testSpdyEcho(TestInfo testInfo) throws Throwable {
152         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
153             @Override
154             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
155                 testSpdyEcho(serverBootstrap, bootstrap);
156             }
157         });
158     }
159 
160     public void testSpdyEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable {
161         logger.info("Testing against SPDY v3.1");
162         testSpdyEcho(sb, cb, SpdyVersion.SPDY_3_1, true);
163     }
164 
165     @Test
166     @Timeout(value = 15000, unit = TimeUnit.MILLISECONDS)
167     public void testSpdyEchoNotAutoRead(TestInfo testInfo) throws Throwable {
168         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
169             @Override
170             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
171                 testSpdyEchoNotAutoRead(serverBootstrap, bootstrap);
172             }
173         });
174     }
175 
176     public void testSpdyEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable {
177         logger.info("Testing against SPDY v3.1");
178         testSpdyEcho(sb, cb, SpdyVersion.SPDY_3_1, false);
179     }
180 
181     private static void testSpdyEcho(
182             ServerBootstrap sb, Bootstrap cb, final SpdyVersion version, boolean autoRead) throws Throwable {
183 
184         ByteBuf frames;
185         switch (version) {
186         case SPDY_3_1:
187             frames = createFrames(3);
188             break;
189         default:
190             throw new IllegalArgumentException("unknown version");
191         }
192 
193         sb.childOption(ChannelOption.AUTO_READ, autoRead);
194         cb.option(ChannelOption.AUTO_READ, autoRead);
195 
196         final SpdyEchoTestServerHandler sh = new SpdyEchoTestServerHandler(autoRead);
197         final SpdyEchoTestClientHandler ch = new SpdyEchoTestClientHandler(frames.copy(), autoRead);
198 
199         sb.childHandler(new ChannelInitializer<SocketChannel>() {
200             @Override
201             public void initChannel(SocketChannel channel) throws Exception {
202                 channel.pipeline().addLast(
203                         new SpdyFrameCodec(version),
204                         sh);
205             }
206         });
207 
208         cb.handler(ch);
209 
210         Channel sc = sb.bind().sync().channel();
211 
212         Channel cc = cb.connect(sc.localAddress()).sync().channel();
213         cc.writeAndFlush(frames);
214 
215         while (ch.counter < frames.writerIndex() - ignoredBytes) {
216             if (sh.exception.get() != null) {
217                 break;
218             }
219             if (ch.exception.get() != null) {
220                 break;
221             }
222 
223             Thread.sleep(50);
224         }
225 
226         if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
227             throw sh.exception.get();
228         }
229         if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
230             throw ch.exception.get();
231         }
232         if (sh.exception.get() != null) {
233             throw sh.exception.get();
234         }
235         if (ch.exception.get() != null) {
236             throw ch.exception.get();
237         }
238     }
239 
240     private static class SpdyEchoTestServerHandler extends ChannelInboundHandlerAdapter {
241         private final boolean autoRead;
242         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
243 
244         SpdyEchoTestServerHandler(boolean autoRead) {
245             this.autoRead = autoRead;
246         }
247 
248         @Override
249         public void channelActive(ChannelHandlerContext ctx) throws Exception {
250             if (!autoRead) {
251                 ctx.read();
252             }
253         }
254 
255         @Override
256         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
257             ctx.write(msg);
258         }
259 
260         @Override
261         public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
262             try {
263                 ctx.flush();
264             } finally {
265                 if (!autoRead) {
266                     ctx.read();
267                 }
268             }
269         }
270 
271         @Override
272         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
273             if (exception.compareAndSet(null, cause)) {
274                 ctx.close();
275             }
276         }
277     }
278 
279     private static class SpdyEchoTestClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
280         private final boolean autoRead;
281         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
282         final ByteBuf frames;
283         volatile int counter;
284 
285         SpdyEchoTestClientHandler(ByteBuf frames, boolean autoRead) {
286             this.frames = frames;
287             this.autoRead = autoRead;
288         }
289         @Override
290         public void channelActive(ChannelHandlerContext ctx) throws Exception {
291             if (!autoRead) {
292                 ctx.read();
293             }
294         }
295 
296         @Override
297         public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
298             byte[] actual = new byte[in.readableBytes()];
299             in.readBytes(actual);
300 
301             int lastIdx = counter;
302             for (int i = 0; i < actual.length; i ++) {
303                 assertEquals(frames.getByte(ignoredBytes + i + lastIdx), actual[i]);
304             }
305 
306             counter += actual.length;
307         }
308 
309         @Override
310         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
311             if (exception.compareAndSet(null, cause)) {
312                 ctx.close();
313             }
314         }
315 
316         @Override
317         public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
318             if (!autoRead) {
319                 ctx.read();
320             }
321         }
322     }
323 }