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 }