1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
59 frames.writeByte(0x80);
60 frames.writeByte(version);
61 frames.writeShort(5);
62 frames.writeInt(0);
63
64
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
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
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
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
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
116 frames.writeByte(0x80);
117 frames.writeByte(version);
118 frames.writeShort(6);
119 frames.writeInt(4);
120 frames.writeInt(random.nextInt());
121
122
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
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
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 }