• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.squareup.okhttp.internal.spdy;
18 
19 import com.squareup.okhttp.internal.Util;
20 import java.io.ByteArrayOutputStream;
21 import java.io.Closeable;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.ServerSocket;
26 import java.net.Socket;
27 import java.util.ArrayList;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.concurrent.BlockingQueue;
31 import java.util.concurrent.Executor;
32 import java.util.concurrent.Executors;
33 import java.util.concurrent.LinkedBlockingQueue;
34 
35 import static java.util.concurrent.Executors.defaultThreadFactory;
36 
37 /** Replays prerecorded outgoing frames and records incoming frames. */
38 public final class MockSpdyPeer implements Closeable {
39   private int frameCount = 0;
40   private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
41   private final SpdyWriter spdyWriter = new SpdyWriter(bytesOut);
42   private final List<OutFrame> outFrames = new ArrayList<OutFrame>();
43   private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>();
44   private int port;
45   private final Executor executor = Executors.newCachedThreadPool(defaultThreadFactory());
46   private ServerSocket serverSocket;
47   private Socket socket;
48 
acceptFrame()49   public void acceptFrame() {
50     frameCount++;
51   }
52 
sendFrame()53   public SpdyWriter sendFrame() {
54     outFrames.add(new OutFrame(frameCount++, bytesOut.size(), Integer.MAX_VALUE));
55     return spdyWriter;
56   }
57 
58   /**
59    * Sends a frame, truncated to {@code truncateToLength} bytes. This is only
60    * useful for testing error handling as the truncated frame will be
61    * malformed.
62    */
sendTruncatedFrame(int truncateToLength)63   public SpdyWriter sendTruncatedFrame(int truncateToLength) {
64     outFrames.add(new OutFrame(frameCount++, bytesOut.size(), truncateToLength));
65     return spdyWriter;
66   }
67 
getPort()68   public int getPort() {
69     return port;
70   }
71 
takeFrame()72   public InFrame takeFrame() throws InterruptedException {
73     return inFrames.take();
74   }
75 
play()76   public void play() throws IOException {
77     if (serverSocket != null) throw new IllegalStateException();
78     serverSocket = new ServerSocket(0);
79     serverSocket.setReuseAddress(true);
80     this.port = serverSocket.getLocalPort();
81     executor.execute(new Runnable() {
82       @Override public void run() {
83         try {
84           readAndWriteFrames();
85         } catch (IOException e) {
86           throw new RuntimeException(e);
87         }
88       }
89     });
90   }
91 
readAndWriteFrames()92   private void readAndWriteFrames() throws IOException {
93     if (socket != null) throw new IllegalStateException();
94     socket = serverSocket.accept();
95     OutputStream out = socket.getOutputStream();
96     InputStream in = socket.getInputStream();
97     SpdyReader reader = new SpdyReader(in);
98 
99     Iterator<OutFrame> outFramesIterator = outFrames.iterator();
100     byte[] outBytes = bytesOut.toByteArray();
101     OutFrame nextOutFrame = null;
102 
103     for (int i = 0; i < frameCount; i++) {
104       if (nextOutFrame == null && outFramesIterator.hasNext()) {
105         nextOutFrame = outFramesIterator.next();
106       }
107 
108       if (nextOutFrame != null && nextOutFrame.sequence == i) {
109         int start = nextOutFrame.start;
110         int truncateToLength = nextOutFrame.truncateToLength;
111         int end;
112         if (outFramesIterator.hasNext()) {
113           nextOutFrame = outFramesIterator.next();
114           end = nextOutFrame.start;
115         } else {
116           end = outBytes.length;
117         }
118 
119         // write a frame
120         int length = Math.min(end - start, truncateToLength);
121         out.write(outBytes, start, length);
122       } else {
123         // read a frame
124         InFrame inFrame = new InFrame(i, reader);
125         reader.nextFrame(inFrame);
126         inFrames.add(inFrame);
127       }
128     }
129     Util.closeQuietly(socket);
130   }
131 
openSocket()132   public Socket openSocket() throws IOException {
133     return new Socket("localhost", port);
134   }
135 
close()136   @Override public void close() throws IOException {
137     Socket socket = this.socket;
138     if (socket != null) {
139       socket.close();
140       this.socket = null;
141     }
142     ServerSocket serverSocket = this.serverSocket;
143     if (serverSocket != null) {
144       serverSocket.close();
145       this.serverSocket = null;
146     }
147   }
148 
149   private static class OutFrame {
150     private final int sequence;
151     private final int start;
152     private final int truncateToLength;
153 
OutFrame(int sequence, int start, int truncateToLength)154     private OutFrame(int sequence, int start, int truncateToLength) {
155       this.sequence = sequence;
156       this.start = start;
157       this.truncateToLength = truncateToLength;
158     }
159   }
160 
161   public static class InFrame implements SpdyReader.Handler {
162     public final int sequence;
163     public final SpdyReader reader;
164     public int type = -1;
165     public int flags;
166     public int streamId;
167     public int associatedStreamId;
168     public int priority;
169     public int slot;
170     public int statusCode;
171     public int deltaWindowSize;
172     public List<String> nameValueBlock;
173     public byte[] data;
174     public Settings settings;
175 
InFrame(int sequence, SpdyReader reader)176     public InFrame(int sequence, SpdyReader reader) {
177       this.sequence = sequence;
178       this.reader = reader;
179     }
180 
settings(int flags, Settings settings)181     @Override public void settings(int flags, Settings settings) {
182       if (this.type != -1) throw new IllegalStateException();
183       this.type = SpdyConnection.TYPE_SETTINGS;
184       this.flags = flags;
185       this.settings = settings;
186     }
187 
188     @Override
synStream(int flags, int streamId, int associatedStreamId, int priority, int slot, List<String> nameValueBlock)189     public void synStream(int flags, int streamId, int associatedStreamId, int priority, int slot,
190         List<String> nameValueBlock) {
191       if (this.type != -1) throw new IllegalStateException();
192       this.type = SpdyConnection.TYPE_SYN_STREAM;
193       this.flags = flags;
194       this.streamId = streamId;
195       this.associatedStreamId = associatedStreamId;
196       this.priority = priority;
197       this.slot = slot;
198       this.nameValueBlock = nameValueBlock;
199     }
200 
synReply(int flags, int streamId, List<String> nameValueBlock)201     @Override public void synReply(int flags, int streamId, List<String> nameValueBlock) {
202       if (this.type != -1) throw new IllegalStateException();
203       this.type = SpdyConnection.TYPE_SYN_REPLY;
204       this.streamId = streamId;
205       this.flags = flags;
206       this.nameValueBlock = nameValueBlock;
207     }
208 
headers(int flags, int streamId, List<String> nameValueBlock)209     @Override public void headers(int flags, int streamId, List<String> nameValueBlock) {
210       if (this.type != -1) throw new IllegalStateException();
211       this.type = SpdyConnection.TYPE_HEADERS;
212       this.streamId = streamId;
213       this.flags = flags;
214       this.nameValueBlock = nameValueBlock;
215     }
216 
data(int flags, int streamId, InputStream in, int length)217     @Override public void data(int flags, int streamId, InputStream in, int length)
218         throws IOException {
219       if (this.type != -1) throw new IllegalStateException();
220       this.type = SpdyConnection.TYPE_DATA;
221       this.flags = flags;
222       this.streamId = streamId;
223       this.data = new byte[length];
224       Util.readFully(in, this.data);
225     }
226 
rstStream(int flags, int streamId, int statusCode)227     @Override public void rstStream(int flags, int streamId, int statusCode) {
228       if (this.type != -1) throw new IllegalStateException();
229       this.type = SpdyConnection.TYPE_RST_STREAM;
230       this.flags = flags;
231       this.streamId = streamId;
232       this.statusCode = statusCode;
233     }
234 
ping(int flags, int streamId)235     @Override public void ping(int flags, int streamId) {
236       if (this.type != -1) throw new IllegalStateException();
237       this.type = SpdyConnection.TYPE_PING;
238       this.flags = flags;
239       this.streamId = streamId;
240     }
241 
noop()242     @Override public void noop() {
243       if (this.type != -1) throw new IllegalStateException();
244       this.type = SpdyConnection.TYPE_NOOP;
245     }
246 
goAway(int flags, int lastGoodStreamId, int statusCode)247     @Override public void goAway(int flags, int lastGoodStreamId, int statusCode) {
248       if (this.type != -1) throw new IllegalStateException();
249       this.type = SpdyConnection.TYPE_GOAWAY;
250       this.flags = flags;
251       this.streamId = lastGoodStreamId;
252       this.statusCode = statusCode;
253     }
254 
windowUpdate(int flags, int streamId, int deltaWindowSize)255     @Override public void windowUpdate(int flags, int streamId, int deltaWindowSize) {
256       if (this.type != -1) throw new IllegalStateException();
257       this.type = SpdyConnection.TYPE_WINDOW_UPDATE;
258       this.flags = flags;
259       this.streamId = streamId;
260       this.deltaWindowSize = deltaWindowSize;
261     }
262   }
263 }