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             try {
224                 Thread.sleep(50);
225             } catch (InterruptedException e) {
226                 // Ignore.
227             }
228         }
229 
230         if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
231             throw sh.exception.get();
232         }
233         if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
234             throw ch.exception.get();
235         }
236         if (sh.exception.get() != null) {
237             throw sh.exception.get();
238         }
239         if (ch.exception.get() != null) {
240             throw ch.exception.get();
241         }
242     }
243 
244     private static class SpdyEchoTestServerHandler extends ChannelInboundHandlerAdapter {
245         private final boolean autoRead;
246         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
247 
248         SpdyEchoTestServerHandler(boolean autoRead) {
249             this.autoRead = autoRead;
250         }
251 
252         @Override
253         public void channelActive(ChannelHandlerContext ctx) throws Exception {
254             if (!autoRead) {
255                 ctx.read();
256             }
257         }
258 
259         @Override
260         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
261             ctx.write(msg);
262         }
263 
264         @Override
265         public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
266             try {
267                 ctx.flush();
268             } finally {
269                 if (!autoRead) {
270                     ctx.read();
271                 }
272             }
273         }
274 
275         @Override
276         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
277             if (exception.compareAndSet(null, cause)) {
278                 ctx.close();
279             }
280         }
281     }
282 
283     private static class SpdyEchoTestClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
284         private final boolean autoRead;
285         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
286         final ByteBuf frames;
287         volatile int counter;
288 
289         SpdyEchoTestClientHandler(ByteBuf frames, boolean autoRead) {
290             this.frames = frames;
291             this.autoRead = autoRead;
292         }
293         @Override
294         public void channelActive(ChannelHandlerContext ctx) throws Exception {
295             if (!autoRead) {
296                 ctx.read();
297             }
298         }
299 
300         @Override
301         public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
302             byte[] actual = new byte[in.readableBytes()];
303             in.readBytes(actual);
304 
305             int lastIdx = counter;
306             for (int i = 0; i < actual.length; i ++) {
307                 assertEquals(frames.getByte(ignoredBytes + i + lastIdx), actual[i]);
308             }
309 
310             counter += actual.length;
311         }
312 
313         @Override
314         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
315             if (exception.compareAndSet(null, cause)) {
316                 ctx.close();
317             }
318         }
319 
320         @Override
321         public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
322             if (!autoRead) {
323                 ctx.read();
324             }
325         }
326     }
327 }