Upgrade to the latest OkHttp.

Note the internal package name has changed from libcore.net.http
to com.squareup.okhttp.

Change-Id: Ib0eb4bdd69f24f2a255975460bd001e59657ddcc
diff --git a/Android.mk b/Android.mk
index a942b67..802757d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -15,18 +15,25 @@
 #
 LOCAL_PATH := $(call my-dir)
 
+okhttp_src_files := $(call all-java-files-under,src/main/java)
+okhttp_src_files := $(filter-out %/Platform.java, $(okhttp_src_files))
+okhttp_src_files += $(call all-java-files-under, android/main/java)
+
 include $(CLEAR_VARS)
 LOCAL_MODULE := okhttp
 LOCAL_MODULE_TAGS := optional
-
-# Include all files under our snapshot except for Libcore.java
-# because it contains OpenJDK dependencies. This file is replaced
-# by an android specific version, see below.
-LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
-LOCAL_SRC_FILES := $(filter-out %/Libcore.java, $(LOCAL_SRC_FILES))
-
-LOCAL_SRC_FILES += $(call all-java-files-under, android/main/java)
-LOCAL_SDK_VERSION := 16
-
-LOCAL_JARJAR_RULES := ${LOCAL_PATH}/jarjar-rules.txt
+LOCAL_SRC_FILES := $(okhttp_src_files)
+LOCAL_JAVACFLAGS := -encoding UTF-8
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
 include $(BUILD_JAVA_LIBRARY)
+
+ifeq ($(WITH_HOST_DALVIK),true)
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := okhttp-hostdex
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_SRC_FILES := $(okhttp_src_files)
+    LOCAL_JAVACFLAGS := -encoding UTF-8
+    LOCAL_BUILD_HOST_DEX := true
+    LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+    include $(BUILD_HOST_JAVA_LIBRARY)
+endif
diff --git a/src/main/java/libcore/net/http/HttpsHandler.java b/android/main/java/com/squareup/okhttp/HttpHandler.java
similarity index 84%
copy from src/main/java/libcore/net/http/HttpsHandler.java
copy to android/main/java/com/squareup/okhttp/HttpHandler.java
index ed9ba72..c960160 100644
--- a/src/main/java/libcore/net/http/HttpsHandler.java
+++ b/android/main/java/com/squareup/okhttp/HttpHandler.java
@@ -15,7 +15,7 @@
  *  limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp;
 
 import java.io.IOException;
 import java.net.Proxy;
@@ -23,20 +23,19 @@
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
 
-public final class HttpsHandler extends URLStreamHandler {
-
+public final class HttpHandler extends URLStreamHandler {
     @Override protected URLConnection openConnection(URL url) throws IOException {
-        return new HttpsURLConnectionImpl(url, getDefaultPort());
+        return new OkHttpClient().open(url);
     }
 
     @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
         if (url == null || proxy == null) {
             throw new IllegalArgumentException("url == null || proxy == null");
         }
-        return new HttpsURLConnectionImpl(url, getDefaultPort(), proxy);
+        return new OkHttpClient().setProxy(proxy).open(url);
     }
 
     @Override protected int getDefaultPort() {
-        return 443;
+        return 80;
     }
 }
diff --git a/src/main/java/libcore/net/http/HttpsHandler.java b/android/main/java/com/squareup/okhttp/HttpsHandler.java
similarity index 89%
rename from src/main/java/libcore/net/http/HttpsHandler.java
rename to android/main/java/com/squareup/okhttp/HttpsHandler.java
index ed9ba72..1dc3826 100644
--- a/src/main/java/libcore/net/http/HttpsHandler.java
+++ b/android/main/java/com/squareup/okhttp/HttpsHandler.java
@@ -15,7 +15,7 @@
  *  limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp;
 
 import java.io.IOException;
 import java.net.Proxy;
@@ -24,16 +24,15 @@
 import java.net.URLStreamHandler;
 
 public final class HttpsHandler extends URLStreamHandler {
-
     @Override protected URLConnection openConnection(URL url) throws IOException {
-        return new HttpsURLConnectionImpl(url, getDefaultPort());
+        return new OkHttpClient().open(url);
     }
 
     @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
         if (url == null || proxy == null) {
             throw new IllegalArgumentException("url == null || proxy == null");
         }
-        return new HttpsURLConnectionImpl(url, getDefaultPort(), proxy);
+        return new OkHttpClient().setProxy(proxy).open(url);
     }
 
     @Override protected int getDefaultPort() {
diff --git a/android/main/java/com/squareup/okhttp/internal/Platform.java b/android/main/java/com/squareup/okhttp/internal/Platform.java
new file mode 100644
index 0000000..7479de0
--- /dev/null
+++ b/android/main/java/com/squareup/okhttp/internal/Platform.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal;
+
+import dalvik.system.SocketTagger;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import javax.net.ssl.SSLSocket;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
+
+/**
+ * Access to proprietary Android APIs. Doesn't use reflection.
+ */
+public final class Platform {
+    private static final Platform PLATFORM = new Platform();
+
+    public static Platform get() {
+        return PLATFORM;
+    }
+
+    public void logW(String warning) {
+        System.logW(warning);
+    }
+
+    public void tagSocket(Socket socket) throws SocketException {
+        SocketTagger.get().tag(socket);
+    }
+
+    public void untagSocket(Socket socket) throws SocketException {
+        SocketTagger.get().untag(socket);
+    }
+
+    public URI toUriLenient(URL url) throws URISyntaxException {
+        return url.toURILenient();
+    }
+
+    public void enableTlsExtensions(SSLSocket socket, String uriHost) {
+        if (socket instanceof OpenSSLSocketImpl) {
+            OpenSSLSocketImpl openSSLSocket = (OpenSSLSocketImpl) socket;
+            openSSLSocket.setUseSessionTickets(true);
+            openSSLSocket.setHostname(uriHost);
+        }
+    }
+
+    public void supportTlsIntolerantServer(SSLSocket socket) {
+        socket.setEnabledProtocols(new String[]{"SSLv3"});
+    }
+
+    /**
+     * Returns the negotiated protocol, or null if no protocol was negotiated.
+     */
+    public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+        return socket instanceof OpenSSLSocketImpl
+                ? ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol()
+                : null;
+    }
+
+    /**
+     * Sets client-supported protocols on a socket to send to a server. The
+     * protocols are only sent if the socket implementation supports NPN.
+     */
+    public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+        if (socket instanceof OpenSSLSocketImpl) {
+            ((OpenSSLSocketImpl) socket).setNpnProtocols(npnProtocols);
+        }
+    }
+
+    /**
+     * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
+     * value blocks. This throws an {@link UnsupportedOperationException} on
+     * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH.
+     */
+    public OutputStream newDeflaterOutputStream(
+            OutputStream out, Deflater deflater, boolean syncFlush) {
+        return new DeflaterOutputStream(out, deflater, syncFlush);
+    }
+}
diff --git a/android/main/java/libcore/util/Libcore.java b/android/main/java/libcore/util/Libcore.java
deleted file mode 100644
index c1cf069..0000000
--- a/android/main/java/libcore/util/Libcore.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import javax.net.ssl.SSLSocket;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.ByteOrder;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-
-/**
- * APIs for interacting with Android's core library. The main purpose of this
- * class is to access hidden methods on
- *
- * org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl
- *
- * via reflection.
- */
-public final class Libcore {
-
-    private Libcore() {
-    }
-
-    private static final Class<?> openSslSocketClass;
-    private static final Method setUseSessionTickets;
-    private static final Method setHostname;
-    private static final Method setNpnProtocols;
-    private static final Method getNpnSelectedProtocol;
-    private static final Constructor<DeflaterOutputStream> deflaterOutputStreamConstructor;
-
-    static {
-        try {
-            openSslSocketClass = Class.forName(
-                    "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
-            setUseSessionTickets = openSslSocketClass.getMethod(
-                    "setUseSessionTickets", boolean.class);
-            setHostname = openSslSocketClass.getMethod("setHostname", String.class);
-            setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
-            getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
-            deflaterOutputStreamConstructor = DeflaterOutputStream.class.getConstructor(
-                   new Class[] { OutputStream.class, Deflater.class, boolean.class });
-        } catch (ClassNotFoundException cnfe) {
-            throw new RuntimeException(cnfe);
-        } catch (NoSuchMethodException nsme) {
-            throw new RuntimeException(nsme);
-        }
-    }
-
-    public static DeflaterOutputStream newDeflaterOutputStream(
-            OutputStream os, Deflater deflater, boolean syncFlush) {
-        try {
-            return deflaterOutputStreamConstructor.newInstance(os, deflater, syncFlush);
-        } catch (InstantiationException e) {
-            throw new RuntimeException("Unknown DeflaterOutputStream implementation.");
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Unknown DeflaterOutputStream implementation.");
-        } catch (InvocationTargetException e) {
-            throw new RuntimeException("Unknown DeflaterOutputStream implementation.");
-        }
-    }
-
-    public static void makeTlsTolerant(SSLSocket socket, String socketHost, boolean tlsTolerant) {
-        if (!tlsTolerant) {
-            socket.setEnabledProtocols(new String[] {"SSLv3"});
-            return;
-        }
-
-        if (openSslSocketClass.isInstance(socket)) {
-            try {
-                setUseSessionTickets.invoke(socket, true);
-                setHostname.invoke(socket, socketHost);
-            } catch (InvocationTargetException e) {
-                throw new RuntimeException(e);
-            } catch (IllegalAccessException e) {
-                throw new AssertionError(e);
-            }
-        } else {
-            throw new RuntimeException("Unknown socket implementation.");
-        }
-    }
-
-    /**
-     * Returns the negotiated protocol, or null if no protocol was negotiated.
-     */
-    public static byte[] getNpnSelectedProtocol(SSLSocket socket) {
-        if (openSslSocketClass.isInstance(socket)) {
-            try {
-                return (byte[]) getNpnSelectedProtocol.invoke(socket);
-            } catch (InvocationTargetException e) {
-                throw new RuntimeException(e);
-            } catch (IllegalAccessException e) {
-                throw new AssertionError(e);
-            }
-        } else {
-            throw new RuntimeException("Unknown socket implementation.");
-        }
-    }
-
-    public static void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
-        if (openSslSocketClass.isInstance(socket)) {
-            try {
-                setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
-            } catch (IllegalAccessException e) {
-                throw new AssertionError(e);
-            } catch (InvocationTargetException e) {
-                throw new RuntimeException(e);
-            }
-        } else {
-            throw new RuntimeException("Unknown socket implementation.");
-        }
-    }
-
-    public static void deleteIfExists(File file) throws IOException {
-        // okhttp-changed: was Libcore.os.remove() in a try/catch block
-        file.delete();
-    }
-
-    public static void logW(String warning) {
-        // okhttp-changed: was System.logw()
-        System.out.println(warning);
-    }
-
-    public static int getEffectivePort(URI uri) {
-        return getEffectivePort(uri.getScheme(), uri.getPort());
-    }
-
-    public static int getEffectivePort(URL url) {
-        return getEffectivePort(url.getProtocol(), url.getPort());
-    }
-
-    private static int getEffectivePort(String scheme, int specifiedPort) {
-        return specifiedPort != -1
-                ? specifiedPort
-                : getDefaultPort(scheme);
-    }
-
-    public static int getDefaultPort(String scheme) {
-        if ("http".equalsIgnoreCase(scheme)) {
-            return 80;
-        } else if ("https".equalsIgnoreCase(scheme)) {
-            return 443;
-        } else {
-            return -1;
-        }
-    }
-
-    public static void checkOffsetAndCount(int arrayLength, int offset, int count) {
-        if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-    }
-
-    public static void tagSocket(Socket socket) {
-    }
-
-    public static void untagSocket(Socket socket) throws SocketException {
-    }
-
-    public static URI toUriLenient(URL url) throws URISyntaxException {
-        return url.toURI(); // this isn't as good as the built-in toUriLenient
-    }
-
-    public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) {
-        if (order == ByteOrder.BIG_ENDIAN) {
-            dst[offset++] = (byte) ((value >> 24) & 0xff);
-            dst[offset++] = (byte) ((value >> 16) & 0xff);
-            dst[offset++] = (byte) ((value >>  8) & 0xff);
-            dst[offset  ] = (byte) ((value >>  0) & 0xff);
-        } else {
-            dst[offset++] = (byte) ((value >>  0) & 0xff);
-            dst[offset++] = (byte) ((value >>  8) & 0xff);
-            dst[offset++] = (byte) ((value >> 16) & 0xff);
-            dst[offset  ] = (byte) ((value >> 24) & 0xff);
-        }
-    }
-}
diff --git a/jarjar-rules.txt b/jarjar-rules.txt
index 83c5233..1629f1c 100644
--- a/jarjar-rules.txt
+++ b/jarjar-rules.txt
@@ -1 +1 @@
-rule libcore.** libcoreunbundled.@1
+rule com.squareup.** com.android.@1
diff --git a/src/main/java/com/squareup/okhttp/Address.java b/src/main/java/com/squareup/okhttp/Address.java
new file mode 100644
index 0000000..430eff5
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/Address.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import static com.squareup.okhttp.internal.Util.equal;
+import java.net.Proxy;
+import java.net.UnknownHostException;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * A specification for a connection to an origin server. For simple connections,
+ * this is the server's hostname and port. If an explicit proxy is requested (or
+ * {@link Proxy#NO_PROXY no proxy} is explicitly requested), this also includes
+ * that proxy information. For secure connections the address also includes the
+ * SSL socket factory and hostname verifier.
+ *
+ * <p>HTTP requests that share the same {@code Address} may also share the same
+ * {@link Connection}.
+ */
+public final class Address {
+    final Proxy proxy;
+    final String uriHost;
+    final int uriPort;
+    final SSLSocketFactory sslSocketFactory;
+    final HostnameVerifier hostnameVerifier;
+
+    public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
+            HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException {
+        if (uriHost == null) throw new NullPointerException("uriHost == null");
+        if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
+        this.proxy = proxy;
+        this.uriHost = uriHost;
+        this.uriPort = uriPort;
+        this.sslSocketFactory = sslSocketFactory;
+        this.hostnameVerifier = hostnameVerifier;
+    }
+
+    /**
+     * Returns the hostname of the origin server.
+     */
+    public String getUriHost() {
+        return uriHost;
+    }
+
+    /**
+     * Returns the port of the origin server; typically 80 or 443. Unlike
+     * may {@code getPort()} accessors, this method never returns -1.
+     */
+    public int getUriPort() {
+        return uriPort;
+    }
+
+    /**
+     * Returns the SSL socket factory, or null if this is not an HTTPS
+     * address.
+     */
+    public SSLSocketFactory getSslSocketFactory() {
+        return sslSocketFactory;
+    }
+
+    /**
+     * Returns the hostname verifier, or null if this is not an HTTPS
+     * address.
+     */
+    public HostnameVerifier getHostnameVerifier() {
+        return hostnameVerifier;
+    }
+
+    /**
+     * Returns this address's explicitly-specified HTTP proxy, or null to
+     * delegate to the HTTP client's proxy selector.
+     */
+    public Proxy getProxy() {
+        return proxy;
+    }
+
+    @Override public boolean equals(Object other) {
+        if (other instanceof Address) {
+            Address that = (Address) other;
+            return equal(this.proxy, that.proxy)
+                    && this.uriHost.equals(that.uriHost)
+                    && this.uriPort == that.uriPort
+                    && equal(this.sslSocketFactory, that.sslSocketFactory)
+                    && equal(this.hostnameVerifier, that.hostnameVerifier);
+        }
+        return false;
+    }
+
+    @Override public int hashCode() {
+        int result = 17;
+        result = 31 * result + uriHost.hashCode();
+        result = 31 * result + uriPort;
+        result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
+        result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
+        result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/src/main/java/com/squareup/okhttp/Connection.java b/src/main/java/com/squareup/okhttp/Connection.java
new file mode 100644
index 0000000..61f74d1
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/Connection.java
@@ -0,0 +1,304 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.http.HttpAuthenticator;
+import com.squareup.okhttp.internal.http.HttpEngine;
+import com.squareup.okhttp.internal.http.HttpTransport;
+import com.squareup.okhttp.internal.http.RawHeaders;
+import com.squareup.okhttp.internal.http.SpdyTransport;
+import com.squareup.okhttp.internal.spdy.SpdyConnection;
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.URL;
+import java.util.Arrays;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection,
+ * which may be used for multiple HTTP request/response exchanges. Connections
+ * may be direct to the origin server or via a proxy.
+ *
+ * <p>Typically instances of this class are created, connected and exercised
+ * automatically by the HTTP client. Applications may use this class to monitor
+ * HTTP connections as members of a {@link ConnectionPool connection pool}.
+ *
+ * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
+ * which isn't so much a connection as a single request/response exchange.
+ *
+ * <h3>Modern TLS</h3>
+ * There are tradeoffs when selecting which options to include when negotiating
+ * a secure connection to a remote host. Newer TLS options are quite useful:
+ * <ul>
+ *   <li>Server Name Indication (SNI) enables one IP address to negotiate secure
+ *       connections for multiple domain names.
+ *   <li>Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used
+ *       for both HTTP and SPDY transports.
+ * </ul>
+ * Unfortunately, older HTTPS servers refuse to connect when such options are
+ * presented. Rather than avoiding these options entirely, this class allows a
+ * connection to be attempted with modern options and then retried without them
+ * should the attempt fail.
+ */
+public final class Connection implements Closeable {
+    private static final byte[] NPN_PROTOCOLS = new byte[] {
+            6, 's', 'p', 'd', 'y', '/', '2',
+            8, 'h', 't', 't', 'p', '/', '1', '.', '1',
+    };
+    private static final byte[] SPDY2 = new byte[] {
+            's', 'p', 'd', 'y', '/', '2',
+    };
+    private static final byte[] HTTP_11 = new byte[] {
+            'h', 't', 't', 'p', '/', '1', '.', '1',
+    };
+
+    private final Address address;
+    private final Proxy proxy;
+    private final InetSocketAddress inetSocketAddress;
+    private final boolean modernTls;
+
+    private Socket socket;
+    private InputStream in;
+    private OutputStream out;
+    private boolean recycled = false;
+    private SpdyConnection spdyConnection;
+    private int httpMinorVersion = 1; // Assume HTTP/1.1
+
+    public Connection(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
+            boolean modernTls) {
+        if (address == null) throw new NullPointerException("address == null");
+        if (proxy == null) throw new NullPointerException("proxy == null");
+        if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
+        this.address = address;
+        this.proxy = proxy;
+        this.inetSocketAddress = inetSocketAddress;
+        this.modernTls = modernTls;
+    }
+
+    public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
+            throws IOException {
+        socket = (proxy.type() != Proxy.Type.HTTP)
+                ? new Socket(proxy)
+                : new Socket();
+        socket.connect(inetSocketAddress, connectTimeout);
+        socket.setSoTimeout(readTimeout);
+        in = socket.getInputStream();
+        out = socket.getOutputStream();
+
+        if (address.sslSocketFactory != null) {
+            upgradeToTls(tunnelRequest);
+        }
+
+        // Buffer the socket stream to permit efficient parsing of HTTP headers and chunk sizes.
+        if (!isSpdy()) {
+            int bufferSize = 128;
+            in = new BufferedInputStream(in, bufferSize);
+        }
+    }
+
+    /**
+     * Create an {@code SSLSocket} and perform the TLS handshake and certificate
+     * validation.
+     */
+    private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException {
+        Platform platform = Platform.get();
+
+        // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
+        if (requiresTunnel()) {
+            makeTunnel(tunnelRequest);
+        }
+
+        // Create the wrapper over connected socket.
+        socket = address.sslSocketFactory.createSocket(
+                socket, address.uriHost, address.uriPort, true /* autoClose */);
+        SSLSocket sslSocket = (SSLSocket) socket;
+        if (modernTls) {
+            platform.enableTlsExtensions(sslSocket, address.uriHost);
+        } else {
+            platform.supportTlsIntolerantServer(sslSocket);
+        }
+
+        if (modernTls) {
+            platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
+        }
+
+        // Force handshake. This can throw!
+        sslSocket.startHandshake();
+
+        // Verify that the socket's certificates are acceptable for the target host.
+        if (!address.hostnameVerifier.verify(address.uriHost, sslSocket.getSession())) {
+            throw new IOException("Hostname '" + address.uriHost + "' was not verified");
+        }
+
+        out = sslSocket.getOutputStream();
+        in = sslSocket.getInputStream();
+
+        byte[] selectedProtocol;
+        if (modernTls
+                && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
+            if (Arrays.equals(selectedProtocol, SPDY2)) {
+                sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
+                spdyConnection = new SpdyConnection.Builder(true, in, out).build();
+            } else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
+                throw new IOException("Unexpected NPN transport "
+                        + new String(selectedProtocol, "ISO-8859-1"));
+            }
+        }
+    }
+
+    @Override public void close() throws IOException {
+        socket.close();
+    }
+
+    /**
+     * Returns the proxy that this connection is using.
+     *
+     * <strong>Warning:</strong> This may be different than the proxy returned
+     * by {@link #getAddress}! That is the proxy that the user asked to be
+     * connected to; this returns the proxy that they were actually connected
+     * to. The two may disagree when a proxy selector selects a different proxy
+     * for a connection.
+     */
+    public Proxy getProxy() {
+        return proxy;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    public InetSocketAddress getSocketAddress() {
+        return inetSocketAddress;
+    }
+
+    public boolean isModernTls() {
+        return modernTls;
+    }
+
+    /**
+     * Returns the socket that this connection uses, or null if the connection
+     * is not currently connected.
+     */
+    public Socket getSocket() {
+        return socket;
+    }
+
+    /**
+     * Returns true if this connection has been used to satisfy an earlier
+     * HTTP request/response pair.
+     *
+     * <p>The HTTP client treats recycled and non-recycled connections
+     * differently. I/O failures on recycled connections are often temporary:
+     * the remote peer may have closed the socket because it was idle for an
+     * extended period of time. When fresh connections suffer similar failures
+     * the problem is fatal and the request is not retried.
+     */
+    public boolean isRecycled() {
+        return recycled;
+    }
+
+    public void setRecycled() {
+        this.recycled = true;
+    }
+
+    /**
+     * Returns true if this connection is eligible to be reused for another
+     * request/response pair.
+     */
+    protected boolean isEligibleForRecycling() {
+        return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
+    }
+
+    /**
+     * Returns the transport appropriate for this connection.
+     */
+    public Object newTransport(HttpEngine httpEngine) throws IOException {
+        return (spdyConnection != null)
+                ? new SpdyTransport(httpEngine, spdyConnection)
+                : new HttpTransport(httpEngine, out, in);
+    }
+
+    /**
+     * Returns true if this is a SPDY connection. Such connections can be used
+     * in multiple HTTP requests simultaneously.
+     */
+    public boolean isSpdy() {
+        return spdyConnection != null;
+    }
+
+    /**
+     * Returns the minor HTTP version that should be used for future requests on
+     * this connection. Either 0 for HTTP/1.0, or 1 for HTTP/1.1. The default
+     * value is 1 for new connections.
+     */
+    public int getHttpMinorVersion() {
+        return httpMinorVersion;
+    }
+
+    public void setHttpMinorVersion(int httpMinorVersion) {
+        this.httpMinorVersion = httpMinorVersion;
+    }
+
+    /**
+     * Returns true if the HTTP connection needs to tunnel one protocol over
+     * another, such as when using HTTPS through an HTTP proxy. When doing so,
+     * we must avoid buffering bytes intended for the higher-level protocol.
+     */
+    public boolean requiresTunnel() {
+        return address.sslSocketFactory != null && proxy != null && proxy.type() == Proxy.Type.HTTP;
+    }
+
+    /**
+     * To make an HTTPS connection over an HTTP proxy, send an unencrypted
+     * CONNECT request to create the proxy connection. This may need to be
+     * retried if the proxy requires authorization.
+     */
+    private void makeTunnel(TunnelRequest tunnelRequest) throws IOException {
+        RawHeaders requestHeaders = tunnelRequest.getRequestHeaders();
+        while (true) {
+            out.write(requestHeaders.toBytes());
+            RawHeaders responseHeaders = RawHeaders.fromBytes(in);
+
+            switch (responseHeaders.getResponseCode()) {
+            case HTTP_OK:
+                return;
+            case HTTP_PROXY_AUTH:
+                requestHeaders = new RawHeaders(requestHeaders);
+                URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
+                boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH,
+                        responseHeaders, requestHeaders, proxy, url);
+                if (credentialsFound) {
+                    continue;
+                } else {
+                    throw new IOException("Failed to authenticate with proxy");
+                }
+            default:
+                throw new IOException("Unexpected response code for CONNECT: "
+                        + responseHeaders.getResponseCode());
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/squareup/okhttp/ConnectionPool.java b/src/main/java/com/squareup/okhttp/ConnectionPool.java
new file mode 100644
index 0000000..afb0e58
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/ConnectionPool.java
@@ -0,0 +1,169 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP
+ * requests that share the same {@link Address} may share a {@link Connection}.
+ * This class implements the policy of which connections to keep open for future
+ * use.
+ *
+ * <p>The {@link #getDefault() system-wide default} uses system properties for
+ * tuning parameters:
+ * <ul>
+ *   <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
+ *       pooled at all. Default is true.
+ *   <li>{@code http.maxConnections} maximum number of connections to each
+ *       address. Default is 5.
+ * </ul>
+ *
+ * <p>The default instance <i>doesn't</i> adjust its configuration as system
+ * properties are changed. This assumes that the applications that set these
+ * parameters do so before making HTTP connections, and that this class is
+ * initialized lazily.
+ */
+public final class ConnectionPool {
+    private static final ConnectionPool systemDefault;
+    static {
+        String keepAlive = System.getProperty("http.keepAlive");
+        String maxConnections = System.getProperty("http.maxConnections");
+        if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
+            systemDefault = new ConnectionPool(0);
+        } else if (maxConnections != null) {
+            systemDefault = new ConnectionPool(Integer.parseInt(maxConnections));
+        } else {
+            systemDefault = new ConnectionPool(5);
+        }
+    }
+
+    /** The maximum number of idle connections for each address. */
+    private final int maxConnections;
+    private final HashMap<Address, List<Connection>> connectionPool
+            = new HashMap<Address, List<Connection>>();
+
+    public ConnectionPool(int maxConnections) {
+        this.maxConnections = maxConnections;
+    }
+
+    public static ConnectionPool getDefault() {
+        return systemDefault;
+    }
+
+    /**
+     * Returns a recycled connection to {@code address}, or null if no such
+     * connection exists.
+     */
+    public Connection get(Address address) {
+        // First try to reuse an existing HTTP connection.
+        synchronized (connectionPool) {
+            List<Connection> connections = connectionPool.get(address);
+            while (connections != null) {
+                Connection connection = connections.get(connections.size() - 1);
+                if (!connection.isSpdy()) {
+                    connections.remove(connections.size() - 1);
+                }
+                if (connections.isEmpty()) {
+                    connectionPool.remove(address);
+                    connections = null;
+                }
+                if (!connection.isEligibleForRecycling()) {
+                    Util.closeQuietly(connection);
+                    continue;
+                }
+                try {
+                    Platform.get().tagSocket(connection.getSocket());
+                } catch (SocketException e) {
+                    // When unable to tag, skip recycling and close
+                    Platform.get().logW("Unable to tagSocket(): " + e);
+                    Util.closeQuietly(connection);
+                    continue;
+                }
+                return connection;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gives {@code connection} to the pool. The pool may store the connection,
+     * or close it, as its policy describes.
+     *
+     * <p>It is an error to use {@code connection} after calling this method.
+     */
+    public void recycle(Connection connection) {
+        if (connection.isSpdy()) {
+            return;
+        }
+
+        try {
+            Platform.get().untagSocket(connection.getSocket());
+        } catch (SocketException e) {
+            // When unable to remove tagging, skip recycling and close
+            Platform.get().logW("Unable to untagSocket(): " + e);
+            Util.closeQuietly(connection);
+            return;
+        }
+
+        if (maxConnections > 0 && connection.isEligibleForRecycling()) {
+            Address address = connection.getAddress();
+            synchronized (connectionPool) {
+                List<Connection> connections = connectionPool.get(address);
+                if (connections == null) {
+                    connections = new ArrayList<Connection>();
+                    connectionPool.put(address, connections);
+                }
+                if (connections.size() < maxConnections) {
+                    connection.setRecycled();
+                    connections.add(connection);
+                    return; // keep the connection open
+                }
+            }
+        }
+
+        // don't close streams while holding a lock!
+        Util.closeQuietly(connection);
+    }
+
+    /**
+     * Shares the SPDY connection with the pool. Callers to this method may
+     * continue to use {@code connection}.
+     */
+    public void share(Connection connection) {
+        if (!connection.isSpdy()) {
+            throw new IllegalArgumentException();
+        }
+        if (maxConnections <= 0 || !connection.isEligibleForRecycling()) {
+            return;
+        }
+        Address address = connection.getAddress();
+        synchronized (connectionPool) {
+            List<Connection> connections = connectionPool.get(address);
+            if (connections == null) {
+                connections = new ArrayList<Connection>(1);
+                connections.add(connection);
+                connectionPool.put(address, connections);
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/squareup/okhttp/OkHttpClient.java b/src/main/java/com/squareup/okhttp/OkHttpClient.java
new file mode 100644
index 0000000..2205487
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/OkHttpClient.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
+import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
+import java.net.CookieHandler;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.ResponseCache;
+import java.net.URL;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Configures and creates HTTP connections.
+ */
+public final class OkHttpClient {
+    private Proxy proxy;
+    private ProxySelector proxySelector;
+    private CookieHandler cookieHandler;
+    private ResponseCache responseCache;
+    private SSLSocketFactory sslSocketFactory;
+    private HostnameVerifier hostnameVerifier;
+    private ConnectionPool connectionPool;
+
+    /**
+     * Sets the HTTP proxy that will be used by connections created by this
+     * client. This takes precedence over {@link #setProxySelector}, which is
+     * only honored when this proxy is null (which it is by default). To disable
+     * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
+     */
+    public OkHttpClient setProxy(Proxy proxy) {
+        this.proxy = proxy;
+        return this;
+    }
+
+    /**
+     * Sets the proxy selection policy to be used if no {@link #setProxy proxy}
+     * is specified explicitly. The proxy selector may return multiple proxies;
+     * in that case they will be tried in sequence until a successful connection
+     * is established.
+     *
+     * <p>If unset, the {@link ProxySelector#getDefault() system-wide default}
+     * proxy selector will be used.
+     */
+    public OkHttpClient setProxySelector(ProxySelector proxySelector) {
+        this.proxySelector = proxySelector;
+        return this;
+    }
+
+    /**
+     * Sets the cookie handler to be used to read outgoing cookies and write
+     * incoming cookies.
+     *
+     * <p>If unset, the {@link CookieHandler#getDefault() system-wide default}
+     * cookie handler will be used.
+     */
+    public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
+        this.cookieHandler = cookieHandler;
+        return this;
+    }
+
+    /**
+     * Sets the response cache to be used to read and write cached responses.
+     *
+     * <p>If unset, the {@link ResponseCache#getDefault() system-wide default}
+     * response cache will be used.
+     */
+    public OkHttpClient setResponseCache(ResponseCache responseCache) {
+        this.responseCache = responseCache;
+        return this;
+    }
+
+    /**
+     * Sets the socket factory used to secure HTTPS connections.
+     *
+     * <p>If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory()
+     * system-wide default} SSL socket factory will be used.
+     */
+    public OkHttpClient setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
+        this.sslSocketFactory = sslSocketFactory;
+        return this;
+    }
+
+    /**
+     * Sets the verifier used to confirm that response certificates apply to
+     * requested hostnames for HTTPS connections.
+     *
+     * <p>If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier()
+     * system-wide default} hostname verifier will be used.
+     */
+    public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+        this.hostnameVerifier = hostnameVerifier;
+        return this;
+    }
+
+    /**
+     * Sets the connection pool used to recycle HTTP and HTTPS connections.
+     *
+     * <p>If unset, the {@link ConnectionPool#getDefault() system-wide
+     * default} connection pool will be used.
+     */
+    public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
+        this.connectionPool = connectionPool;
+        return this;
+    }
+
+    public HttpURLConnection open(URL url) {
+        ProxySelector proxySelector = this.proxySelector != null
+                ? this.proxySelector
+                : ProxySelector.getDefault();
+        CookieHandler cookieHandler = this.cookieHandler != null
+                ? this.cookieHandler
+                : CookieHandler.getDefault();
+        ResponseCache responseCache = this.responseCache != null
+                ? this.responseCache
+                : ResponseCache.getDefault();
+        ConnectionPool connectionPool = this.connectionPool != null
+                ? this.connectionPool
+                : ConnectionPool.getDefault();
+
+        String protocol = url.getProtocol();
+        if (protocol.equals("http")) {
+            return new HttpURLConnectionImpl(
+                    url, 80, proxy, proxySelector, cookieHandler, responseCache, connectionPool);
+        } else if (protocol.equals("https")) {
+            HttpsURLConnectionImpl result = new HttpsURLConnectionImpl(
+                    url, 443, proxy, proxySelector, cookieHandler, responseCache, connectionPool);
+            result.setSSLSocketFactory(this.sslSocketFactory != null
+                    ? this.sslSocketFactory
+                    : HttpsURLConnection.getDefaultSSLSocketFactory());
+            result.setHostnameVerifier(this.hostnameVerifier != null
+                    ? this.hostnameVerifier
+                    : HttpsURLConnection.getDefaultHostnameVerifier());
+            return result;
+        } else {
+            throw new IllegalArgumentException("Unexpected protocol: " + protocol);
+        }
+    }
+}
diff --git a/src/main/java/com/squareup/okhttp/OkHttpConnection.java b/src/main/java/com/squareup/okhttp/OkHttpConnection.java
deleted file mode 100644
index 5df657a..0000000
--- a/src/main/java/com/squareup/okhttp/OkHttpConnection.java
+++ /dev/null
@@ -1,829 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.squareup.okhttp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.SocketPermission;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Arrays;
-import libcore.net.http.HttpEngine;
-
-/**
- * An {@link java.net.URLConnection} for HTTP (<a
- * href="http://tools.ietf.org/html/rfc2616">RFC 2616</a>) used to send and
- * receive data over the web. Data may be of any type and length. This class may
- * be used to send and receive streaming data whose length is not known in
- * advance.
- *
- * <p>Uses of this class follow a pattern:
- * <ol>
- *   <li>Obtain a new {@code HttpURLConnection} by calling {@link
- *       java.net.URL#openConnection() URL.openConnection()} and casting the result to
- *       {@code HttpURLConnection}.
- *   <li>Prepare the request. The primary property of a request is its URI.
- *       Request headers may also include metadata such as credentials, preferred
- *       content types, and session cookies.
- *   <li>Optionally upload a request body. Instances must be configured with
- *       {@link #setDoOutput(boolean) setDoOutput(true)} if they include a
- *       request body. Transmit data by writing to the stream returned by {@link
- *       #getOutputStream()}.
- *   <li>Read the response. Response headers typically include metadata such as
- *       the response body's content type and length, modified dates and session
- *       cookies. The response body may be read from the stream returned by {@link
- *       #getInputStream()}. If the response has no body, that method returns an
- *       empty stream.
- *   <li>Disconnect. Once the response body has been read, the {@code
- *       HttpURLConnection} should be closed by calling {@link #disconnect()}.
- *       Disconnecting releases the resources held by a connection so they may
- *       be closed or reused.
- * </ol>
- *
- * <p>For example, to retrieve the webpage at {@code http://www.android.com/}:
- * <pre>   {@code
- *   URL url = new URL("http://www.android.com/");
- *   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
- *   try {
- *     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
- *     readStream(in);
- *   } finally {
- *     urlConnection.disconnect();
- *   }
- * }</pre>
- *
- * <h3>Secure Communication with HTTPS</h3>
- * Calling {@link java.net.URL#openConnection()} on a URL with the "https"
- * scheme will return an {@code HttpsURLConnection}, which allows for
- * overriding the default {@link javax.net.ssl.HostnameVerifier
- * HostnameVerifier} and {@link javax.net.ssl.SSLSocketFactory
- * SSLSocketFactory}. An application-supplied {@code SSLSocketFactory}
- * created from an {@link javax.net.ssl.SSLContext SSLContext} can
- * provide a custom {@link javax.net.ssl.X509TrustManager
- * X509TrustManager} for verifying certificate chains and a custom
- * {@link javax.net.ssl.X509KeyManager X509KeyManager} for supplying
- * client certificates. See {@link OkHttpsConnection HttpsURLConnection} for
- * more details.
- *
- * <h3>Response Handling</h3>
- * {@code HttpURLConnection} will follow up to five HTTP redirects. It will
- * follow redirects from one origin server to another. This implementation
- * doesn't follow redirects from HTTPS to HTTP or vice versa.
- *
- * <p>If the HTTP response indicates that an error occurred, {@link
- * #getInputStream()} will throw an {@link java.io.IOException}. Use {@link
- * #getErrorStream()} to read the error response. The headers can be read in
- * the normal way using {@link #getHeaderFields()},
- *
- * <h3>Posting Content</h3>
- * To upload data to a web server, configure the connection for output using
- * {@link #setDoOutput(boolean) setDoOutput(true)}.
- *
- * <p>For best performance, you should call either {@link
- * #setFixedLengthStreamingMode(int)} when the body length is known in advance,
- * or {@link #setChunkedStreamingMode(int)} when it is not. Otherwise {@code
- * HttpURLConnection} will be forced to buffer the complete request body in
- * memory before it is transmitted, wasting (and possibly exhausting) heap and
- * increasing latency.
- *
- * <p>For example, to perform an upload: <pre>   {@code
- *   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
- *   try {
- *     urlConnection.setDoOutput(true);
- *     urlConnection.setChunkedStreamingMode(0);
- *
- *     OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
- *     writeStream(out);
- *
- *     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
- *     readStream(in);
- *   } finally {
- *     urlConnection.disconnect();
- *   }
- * }</pre>
- *
- * <h3>Performance</h3>
- * The input and output streams returned by this class are <strong>not
- * buffered</strong>. Most callers should wrap the returned streams with {@link
- * java.io.BufferedInputStream BufferedInputStream} or {@link
- * java.io.BufferedOutputStream BufferedOutputStream}. Callers that do only bulk
- * reads or writes may omit buffering.
- *
- * <p>When transferring large amounts of data to or from a server, use streams
- * to limit how much data is in memory at once. Unless you need the entire
- * body to be in memory at once, process it as a stream (rather than storing
- * the complete body as a single byte array or string).
- *
- * <p>To reduce latency, this class may reuse the same underlying {@code Socket}
- * for multiple request/response pairs. As a result, HTTP connections may be
- * held open longer than necessary. Calls to {@link #disconnect()} may return
- * the socket to a pool of connected sockets. This behavior can be disabled by
- * setting the {@code http.keepAlive} system property to {@code false} before
- * issuing any HTTP requests. The {@code http.maxConnections} property may be
- * used to control how many idle connections to each server will be held.
- *
- * <p>By default, this implementation of {@code HttpURLConnection} requests that
- * servers use gzip compression. Since {@link #getContentLength()} returns the
- * number of bytes transmitted, you cannot use that method to predict how many
- * bytes can be read from {@link #getInputStream()}. Instead, read that stream
- * until it is exhausted: when {@link java.io.InputStream#read} returns -1. Gzip
- * compression can be disabled by setting the acceptable encodings in the
- * request header: <pre>   {@code
- *   urlConnection.setRequestProperty("Accept-Encoding", "identity");
- * }</pre>
- *
- * <h3>Handling Network Sign-On</h3>
- * Some Wi-Fi networks block Internet access until the user clicks through a
- * sign-on page. Such sign-on pages are typically presented by using HTTP
- * redirects. You can use {@link #getURL()} to test if your connection has been
- * unexpectedly redirected. This check is not valid until <strong>after</strong>
- * the response headers have been received, which you can trigger by calling
- * {@link #getHeaderFields()} or {@link #getInputStream()}. For example, to
- * check that a response was not redirected to an unexpected host:
- * <pre>   {@code
- *   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
- *   try {
- *     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
- *     if (!url.getHost().equals(urlConnection.getURL().getHost())) {
- *       // we were redirected! Kick the user out to the browser to sign on?
- *     }
- *     ...
- *   } finally {
- *     urlConnection.disconnect();
- *   }
- * }</pre>
- *
- * <h3>HTTP Authentication</h3>
- * {@code HttpURLConnection} supports <a
- * href="http://www.ietf.org/rfc/rfc2617">HTTP basic authentication</a>. Use
- * {@link java.net.Authenticator} to set the VM-wide authentication handler:
- * <pre>   {@code
- *   Authenticator.setDefault(new Authenticator() {
- *     protected PasswordAuthentication getPasswordAuthentication() {
- *       return new PasswordAuthentication(username, password.toCharArray());
- *     }
- *   });
- * }</pre>
- * Unless paired with HTTPS, this is <strong>not</strong> a secure mechanism for
- * user authentication. In particular, the username, password, request and
- * response are all transmitted over the network without encryption.
- *
- * <h3>Sessions with Cookies</h3>
- * To establish and maintain a potentially long-lived session between client
- * and server, {@code HttpURLConnection} includes an extensible cookie manager.
- * Enable VM-wide cookie management using {@link java.net.CookieHandler} and {@link
- * java.net.CookieManager}: <pre>   {@code
- *   CookieManager cookieManager = new CookieManager();
- *   CookieHandler.setDefault(cookieManager);
- * }</pre>
- * By default, {@code CookieManager} accepts cookies from the <a
- * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html">origin
- * server</a> only. Two other policies are included: {@link
- * java.net.CookiePolicy#ACCEPT_ALL} and {@link java.net.CookiePolicy#ACCEPT_NONE}. Implement
- * {@link java.net.CookiePolicy} to define a custom policy.
- *
- * <p>The default {@code CookieManager} keeps all accepted cookies in memory. It
- * will forget these cookies when the VM exits. Implement {@link java.net.CookieStore} to
- * define a custom cookie store.
- *
- * <p>In addition to the cookies set by HTTP responses, you may set cookies
- * programmatically. To be included in HTTP request headers, cookies must have
- * the domain and path properties set.
- *
- * <p>By default, new instances of {@code HttpCookie} work only with servers
- * that support <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>
- * cookies. Many web servers support only the older specification, <a
- * href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a>. For compatibility
- * with the most web servers, set the cookie version to 0.
- *
- * <p>For example, to receive {@code www.twitter.com} in French: <pre>   {@code
- *   HttpCookie cookie = new HttpCookie("lang", "fr");
- *   cookie.setDomain("twitter.com");
- *   cookie.setPath("/");
- *   cookie.setVersion(0);
- *   cookieManager.getCookieStore().add(new URI("http://twitter.com/"), cookie);
- * }</pre>
- *
- * <h3>HTTP Methods</h3>
- * <p>{@code HttpURLConnection} uses the {@code GET} method by default. It will
- * use {@code POST} if {@link #setDoOutput setDoOutput(true)} has been called.
- * Other HTTP methods ({@code OPTIONS}, {@code HEAD}, {@code PUT}, {@code
- * DELETE} and {@code TRACE}) can be used with {@link #setRequestMethod}.
- *
- * <h3>Proxies</h3>
- * By default, this class will connect directly to the <a
- * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html">origin
- * server</a>. It can also connect via an {@link java.net.Proxy.Type#HTTP HTTP} or {@link
- * java.net.Proxy.Type#SOCKS SOCKS} proxy. To use a proxy, use {@link
- * java.net.URL#openConnection(java.net.Proxy) URL.openConnection(Proxy)} when creating the
- * connection.
- *
- * <h3>IPv6 Support</h3>
- * <p>This class includes transparent support for IPv6. For hosts with both IPv4
- * and IPv6 addresses, it will attempt to connect to each of a host's addresses
- * until a connection is established.
- *
- * <h3>Response Caching</h3>
- * Android 4.0 (Ice Cream Sandwich) includes a response cache. See {@code
- * android.net.http.HttpResponseCache} for instructions on enabling HTTP caching
- * in your application.
- *
- * <h3>Avoiding Bugs In Earlier Releases</h3>
- * Prior to Android 2.2 (Froyo), this class had some frustrating bugs. In
- * particular, calling {@code close()} on a readable {@code InputStream} could
- * <a href="http://code.google.com/p/android/issues/detail?id=2939">poison the
- * connection pool</a>. Work around this by disabling connection pooling:
- * <pre>   {@code
- * private void disableConnectionReuseIfNecessary() {
- *   // Work around pre-Froyo bugs in HTTP connection reuse.
- *   if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
- *     System.setProperty("http.keepAlive", "false");
- *   }
- * }}</pre>
- *
- * <p>Each instance of {@code HttpURLConnection} may be used for one
- * request/response pair. Instances of this class are not thread safe.
- */
-public abstract class OkHttpConnection extends URLConnection {
-
-    /**
-     * The subset of HTTP methods that the user may select via {@link
-     * #setRequestMethod(String)}.
-     */
-    private static final String[] PERMITTED_USER_METHODS = {
-            HttpEngine.OPTIONS,
-            HttpEngine.GET,
-            HttpEngine.HEAD,
-            HttpEngine.POST,
-            HttpEngine.PUT,
-            HttpEngine.DELETE,
-            HttpEngine.TRACE
-            // Note: we don't allow users to specify "CONNECT"
-    };
-
-    /**
-     * The HTTP request method of this {@code HttpURLConnection}. The default
-     * value is {@code "GET"}.
-     */
-    protected String method = HttpEngine.GET;
-
-    /**
-     * The status code of the response obtained from the HTTP request. The
-     * default value is {@code -1}.
-     * <p>
-     * <li>1xx: Informational</li>
-     * <li>2xx: Success</li>
-     * <li>3xx: Relocation/Redirection</li>
-     * <li>4xx: Client Error</li>
-     * <li>5xx: Server Error</li>
-     */
-    protected int responseCode = -1;
-
-    /**
-     * The HTTP response message which corresponds to the response code.
-     */
-    protected String responseMessage;
-
-    /**
-     * Flag to define whether the protocol will automatically follow redirects
-     * or not. The default value is {@code true}.
-     */
-    protected boolean instanceFollowRedirects = followRedirects;
-
-    private static boolean followRedirects = true;
-
-    /**
-     * If the HTTP chunked encoding is enabled this parameter defines the
-     * chunk-length. Default value is {@code -1} that means the chunked encoding
-     * mode is disabled.
-     */
-    protected int chunkLength = -1;
-
-    /**
-     * If using HTTP fixed-length streaming mode this parameter defines the
-     * fixed length of content. Default value is {@code -1} that means the
-     * fixed-length streaming mode is disabled.
-     */
-    protected int fixedContentLength = -1;
-
-    // 2XX: generally "OK"
-    // 3XX: relocation/redirect
-    // 4XX: client error
-    // 5XX: server error
-    /**
-     * Numeric status code, 202: Accepted.
-     */
-    public static final int HTTP_ACCEPTED = 202;
-
-    /**
-     * Numeric status code, 502: Bad Gateway.
-     */
-    public static final int HTTP_BAD_GATEWAY = 502;
-
-    /**
-     * Numeric status code, 405: Bad Method.
-     */
-    public static final int HTTP_BAD_METHOD = 405;
-
-    /**
-     * Numeric status code, 400: Bad Request.
-     */
-    public static final int HTTP_BAD_REQUEST = 400;
-
-    /**
-     * Numeric status code, 408: Client Timeout.
-     */
-    public static final int HTTP_CLIENT_TIMEOUT = 408;
-
-    /**
-     * Numeric status code, 409: Conflict.
-     */
-    public static final int HTTP_CONFLICT = 409;
-
-    /**
-     * Numeric status code, 201: Created.
-     */
-    public static final int HTTP_CREATED = 201;
-
-    /**
-     * Numeric status code, 413: Entity too large.
-     */
-    public static final int HTTP_ENTITY_TOO_LARGE = 413;
-
-    /**
-     * Numeric status code, 403: Forbidden.
-     */
-    public static final int HTTP_FORBIDDEN = 403;
-
-    /**
-     * Numeric status code, 504: Gateway timeout.
-     */
-    public static final int HTTP_GATEWAY_TIMEOUT = 504;
-
-    /**
-     * Numeric status code, 410: Gone.
-     */
-    public static final int HTTP_GONE = 410;
-
-    /**
-     * Numeric status code, 500: Internal error.
-     */
-    public static final int HTTP_INTERNAL_ERROR = 500;
-
-    /**
-     * Numeric status code, 411: Length required.
-     */
-    public static final int HTTP_LENGTH_REQUIRED = 411;
-
-    /**
-     * Numeric status code, 301 Moved permanently.
-     */
-    public static final int HTTP_MOVED_PERM = 301;
-
-    /**
-     * Numeric status code, 302: Moved temporarily.
-     */
-    public static final int HTTP_MOVED_TEMP = 302;
-
-    /**
-     * Numeric status code, 300: Multiple choices.
-     */
-    public static final int HTTP_MULT_CHOICE = 300;
-
-    /**
-     * Numeric status code, 204: No content.
-     */
-    public static final int HTTP_NO_CONTENT = 204;
-
-    /**
-     * Numeric status code, 406: Not acceptable.
-     */
-    public static final int HTTP_NOT_ACCEPTABLE = 406;
-
-    /**
-     * Numeric status code, 203: Not authoritative.
-     */
-    public static final int HTTP_NOT_AUTHORITATIVE = 203;
-
-    /**
-     * Numeric status code, 404: Not found.
-     */
-    public static final int HTTP_NOT_FOUND = 404;
-
-    /**
-     * Numeric status code, 501: Not implemented.
-     */
-    public static final int HTTP_NOT_IMPLEMENTED = 501;
-
-    /**
-     * Numeric status code, 304: Not modified.
-     */
-    public static final int HTTP_NOT_MODIFIED = 304;
-
-    /**
-     * Numeric status code, 200: OK.
-     */
-    public static final int HTTP_OK = 200;
-
-    /**
-     * Numeric status code, 206: Partial.
-     */
-    public static final int HTTP_PARTIAL = 206;
-
-    /**
-     * Numeric status code, 402: Payment required.
-     */
-    public static final int HTTP_PAYMENT_REQUIRED = 402;
-
-    /**
-     * Numeric status code, 412: Precondition failed.
-     */
-    public static final int HTTP_PRECON_FAILED = 412;
-
-    /**
-     * Numeric status code, 407: Proxy authentication required.
-     */
-    public static final int HTTP_PROXY_AUTH = 407;
-
-    /**
-     * Numeric status code, 414: Request too long.
-     */
-    public static final int HTTP_REQ_TOO_LONG = 414;
-
-    /**
-     * Numeric status code, 205: Reset.
-     */
-    public static final int HTTP_RESET = 205;
-
-    /**
-     * Numeric status code, 303: See other.
-     */
-    public static final int HTTP_SEE_OTHER = 303;
-
-    /**
-     * Numeric status code, 500: Internal error.
-     *
-     * @deprecated Use {@link #HTTP_INTERNAL_ERROR}
-     */
-    @Deprecated
-    public static final int HTTP_SERVER_ERROR = 500;
-
-    /**
-     * Numeric status code, 305: Use proxy.
-     *
-     * <p>Like Firefox and Chrome, this class doesn't honor this response code.
-     * Other implementations respond to this status code by retrying the request
-     * using the HTTP proxy named by the response's Location header field.
-     */
-    public static final int HTTP_USE_PROXY = 305;
-
-    /**
-     * Numeric status code, 401: Unauthorized.
-     */
-    public static final int HTTP_UNAUTHORIZED = 401;
-
-    /**
-     * Numeric status code, 415: Unsupported type.
-     */
-    public static final int HTTP_UNSUPPORTED_TYPE = 415;
-
-    /**
-     * Numeric status code, 503: Unavailable.
-     */
-    public static final int HTTP_UNAVAILABLE = 503;
-
-    /**
-     * Numeric status code, 505: Version not supported.
-     */
-    public static final int HTTP_VERSION = 505;
-
-    /**
-     * Returns a new OkHttpConnection or OkHttpsConnection to {@code url}.
-     */
-    public static OkHttpConnection open(URL url) {
-        String protocol = url.getProtocol();
-        if (protocol.equals("http")) {
-            return new libcore.net.http.HttpURLConnectionImpl(url, 80);
-        } else if (protocol.equals("https")) {
-            return new libcore.net.http.HttpsURLConnectionImpl(url, 443);
-        } else {
-            throw new IllegalArgumentException();
-        }
-    }
-
-    /**
-     * Returns a new OkHttpConnection or OkHttpsConnection to {@code url} that
-     * connects via {@code proxy}.
-     */
-    public static OkHttpConnection open(URL url, Proxy proxy) {
-        String protocol = url.getProtocol();
-        if (protocol.equals("http")) {
-            return new libcore.net.http.HttpURLConnectionImpl(url, 80, proxy);
-        } else if (protocol.equals("https")) {
-            return new libcore.net.http.HttpsURLConnectionImpl(url, 443, proxy);
-        } else {
-            throw new IllegalArgumentException();
-        }
-    }
-
-    /**
-     * Constructs a new {@code HttpURLConnection} instance pointing to the
-     * resource specified by the {@code url}.
-     *
-     * @param url
-     *            the URL of this connection.
-     * @see java.net.URL
-     * @see java.net.URLConnection
-     */
-    protected OkHttpConnection(URL url) {
-        super(url);
-    }
-
-    /**
-     * Releases this connection so that its resources may be either reused or
-     * closed.
-     *
-     * <p>Unlike other Java implementations, this will not necessarily close
-     * socket connections that can be reused. You can disable all connection
-     * reuse by setting the {@code http.keepAlive} system property to {@code
-     * false} before issuing any HTTP requests.
-     */
-    public abstract void disconnect();
-
-    /**
-     * Returns an input stream from the server in the case of an error such as
-     * the requested file has not been found on the remote server. This stream
-     * can be used to read the data the server will send back.
-     *
-     * @return the error input stream returned by the server.
-     */
-    public InputStream getErrorStream() {
-        return null;
-    }
-
-    /**
-     * Returns the value of {@code followRedirects} which indicates if this
-     * connection follows a different URL redirected by the server. It is
-     * enabled by default.
-     *
-     * @return the value of the flag.
-     * @see #setFollowRedirects
-     */
-    public static boolean getFollowRedirects() {
-        return followRedirects;
-    }
-
-    /**
-     * Returns the permission object (in this case {@code SocketPermission})
-     * with the host and the port number as the target name and {@code
-     * "resolve, connect"} as the action list. If the port number of this URL
-     * instance is lower than {@code 0} the port will be set to {@code 80}.
-     *
-     * @return the permission object required for this connection.
-     * @throws java.io.IOException
-     *             if an IO exception occurs during the creation of the
-     *             permission object.
-     */
-    @Override
-    public java.security.Permission getPermission() throws IOException {
-        int port = url.getPort();
-        if (port < 0) {
-            port = 80;
-        }
-        return new SocketPermission(url.getHost() + ":" + port,
-                "connect, resolve");
-    }
-
-    /**
-     * Returns the request method which will be used to make the request to the
-     * remote HTTP server. All possible methods of this HTTP implementation is
-     * listed in the class definition.
-     *
-     * @return the request method string.
-     * @see #method
-     * @see #setRequestMethod
-     */
-    public String getRequestMethod() {
-        return method;
-    }
-
-    /**
-     * Returns the response code returned by the remote HTTP server.
-     *
-     * @return the response code, -1 if no valid response code.
-     * @throws java.io.IOException
-     *             if there is an IO error during the retrieval.
-     * @see #getResponseMessage
-     */
-    public int getResponseCode() throws IOException {
-        // Call getInputStream() first since getHeaderField() doesn't return
-        // exceptions
-        getInputStream();
-        String response = getHeaderField(0);
-        if (response == null) {
-            return -1;
-        }
-        response = response.trim();
-        int mark = response.indexOf(" ") + 1;
-        if (mark == 0) {
-            return -1;
-        }
-        int last = mark + 3;
-        if (last > response.length()) {
-            last = response.length();
-        }
-        responseCode = Integer.parseInt(response.substring(mark, last));
-        if (last + 1 <= response.length()) {
-            responseMessage = response.substring(last + 1);
-        }
-        return responseCode;
-    }
-
-    /**
-     * Returns the response message returned by the remote HTTP server.
-     *
-     * @return the response message. {@code null} if no such response exists.
-     * @throws java.io.IOException
-     *             if there is an error during the retrieval.
-     * @see #getResponseCode()
-     */
-    public String getResponseMessage() throws IOException {
-        if (responseMessage != null) {
-            return responseMessage;
-        }
-        getResponseCode();
-        return responseMessage;
-    }
-
-    /**
-     * Sets the flag of whether this connection will follow redirects returned
-     * by the remote server.
-     *
-     * @param auto
-     *            the value to enable or disable this option.
-     */
-    public static void setFollowRedirects(boolean auto) {
-        followRedirects = auto;
-    }
-
-    /**
-     * Sets the request command which will be sent to the remote HTTP server.
-     * This method can only be called before the connection is made.
-     *
-     * @param method
-     *            the string representing the method to be used.
-     * @throws java.net.ProtocolException
-     *             if this is called after connected, or the method is not
-     *             supported by this HTTP implementation.
-     * @see #getRequestMethod()
-     * @see #method
-     */
-    public void setRequestMethod(String method) throws ProtocolException {
-        if (connected) {
-            throw new ProtocolException("Connection already established");
-        }
-        for (String permittedUserMethod : PERMITTED_USER_METHODS) {
-            if (permittedUserMethod.equals(method)) {
-                // if there is a supported method that matches the desired
-                // method, then set the current method and return
-                this.method = permittedUserMethod;
-                return;
-            }
-        }
-        // if none matches, then throw ProtocolException
-        throw new ProtocolException("Unknown method '" + method + "'; must be one of "
-                + Arrays.toString(PERMITTED_USER_METHODS));
-    }
-
-    /**
-     * Returns whether this connection uses a proxy server or not.
-     *
-     * @return {@code true} if this connection passes a proxy server, false
-     *         otherwise.
-     */
-    public abstract boolean usingProxy();
-
-    /**
-     * Returns the encoding used to transmit the response body over the network.
-     * This is null or "identity" if the content was not encoded, or "gzip" if
-     * the body was gzip compressed. Most callers will be more interested in the
-     * {@link #getContentType() content type}, which may also include the
-     * content's character encoding.
-     */
-    @Override public String getContentEncoding() {
-        return super.getContentEncoding(); // overridden for Javadoc only
-    }
-
-    /**
-     * Returns whether this connection follows redirects.
-     *
-     * @return {@code true} if this connection follows redirects, false
-     *         otherwise.
-     */
-    public boolean getInstanceFollowRedirects() {
-        return instanceFollowRedirects;
-    }
-
-    /**
-     * Sets whether this connection follows redirects.
-     *
-     * @param followRedirects
-     *            {@code true} if this connection will follows redirects, false
-     *            otherwise.
-     */
-    public void setInstanceFollowRedirects(boolean followRedirects) {
-        instanceFollowRedirects = followRedirects;
-    }
-
-    /**
-     * Returns the date value in milliseconds since {@code 01.01.1970, 00:00h}
-     * corresponding to the header field {@code field}. The {@code defaultValue}
-     * will be returned if no such field can be found in the response header.
-     *
-     * @param field
-     *            the header field name.
-     * @param defaultValue
-     *            the default value to use if the specified header field wont be
-     *            found.
-     * @return the header field represented in milliseconds since January 1,
-     *         1970 GMT.
-     */
-    @Override
-    public long getHeaderFieldDate(String field, long defaultValue) {
-        return super.getHeaderFieldDate(field, defaultValue);
-    }
-
-    /**
-     * If the length of a HTTP request body is known ahead, sets fixed length to
-     * enable streaming without buffering. Sets after connection will cause an
-     * exception.
-     *
-     * @see #setChunkedStreamingMode
-     * @param contentLength
-     *            the fixed length of the HTTP request body.
-     * @throws IllegalStateException
-     *             if already connected or another mode already set.
-     * @throws IllegalArgumentException
-     *             if {@code contentLength} is less than zero.
-     */
-    public void setFixedLengthStreamingMode(int contentLength) {
-        if (super.connected) {
-            throw new IllegalStateException("Already connected");
-        }
-        if (chunkLength > 0) {
-            throw new IllegalStateException("Already in chunked mode");
-        }
-        if (contentLength < 0) {
-            throw new IllegalArgumentException("contentLength < 0");
-        }
-        this.fixedContentLength = contentLength;
-    }
-
-    /**
-     * Stream a request body whose length is not known in advance. Old HTTP/1.0
-     * only servers may not support this mode.
-     *
-     * <p>When HTTP chunked encoding is used, the stream is divided into
-     * chunks, each prefixed with a header containing the chunk's size. Setting
-     * a large chunk length requires a large internal buffer, potentially
-     * wasting memory. Setting a small chunk length increases the number of
-     * bytes that must be transmitted because of the header on every chunk.
-     * Most caller should use {@code 0} to get the system default.
-     *
-     * @see #setFixedLengthStreamingMode
-     * @param chunkLength the length to use, or {@code 0} for the default chunk
-     *     length.
-     * @throws IllegalStateException if already connected or another mode
-     *     already set.
-     */
-    public void setChunkedStreamingMode(int chunkLength) {
-        if (super.connected) {
-            throw new IllegalStateException("Already connected");
-        }
-        if (fixedContentLength >= 0) {
-            throw new IllegalStateException("Already in fixed-length mode");
-        }
-        if (chunkLength <= 0) {
-            this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH;
-        } else {
-            this.chunkLength = chunkLength;
-        }
-    }
-}
diff --git a/src/main/java/com/squareup/okhttp/OkHttpsConnection.java b/src/main/java/com/squareup/okhttp/OkHttpsConnection.java
deleted file mode 100644
index d1c144f..0000000
--- a/src/main/java/com/squareup/okhttp/OkHttpsConnection.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.squareup.okhttp;
-
-import java.net.URL;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocketFactory;
-
-/**
- * An {@link java.net.HttpURLConnection} for HTTPS (<a
- * href="http://tools.ietf.org/html/rfc2818">RFC 2818</a>). A
- * connected {@code HttpsURLConnection} allows access to the
- * negotiated cipher suite, the server certificate chain, and the
- * client certificate chain if any.
- *
- * <h3>Providing an application specific X509TrustManager</h3>
- *
- * If an application wants to trust Certificate Authority (CA)
- * certificates that are not part of the system, it should specify its
- * own {@code X509TrustManager} via a {@code SSLSocketFactory} set on
- * the {@code HttpsURLConnection}. The {@code X509TrustManager} can be
- * created based on a {@code KeyStore} using a {@code
- * TrustManagerFactory} to supply trusted CA certificates. Note that
- * self-signed certificates are effectively their own CA and can be
- * trusted by including them in a {@code KeyStore}.
- *
- * <p>For example, to trust a set of certificates specified by a {@code KeyStore}:
- * <pre>   {@code
- *   KeyStore keyStore = ...;
- *   TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
- *   tmf.init(keyStore);
- *
- *   SSLContext context = SSLContext.getInstance("TLS");
- *   context.init(null, tmf.getTrustManagers(), null);
- *
- *   URL url = new URL("https://www.example.com/");
- *   HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
- *   urlConnection.setSSLSocketFactory(context.getSocketFactory());
- *   InputStream in = urlConnection.getInputStream();
- * }</pre>
- *
- * <p>It is possible to implement {@code X509TrustManager} directly
- * instead of using one created by a {@code
- * TrustManagerFactory}. While this is straightforward in the insecure
- * case of allowing all certificate chains to pass verification,
- * writing a proper implementation will usually want to take advantage
- * of {@link java.security.cert.CertPathValidator
- * CertPathValidator}. In general, it might be better to write a
- * custom {@code KeyStore} implementation to pass to the {@code
- * TrustManagerFactory} than to try and write a custom {@code
- * X509TrustManager}.
- *
- * <h3>Providing an application specific X509KeyManager</h3>
- *
- * A custom {@code X509KeyManager} can be used to supply a client
- * certificate and its associated private key to authenticate a
- * connection to the server. The {@code X509KeyManager} can be created
- * based on a {@code KeyStore} using a {@code KeyManagerFactory}.
- *
- * <p>For example, to supply client certificates from a {@code KeyStore}:
- * <pre>   {@code
- *   KeyStore keyStore = ...;
- *   KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
- *   kmf.init(keyStore);
- *
- *   SSLContext context = SSLContext.getInstance("TLS");
- *   context.init(kmf.getKeyManagers(), null, null);
- *
- *   URL url = new URL("https://www.example.com/");
- *   HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
- *   urlConnection.setSSLSocketFactory(context.getSocketFactory());
- *   InputStream in = urlConnection.getInputStream();
- * }</pre>
- *
- * <p>A {@code X509KeyManager} can also be implemented directly. This
- * can allow an application to return a certificate and private key
- * from a non-{@code KeyStore} source or to specify its own logic for
- * selecting a specific credential to use when many may be present in
- * a single {@code KeyStore}.
- *
- * <h3>TLS Intolerance Support</h3>
- *
- * This class attempts to create secure connections using common TLS
- * extensions and SSL deflate compression. Should that fail, the
- * connection will be retried with SSLv3 only.
- */
-public abstract class OkHttpsConnection extends OkHttpConnection {
-
-    private static HostnameVerifier defaultHostnameVerifier
-            = HttpsURLConnection.getDefaultHostnameVerifier();
-
-    private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory
-            .getDefault();
-
-    /**
-     * Sets the default hostname verifier to be used by new instances.
-     *
-     * @param v
-     *            the new default hostname verifier
-     * @throws IllegalArgumentException
-     *             if the specified verifier is {@code null}.
-     */
-    public static void setDefaultHostnameVerifier(HostnameVerifier v) {
-        if (v == null) {
-            throw new IllegalArgumentException("HostnameVerifier is null");
-        }
-        defaultHostnameVerifier = v;
-    }
-
-    /**
-     * Returns the default hostname verifier.
-     *
-     * @return the default hostname verifier.
-     */
-    public static HostnameVerifier getDefaultHostnameVerifier() {
-        return defaultHostnameVerifier;
-    }
-
-    /**
-     * Sets the default SSL socket factory to be used by new instances.
-     *
-     * @param sf
-     *            the new default SSL socket factory.
-     * @throws IllegalArgumentException
-     *             if the specified socket factory is {@code null}.
-     */
-    public static void setDefaultSSLSocketFactory(SSLSocketFactory sf) {
-        if (sf == null) {
-            throw new IllegalArgumentException("SSLSocketFactory is null");
-        }
-        defaultSSLSocketFactory = sf;
-    }
-
-    /**
-     * Returns the default SSL socket factory for new instances.
-     *
-     * @return the default SSL socket factory for new instances.
-     */
-    public static SSLSocketFactory getDefaultSSLSocketFactory() {
-        return defaultSSLSocketFactory;
-    }
-
-    /**
-     * The host name verifier used by this connection. It is initialized from
-     * the default hostname verifier
-     * {@link #setDefaultHostnameVerifier(javax.net.ssl.HostnameVerifier)} or
-     * {@link #getDefaultHostnameVerifier()}.
-     */
-    protected HostnameVerifier hostnameVerifier;
-
-    private SSLSocketFactory sslSocketFactory;
-
-    /**
-     * Creates a new {@code HttpsURLConnection} with the specified {@code URL}.
-     *
-     * @param url
-     *            the {@code URL} to connect to.
-     */
-    protected OkHttpsConnection(URL url) {
-        super(url);
-        hostnameVerifier = defaultHostnameVerifier;
-        sslSocketFactory = defaultSSLSocketFactory;
-    }
-
-    /**
-     * Returns the name of the cipher suite negotiated during the SSL handshake.
-     *
-     * @return the name of the cipher suite negotiated during the SSL handshake.
-     * @throws IllegalStateException
-     *             if no connection has been established yet.
-     */
-    public abstract String getCipherSuite();
-
-    /**
-     * Returns the list of local certificates used during the handshake. These
-     * certificates were sent to the peer.
-     *
-     * @return Returns the list of certificates used during the handshake with
-     *         the local identity certificate followed by CAs, or {@code null}
-     *         if no certificates were used during the handshake.
-     * @throws IllegalStateException
-     *             if no connection has been established yet.
-     */
-    public abstract Certificate[] getLocalCertificates();
-
-    /**
-     * Return the list of certificates identifying the peer during the
-     * handshake.
-     *
-     * @return the list of certificates identifying the peer with the peer's
-     *         identity certificate followed by CAs.
-     * @throws javax.net.ssl.SSLPeerUnverifiedException
-     *             if the identity of the peer has not been verified..
-     * @throws IllegalStateException
-     *             if no connection has been established yet.
-     */
-    public abstract Certificate[] getServerCertificates() throws SSLPeerUnverifiedException;
-
-    /**
-     * Returns the {@code Principal} identifying the peer.
-     *
-     * @return the {@code Principal} identifying the peer.
-     * @throws javax.net.ssl.SSLPeerUnverifiedException
-     *             if the identity of the peer has not been verified.
-     * @throws IllegalStateException
-     *             if no connection has been established yet.
-     */
-    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-        Certificate[] certs = getServerCertificates();
-        if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) {
-            throw new SSLPeerUnverifiedException("No server's end-entity certificate");
-        }
-        return ((X509Certificate) certs[0]).getSubjectX500Principal();
-    }
-
-    /**
-     * Returns the {@code Principal} used to identify the local host during the handshake.
-     *
-     * @return the {@code Principal} used to identify the local host during the handshake, or
-     *         {@code null} if none was used.
-     * @throws IllegalStateException
-     *             if no connection has been established yet.
-     */
-    public Principal getLocalPrincipal() {
-        Certificate[] certs = getLocalCertificates();
-        if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) {
-            return null;
-        }
-        return ((X509Certificate) certs[0]).getSubjectX500Principal();
-    }
-
-    /**
-     * Sets the hostname verifier for this instance.
-     *
-     * @param v
-     *            the hostname verifier for this instance.
-     * @throws IllegalArgumentException
-     *             if the specified verifier is {@code null}.
-     */
-    public void setHostnameVerifier(HostnameVerifier v) {
-        if (v == null) {
-            throw new IllegalArgumentException("HostnameVerifier is null");
-        }
-        hostnameVerifier = v;
-    }
-
-    /**
-     * Returns the hostname verifier used by this instance.
-     */
-    public HostnameVerifier getHostnameVerifier() {
-        return hostnameVerifier;
-    }
-
-    /**
-     * Sets the SSL socket factory for this instance.
-     *
-     * @param sf
-     *            the SSL socket factory to be used by this instance.
-     * @throws IllegalArgumentException
-     *             if the specified socket factory is {@code null}.
-     */
-    public void setSSLSocketFactory(SSLSocketFactory sf) {
-        if (sf == null) {
-            throw new IllegalArgumentException("SSLSocketFactory is null");
-        }
-        sslSocketFactory = sf;
-    }
-
-    /**
-     * Returns the SSL socket factory used by this instance.
-     */
-    public SSLSocketFactory getSSLSocketFactory() {
-        return sslSocketFactory;
-    }
-
-}
diff --git a/src/main/java/libcore/util/ExtendedResponseCache.java b/src/main/java/com/squareup/okhttp/OkResponseCache.java
similarity index 66%
rename from src/main/java/libcore/util/ExtendedResponseCache.java
rename to src/main/java/com/squareup/okhttp/OkResponseCache.java
index 2d89569..c0f7c56 100644
--- a/src/main/java/libcore/util/ExtendedResponseCache.java
+++ b/src/main/java/com/squareup/okhttp/OkResponseCache.java
@@ -13,43 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.squareup.okhttp;
 
-package libcore.util;
-
-import com.squareup.okhttp.OkHttpConnection;
 import java.io.IOException;
 import java.net.CacheResponse;
+import java.net.HttpURLConnection;
 
 /**
  * A response cache that supports statistics tracking and updating stored
- * responses. Implementations of {@link java.net.ResponseCache} should implement this
- * interface to receive additional support from the HTTP engine.
- *
- * @hide
+ * responses. Implementations of {@link java.net.ResponseCache} should implement
+ * this interface to receive additional support from the HTTP engine.
  */
-public interface ExtendedResponseCache {
-
-    /*
-     * This hidden interface is defined in a non-hidden package (java.net) so
-     * its @hide tag will be parsed by Doclava. This hides this interface from
-     * implementing classes' documentation.
-     */
+public interface OkResponseCache {
 
     /**
      * Track an HTTP response being satisfied by {@code source}.
-     * @hide
      */
     void trackResponse(ResponseSource source);
 
     /**
      * Track an conditional GET that was satisfied by this cache.
-     * @hide
      */
     void trackConditionalCacheHit();
 
     /**
      * Updates stored HTTP headers using a hit on a conditional GET.
-     * @hide
      */
-    void update(CacheResponse conditionalCacheHit, OkHttpConnection httpConnection) throws IOException;
+    void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
+            throws IOException;
 }
diff --git a/src/main/java/libcore/util/ResponseSource.java b/src/main/java/com/squareup/okhttp/ResponseSource.java
similarity index 70%
rename from src/main/java/libcore/util/ResponseSource.java
rename to src/main/java/com/squareup/okhttp/ResponseSource.java
index 8e7bfae..83388d6 100644
--- a/src/main/java/libcore/util/ResponseSource.java
+++ b/src/main/java/com/squareup/okhttp/ResponseSource.java
@@ -13,29 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package libcore.util;
+package com.squareup.okhttp;
 
 /**
- * Where the HTTP client should look for a response.
- *
- * @hide
+ * The source of an HTTP response.
  */
 public enum ResponseSource {
 
     /**
-     * Return the response from the cache immediately.
+     * The response was returned from the local cache.
      */
     CACHE,
 
     /**
-     * Make a conditional request to the host, returning the cache response if
-     * the cache is valid and the network response otherwise.
+     * The response is available in the cache but must be validated with the
+     * network. The cache result will be used if it is still valid; otherwise
+     * the network's response will be used.
      */
     CONDITIONAL_CACHE,
 
     /**
-     * Return the response from the network.
+     * The response was returned from the network.
      */
     NETWORK;
 
diff --git a/src/main/java/com/squareup/okhttp/TunnelRequest.java b/src/main/java/com/squareup/okhttp/TunnelRequest.java
new file mode 100644
index 0000000..39a820c
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/TunnelRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp;
+
+import static com.squareup.okhttp.internal.Util.getDefaultPort;
+import com.squareup.okhttp.internal.http.RawHeaders;
+
+/**
+ * Routing and authentication information sent to an HTTP proxy to create a
+ * HTTPS to an origin server. Everything in the tunnel request is sent
+ * unencrypted to the proxy server.
+ *
+ * <p>See <a href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section
+ * 5.2</a>.
+ */
+public final class TunnelRequest {
+    final String host;
+    final int port;
+    final String userAgent;
+    final String proxyAuthorization;
+
+    /**
+     * @param host the origin server's hostname. Not null.
+     * @param port the origin server's port, like 80 or 443.
+     * @param userAgent the client's user-agent. Not null.
+     * @param proxyAuthorization proxy authorization, or null if the proxy is
+     *     used without an authorization header.
+     */
+    public TunnelRequest(String host, int port, String userAgent, String proxyAuthorization) {
+        if (host == null) throw new NullPointerException("host == null");
+        if (userAgent == null) throw new NullPointerException("userAgent == null");
+        this.host = host;
+        this.port = port;
+        this.userAgent = userAgent;
+        this.proxyAuthorization = proxyAuthorization;
+    }
+
+    /**
+     * If we're creating a TLS tunnel, send only the minimum set of headers.
+     * This avoids sending potentially sensitive data like HTTP cookies to
+     * the proxy unencrypted.
+     */
+    RawHeaders getRequestHeaders() {
+        RawHeaders result = new RawHeaders();
+        result.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1");
+
+        // Always set Host and User-Agent.
+        result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
+        result.set("User-Agent", userAgent);
+
+        // Copy over the Proxy-Authorization header if it exists.
+        if (proxyAuthorization != null) {
+            result.set("Proxy-Authorization", proxyAuthorization);
+        }
+
+        // Always set the Proxy-Connection to Keep-Alive for the benefit of
+        // HTTP/1.0 proxies like Squid.
+        result.set("Proxy-Connection", "Keep-Alive");
+        return result;
+    }
+}
diff --git a/src/main/java/libcore/io/Base64.java b/src/main/java/com/squareup/okhttp/internal/Base64.java
similarity index 97%
rename from src/main/java/libcore/io/Base64.java
rename to src/main/java/com/squareup/okhttp/internal/Base64.java
index 96d3b9d..458e536 100644
--- a/src/main/java/libcore/io/Base64.java
+++ b/src/main/java/com/squareup/okhttp/internal/Base64.java
@@ -19,10 +19,10 @@
 * @author Alexander Y. Kleymenov
 */
 
-package libcore.io;
+package com.squareup.okhttp.internal;
 
+import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
 import java.io.UnsupportedEncodingException;
-import libcore.util.EmptyArray;
 
 /**
  * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder.
@@ -41,7 +41,7 @@
         int length = len / 4 * 3;
         // return an empty array on empty or short input without padding
         if (length == 0) {
-            return EmptyArray.BYTE;
+            return EMPTY_BYTE_ARRAY;
         }
         // temporary array
         byte[] out = new byte[length];
diff --git a/src/main/java/libcore/io/DiskLruCache.java b/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
similarity index 97%
rename from src/main/java/libcore/io/DiskLruCache.java
rename to src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
index 5b78838..96f6d96 100644
--- a/src/main/java/libcore/io/DiskLruCache.java
+++ b/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package libcore.io;
+package com.squareup.okhttp.internal;
 
+import static com.squareup.okhttp.internal.Util.UTF_8;
 import java.io.BufferedWriter;
 import java.io.Closeable;
 import java.io.EOFException;
@@ -41,8 +42,6 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import libcore.util.Charsets;
-import libcore.util.Libcore;
 
 /**
  * A cache that uses a bounded amount of space on a filesystem. Each cache
@@ -213,7 +212,7 @@
                 cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true));
                 return cache;
             } catch (IOException journalIsCorrupt) {
-                Libcore.logW("DiskLruCache " + directory + " is corrupt: "
+                Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
                         + journalIsCorrupt.getMessage() + ", removing");
                 cache.delete();
             }
@@ -228,7 +227,7 @@
 
     private void readJournal() throws IOException {
         StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile),
-                Charsets.US_ASCII);
+                Util.US_ASCII);
         try {
             String magic = reader.readLine();
             String version = reader.readLine();
@@ -252,7 +251,7 @@
                 }
             }
         } finally {
-            IoUtils.closeQuietly(reader);
+            Util.closeQuietly(reader);
         }
     }
 
@@ -344,7 +343,7 @@
     }
 
     private static void deleteIfExists(File file) throws IOException {
-        Libcore.deleteIfExists(file);
+        file.delete();
     }
 
     /**
@@ -460,7 +459,7 @@
                 }
                 if (!entry.getDirtyFile(i).exists()) {
                     editor.abort();
-                    Libcore.logW(
+                    Platform.get().logW(
                             "DiskLruCache: Newly created entry doesn't have file for index " + i);
                     return;
                 }
@@ -598,7 +597,7 @@
      */
     public void delete() throws IOException {
         close();
-        IoUtils.deleteContents(directory);
+        Util.deleteContents(directory);
     }
 
     private void validateKey(String key) {
@@ -609,7 +608,7 @@
     }
 
     private static String inputStreamToString(InputStream in) throws IOException {
-        return Streams.readFully(new InputStreamReader(in, Charsets.UTF_8));
+        return Util.readFully(new InputStreamReader(in, UTF_8));
     }
 
     /**
@@ -651,7 +650,7 @@
 
         @Override public void close() {
             for (InputStream in : ins) {
-                IoUtils.closeQuietly(in);
+                Util.closeQuietly(in);
             }
         }
     }
@@ -719,10 +718,10 @@
         public void set(int index, String value) throws IOException {
             Writer writer = null;
             try {
-                writer = new OutputStreamWriter(newOutputStream(index), Charsets.UTF_8);
+                writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
                 writer.write(value);
             } finally {
-                IoUtils.closeQuietly(writer);
+                Util.closeQuietly(writer);
             }
         }
 
diff --git a/src/main/java/com/squareup/okhttp/internal/Dns.java b/src/main/java/com/squareup/okhttp/internal/Dns.java
new file mode 100644
index 0000000..37fa609
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/internal/Dns.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Domain name service. Prefer this over {@link InetAddress#getAllByName} to
+ * make code more testable.
+ */
+public interface Dns {
+    Dns DEFAULT = new Dns() {
+        @Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
+            return InetAddress.getAllByName(host);
+        }
+    };
+
+    InetAddress[] getAllByName(String host) throws UnknownHostException;
+}
diff --git a/src/main/java/com/squareup/okhttp/internal/Platform.java b/src/main/java/com/squareup/okhttp/internal/Platform.java
new file mode 100644
index 0000000..ab71c62
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/internal/Platform.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal;
+
+import com.squareup.okhttp.OkHttpClient;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Access to Platform-specific features necessary for SPDY and advanced TLS.
+ *
+ * <h3>SPDY</h3>
+ * SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's
+ * available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It
+ * also requires a recent version of {@code DeflaterOutputStream} that is
+ * public API in Java 7 and callable via reflection in Android 4.1+.
+ */
+public class Platform {
+    private static final Platform PLATFORM = findPlatform();
+
+    private Constructor<DeflaterOutputStream> deflaterConstructor;
+
+    public static Platform get() {
+        return PLATFORM;
+    }
+
+    public void logW(String warning) {
+        System.out.println(warning);
+    }
+
+    public void tagSocket(Socket socket) throws SocketException {
+    }
+
+    public void untagSocket(Socket socket) throws SocketException {
+    }
+
+    public URI toUriLenient(URL url) throws URISyntaxException {
+        return url.toURI(); // this isn't as good as the built-in toUriLenient
+    }
+
+    /**
+     * Attempt a TLS connection with useful extensions enabled. This mode
+     * supports more features, but is less likely to be compatible with older
+     * HTTPS servers.
+     */
+    public void enableTlsExtensions(SSLSocket socket, String uriHost) {
+    }
+
+    /**
+     * Attempt a secure connection with basic functionality to maximize
+     * compatibility. Currently this uses SSL 3.0.
+     */
+    public void supportTlsIntolerantServer(SSLSocket socket) {
+        socket.setEnabledProtocols(new String[]{"SSLv3"});
+    }
+
+    /**
+     * Returns the negotiated protocol, or null if no protocol was negotiated.
+     */
+    public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+        return null;
+    }
+
+    /**
+     * Sets client-supported protocols on a socket to send to a server. The
+     * protocols are only sent if the socket implementation supports NPN.
+     */
+    public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+    }
+
+    /**
+     * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
+     * value blocks. This throws an {@link UnsupportedOperationException} on
+     * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH.
+     */
+    public OutputStream newDeflaterOutputStream(
+            OutputStream out, Deflater deflater, boolean syncFlush) {
+        try {
+            Constructor<DeflaterOutputStream> constructor = deflaterConstructor;
+            if (constructor == null) {
+                constructor = deflaterConstructor = DeflaterOutputStream.class.getConstructor(
+                        OutputStream.class, Deflater.class, boolean.class);
+            }
+            return constructor.newInstance(out, deflater, syncFlush);
+        } catch (NoSuchMethodException e) {
+            throw new UnsupportedOperationException("Cannot SPDY; no SYNC_FLUSH available");
+        } catch (InvocationTargetException e) {
+            throw e.getCause() instanceof RuntimeException
+                    ? (RuntimeException) e.getCause()
+                    : new RuntimeException(e.getCause());
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Attempt to match the host runtime to a capable Platform implementation.
+     */
+    private static Platform findPlatform() {
+        // Attempt to find Android 2.3+ APIs.
+        Class<?> openSslSocketClass;
+        Method setUseSessionTickets;
+        Method setHostname;
+        try {
+            openSslSocketClass = Class.forName(
+                    "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+            setUseSessionTickets = openSslSocketClass.getMethod(
+                    "setUseSessionTickets", boolean.class);
+            setHostname = openSslSocketClass.getMethod("setHostname", String.class);
+
+            // Attempt to find Android 4.1+ APIs.
+            try {
+                Method setNpnProtocols = openSslSocketClass.getMethod(
+                        "setNpnProtocols", byte[].class);
+                Method getNpnSelectedProtocol = openSslSocketClass.getMethod(
+                        "getNpnSelectedProtocol");
+                return new Android41(openSslSocketClass, setUseSessionTickets, setHostname,
+                        setNpnProtocols, getNpnSelectedProtocol);
+            } catch (NoSuchMethodException ignored) {
+                return new Android23(openSslSocketClass, setUseSessionTickets, setHostname);
+            }
+        } catch (ClassNotFoundException ignored) {
+            // This isn't an Android runtime.
+        } catch (NoSuchMethodException ignored) {
+            // This isn't Android 2.3 or better.
+        }
+
+        // Attempt to find the Jetty's NPN extension for OpenJDK.
+        try {
+            String npnClassName = "org.eclipse.jetty.npn.NextProtoNego";
+            Class<?> nextProtoNegoClass = Class.forName(npnClassName);
+            Class<?> providerClass = Class.forName(npnClassName + "$Provider");
+            Class<?> clientProviderClass = Class.forName(npnClassName + "$ClientProvider");
+            Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
+            Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
+            return new JdkWithJettyNpnPlatform(putMethod, getMethod, clientProviderClass);
+        } catch (ClassNotFoundException ignored) {
+            return new Platform(); // NPN isn't on the classpath.
+        } catch (NoSuchMethodException ignored) {
+            return new Platform(); // The NPN version isn't what we expect.
+        }
+    }
+
+    /**
+     * Android version 2.3 and newer support TLS session tickets and server name
+     * indication (SNI).
+     */
+    private static class Android23 extends Platform {
+        protected final Class<?> openSslSocketClass;
+        private final Method setUseSessionTickets;
+        private final Method setHostname;
+
+        private Android23(Class<?> openSslSocketClass, Method setUseSessionTickets,
+                Method setHostname) {
+            this.openSslSocketClass = openSslSocketClass;
+            this.setUseSessionTickets = setUseSessionTickets;
+            this.setHostname = setHostname;
+        }
+
+        @Override public void enableTlsExtensions(SSLSocket socket, String uriHost) {
+            super.enableTlsExtensions(socket, uriHost);
+            if (openSslSocketClass.isInstance(socket)) {
+                // This is Android: use reflection on OpenSslSocketImpl.
+                try {
+                    setUseSessionTickets.invoke(socket, true);
+                    setHostname.invoke(socket, uriHost);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e);
+                } catch (IllegalAccessException e) {
+                    throw new AssertionError(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Android version 4.1 and newer support NPN.
+     */
+    private static class Android41 extends Android23 {
+        private final Method setNpnProtocols;
+        private final Method getNpnSelectedProtocol;
+
+        private Android41(Class<?> openSslSocketClass, Method setUseSessionTickets,
+                Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) {
+            super(openSslSocketClass, setUseSessionTickets, setHostname);
+            this.setNpnProtocols = setNpnProtocols;
+            this.getNpnSelectedProtocol = getNpnSelectedProtocol;
+        }
+
+        @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+            if (!openSslSocketClass.isInstance(socket)) {
+                return;
+            }
+            try {
+                setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
+            } catch (IllegalAccessException e) {
+                throw new AssertionError(e);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+            if (!openSslSocketClass.isInstance(socket)) {
+                return null;
+            }
+            try {
+                return (byte[]) getNpnSelectedProtocol.invoke(socket);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    /**
+     * OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class
+     * path.
+     */
+    private static class JdkWithJettyNpnPlatform extends Platform {
+        private final Method getMethod;
+        private final Method putMethod;
+        private final Class<?> clientProviderClass;
+
+        public JdkWithJettyNpnPlatform(
+                Method putMethod, Method getMethod, Class<?> clientProviderClass) {
+            this.putMethod = putMethod;
+            this.getMethod = getMethod;
+            this.clientProviderClass = clientProviderClass;
+        }
+
+        @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+            try {
+                List<String> strings = new ArrayList<String>();
+                for (int i = 0; i < npnProtocols.length;) {
+                    int length = npnProtocols[i++];
+                    strings.add(new String(npnProtocols, i, length, "US-ASCII"));
+                    i += length;
+                }
+                Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
+                        new Class[] {clientProviderClass}, new JettyNpnProvider(strings));
+                putMethod.invoke(null, socket, provider);
+            } catch (UnsupportedEncodingException e) {
+                throw new AssertionError(e);
+            } catch (InvocationTargetException e) {
+                throw new AssertionError(e);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+            try {
+                JettyNpnProvider provider = (JettyNpnProvider) Proxy.getInvocationHandler(
+                        getMethod.invoke(null, socket));
+                if (!provider.unsupported && provider.selected == null) {
+                    Logger logger = Logger.getLogger(OkHttpClient.class.getName());
+                    logger.log(Level.INFO, "NPN callback dropped so SPDY is disabled. "
+                            + "Is npn-boot on the boot class path?");
+                    return null;
+                }
+                return provider.unsupported
+                        ? null
+                        : provider.selected.getBytes("US-ASCII");
+            } catch (UnsupportedEncodingException e) {
+                throw new AssertionError();
+            } catch (InvocationTargetException e) {
+                throw new AssertionError();
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            }
+        }
+    }
+
+    /**
+     * Handle the methods of NextProtoNego's ClientProvider and ServerProvider
+     * without a compile-time dependency on those interfaces.
+     */
+    private static class JettyNpnProvider implements InvocationHandler {
+        private final List<String> clientProtocols;
+        private boolean unsupported;
+        private String selected;
+
+        public JettyNpnProvider(List<String> clientProtocols) {
+            this.clientProtocols = clientProtocols;
+        }
+
+        @Override public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            String methodName = method.getName();
+            Class<?> returnType = method.getReturnType();
+            if (methodName.equals("supports") && boolean.class == returnType) {
+                return true;
+            } else if (methodName.equals("unsupported") && void.class == returnType) {
+                this.unsupported = true;
+                return null;
+            } else if (methodName.equals("selectProtocol") && String.class == returnType
+                    && args.length == 1 && (args[0] == null || args[0] instanceof List)) {
+                // TODO: use OpenSSL's algorithm which uses both lists
+                List<?> serverProtocols = (List) args[0];
+                System.out.println("CLIENT PROTOCOLS: " + clientProtocols + " SERVER PROTOCOLS: " + serverProtocols);
+                this.selected = clientProtocols.get(0);
+                return selected;
+            } else {
+                return method.invoke(this, args);
+            }
+        }
+    }
+}
diff --git a/src/main/java/libcore/io/StrictLineReader.java b/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java
similarity index 93%
rename from src/main/java/libcore/io/StrictLineReader.java
rename to src/main/java/com/squareup/okhttp/internal/StrictLineReader.java
index dabb516..9011b2c 100644
--- a/src/main/java/libcore/io/StrictLineReader.java
+++ b/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package libcore.io;
+package com.squareup.okhttp.internal;
 
+import static com.squareup.okhttp.internal.Util.ISO_8859_1;
+import static com.squareup.okhttp.internal.Util.US_ASCII;
+import static com.squareup.okhttp.internal.Util.UTF_8;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
-import libcore.util.Charsets;
 
 /**
  * Buffers input from an {@link InputStream} for reading lines.
@@ -78,7 +80,7 @@
      * @throws IllegalArgumentException for negative or zero {@code capacity}.
      */
     public StrictLineReader(InputStream in, int capacity) {
-        this(in, capacity, Charsets.US_ASCII);
+        this(in, capacity, US_ASCII);
     }
 
     /**
@@ -112,8 +114,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity <= 0");
         }
-        if (!(charset.equals(Charsets.US_ASCII) || charset.equals(Charsets.UTF_8)
-                || charset.equals(Charsets.ISO_8859_1))) {
+        if (!(charset.equals(US_ASCII) || charset.equals(UTF_8) || charset.equals(ISO_8859_1))) {
             throw new IllegalArgumentException("Unsupported encoding");
         }
 
@@ -213,16 +214,6 @@
     }
 
     /**
-     * Check whether there was an unterminated line at end of input after the line reader reported
-     * end-of-input with EOFException. The value is meaningless in any other situation.
-     *
-     * @return true if there was an unterminated line at end of input.
-     */
-    public boolean hasUnterminatedLine() {
-        return end == -1;
-    }
-
-    /**
      * Reads new input data into the buffer. Call only with pos == end or end == -1,
      * depending on the desired outcome if the function throws.
      *
diff --git a/src/main/java/com/squareup/okhttp/internal/Util.java b/src/main/java/com/squareup/okhttp/internal/Util.java
new file mode 100644
index 0000000..f47362c
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/internal/Util.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Junk drawer of utility methods.
+ */
+public final class Util {
+    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    /** A cheap and type-safe constant for the ISO-8859-1 Charset. */
+    public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
+
+    /** A cheap and type-safe constant for the US-ASCII Charset. */
+    public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+    /** A cheap and type-safe constant for the UTF-8 Charset. */
+    public static final Charset UTF_8 = Charset.forName("UTF-8");
+    private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
+
+    private Util() {
+    }
+
+    public static int getEffectivePort(URI uri) {
+        return getEffectivePort(uri.getScheme(), uri.getPort());
+    }
+
+    public static int getEffectivePort(URL url) {
+        return getEffectivePort(url.getProtocol(), url.getPort());
+    }
+
+    private static int getEffectivePort(String scheme, int specifiedPort) {
+        return specifiedPort != -1
+                ? specifiedPort
+                : getDefaultPort(scheme);
+    }
+
+    public static int getDefaultPort(String scheme) {
+        if ("http".equalsIgnoreCase(scheme)) {
+            return 80;
+        } else if ("https".equalsIgnoreCase(scheme)) {
+            return 443;
+        } else {
+            return -1;
+        }
+    }
+
+    public static void checkOffsetAndCount(int arrayLength, int offset, int count) {
+        if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) {
+        if (order == ByteOrder.BIG_ENDIAN) {
+            dst[offset++] = (byte) ((value >> 24) & 0xff);
+            dst[offset++] = (byte) ((value >> 16) & 0xff);
+            dst[offset++] = (byte) ((value >>  8) & 0xff);
+            dst[offset  ] = (byte) ((value >>  0) & 0xff);
+        } else {
+            dst[offset++] = (byte) ((value >>  0) & 0xff);
+            dst[offset++] = (byte) ((value >>  8) & 0xff);
+            dst[offset++] = (byte) ((value >> 16) & 0xff);
+            dst[offset  ] = (byte) ((value >> 24) & 0xff);
+        }
+    }
+
+    /**
+     * Returns true if two possibly-null objects are equal.
+     */
+    public static boolean equal(Object a, Object b) {
+        return a == b || (a != null && a.equals(b));
+    }
+
+    /**
+     * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
+     */
+    public static void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    /**
+     * Closes {@code a} and {@code b}. If either close fails, this completes
+     * the other close and rethrows the first encountered exception.
+     */
+    public static void closeAll(Closeable a, Closeable b) throws IOException {
+        Throwable thrown = null;
+        try {
+            a.close();
+        } catch (Throwable e) {
+            thrown = e;
+        }
+        try {
+            b.close();
+        } catch (Throwable e) {
+            if (thrown == null) thrown = e;
+        }
+        if (thrown == null) return;
+        if (thrown instanceof IOException) throw (IOException) thrown;
+        if (thrown instanceof RuntimeException) throw (RuntimeException) thrown;
+        if (thrown instanceof Error) throw (Error) thrown;
+        throw new AssertionError(thrown);
+    }
+
+    /**
+     * Recursively delete everything in {@code dir}.
+     */
+    // TODO: this should specify paths as Strings rather than as Files
+    public static void deleteContents(File dir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files == null) {
+            throw new IllegalArgumentException("not a directory: " + dir);
+        }
+        for (File file : files) {
+            if (file.isDirectory()) {
+                deleteContents(file);
+            }
+            if (!file.delete()) {
+                throw new IOException("failed to delete file: " + file);
+            }
+        }
+    }
+
+    /**
+     * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int).
+     * InputStream assumes that you implement InputStream.read(int) and provides default
+     * implementations of the others, but often the opposite is more efficient.
+     */
+    public static int readSingleByte(InputStream in) throws IOException {
+        byte[] buffer = new byte[1];
+        int result = in.read(buffer, 0, 1);
+        return (result != -1) ? buffer[0] & 0xff : -1;
+    }
+
+    /**
+     * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int).
+     * OutputStream assumes that you implement OutputStream.write(int) and provides default
+     * implementations of the others, but often the opposite is more efficient.
+     */
+    public static void writeSingleByte(OutputStream out, int b) throws IOException {
+        byte[] buffer = new byte[1];
+        buffer[0] = (byte) (b & 0xff);
+        out.write(buffer);
+    }
+
+    /**
+     * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available.
+     */
+    public static void readFully(InputStream in, byte[] dst) throws IOException {
+        readFully(in, dst, 0, dst.length);
+    }
+
+    /**
+     * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws
+     * EOFException if insufficient bytes are available.
+     *
+     * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}.
+     */
+    public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) throws IOException {
+        if (byteCount == 0) {
+            return;
+        }
+        if (in == null) {
+            throw new NullPointerException("in == null");
+        }
+        if (dst == null) {
+            throw new NullPointerException("dst == null");
+        }
+        checkOffsetAndCount(dst.length, offset, byteCount);
+        while (byteCount > 0) {
+            int bytesRead = in.read(dst, offset, byteCount);
+            if (bytesRead < 0) {
+                throw new EOFException();
+            }
+            offset += bytesRead;
+            byteCount -= bytesRead;
+        }
+    }
+
+    /**
+     * Returns the remainder of 'reader' as a string, closing it when done.
+     */
+    public static String readFully(Reader reader) throws IOException {
+        try {
+            StringWriter writer = new StringWriter();
+            char[] buffer = new char[1024];
+            int count;
+            while ((count = reader.read(buffer)) != -1) {
+                writer.write(buffer, 0, count);
+            }
+            return writer.toString();
+        } finally {
+            reader.close();
+        }
+    }
+
+    public static void skipAll(InputStream in) throws IOException {
+        do {
+            in.skip(Long.MAX_VALUE);
+        } while (in.read() != -1);
+    }
+
+    /**
+     * Call {@code in.read()} repeatedly until either the stream is exhausted or
+     * {@code byteCount} bytes have been read.
+     *
+     * <p>This method reuses the skip buffer but is careful to never use it at
+     * the same time that another stream is using it. Otherwise streams that use
+     * the caller's buffer for consistency checks like CRC could be clobbered by
+     * other threads. A thread-local buffer is also insufficient because some
+     * streams may call other streams in their skip() method, also clobbering the
+     * buffer.
+     */
+    public static long skipByReading(InputStream in, long byteCount) throws IOException {
+        // acquire the shared skip buffer.
+        byte[] buffer = skipBuffer.getAndSet(null);
+        if (buffer == null) {
+            buffer = new byte[4096];
+        }
+
+        long skipped = 0;
+        while (skipped < byteCount) {
+            int toRead = (int) Math.min(byteCount - skipped, buffer.length);
+            int read = in.read(buffer, 0, toRead);
+            if (read == -1) {
+                break;
+            }
+            skipped += read;
+            if (read < toRead) {
+                break;
+            }
+        }
+
+        // release the shared skip buffer.
+        skipBuffer.set(buffer);
+
+        return skipped;
+    }
+
+    /**
+     * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
+     * Returns the total number of bytes transferred.
+     */
+    public static int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
+
+    /**
+     * Returns the ASCII characters up to but not including the next "\r\n", or
+     * "\n".
+     *
+     * @throws java.io.EOFException if the stream is exhausted before the next newline
+     *     character.
+     */
+    public static String readAsciiLine(InputStream in) throws IOException {
+        // TODO: support UTF-8 here instead
+        StringBuilder result = new StringBuilder(80);
+        while (true) {
+            int c = in.read();
+            if (c == -1) {
+                throw new EOFException();
+            } else if (c == '\n') {
+                break;
+            }
+
+            result.append((char) c);
+        }
+        int length = result.length();
+        if (length > 0 && result.charAt(length - 1) == '\r') {
+            result.setLength(length - 1);
+        }
+        return result.toString();
+    }
+}
diff --git a/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
similarity index 96%
rename from src/main/java/libcore/net/http/AbstractHttpInputStream.java
rename to src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
index 70f76b7..d915552 100644
--- a/src/main/java/libcore/net/http/AbstractHttpInputStream.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.internal.Util;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.CacheRequest;
-import libcore.io.Streams;
 
 /**
  * An input stream for the body of an HTTP response.
@@ -60,7 +60,7 @@
      * need to override the latter.
      */
     @Override public final int read() throws IOException {
-        return Streams.readSingleByte(this);
+        return Util.readSingleByte(this);
     }
 
     protected final void checkNotClosed() throws IOException {
diff --git a/src/main/java/libcore/net/http/AbstractHttpOutputStream.java b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
similarity index 96%
rename from src/main/java/libcore/net/http/AbstractHttpOutputStream.java
rename to src/main/java/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
index 145bc50..5d835cc 100644
--- a/src/main/java/libcore/net/http/AbstractHttpOutputStream.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/src/main/java/libcore/net/http/HeaderParser.java b/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java
similarity index 98%
rename from src/main/java/libcore/net/http/HeaderParser.java
rename to src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java
index b35db78..0dd096e 100644
--- a/src/main/java/libcore/net/http/HeaderParser.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 final class HeaderParser {
 
diff --git a/src/main/java/libcore/net/http/HttpAuthenticator.java b/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
similarity index 95%
rename from src/main/java/libcore/net/http/HttpAuthenticator.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
index 882d16f..70104a5 100644
--- a/src/main/java/libcore/net/http/HttpAuthenticator.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java
@@ -14,12 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
-import static com.squareup.okhttp.OkHttpConnection.HTTP_PROXY_AUTH;
-import static com.squareup.okhttp.OkHttpConnection.HTTP_UNAUTHORIZED;
+import com.squareup.okhttp.internal.Base64;
 import java.io.IOException;
 import java.net.Authenticator;
+import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.PasswordAuthentication;
@@ -27,7 +28,6 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
-import libcore.io.Base64;
 
 /**
  * Handles HTTP authentication headers from origin and proxy servers.
@@ -42,7 +42,7 @@
      * @return true if credentials have been added to successorRequestHeaders
      *     and another request should be attempted.
      */
-    public static boolean processAuthHeader(int responseCode, RawHeaders responeHeaders,
+    public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders,
             RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException {
         if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
             throw new IllegalArgumentException();
@@ -52,7 +52,7 @@
         String challengeHeader = responseCode == HTTP_PROXY_AUTH
                 ? "Proxy-Authenticate"
                 : "WWW-Authenticate";
-        String credentials = getCredentials(responeHeaders, challengeHeader, proxy, url);
+        String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url);
         if (credentials == null) {
             return false; // Could not find credentials so end the request cycle.
         }
diff --git a/src/main/java/libcore/net/http/HttpDate.java b/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
similarity index 97%
rename from src/main/java/libcore/net/http/HttpDate.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
index 41ae5ef..41f03fa 100644
--- a/src/main/java/libcore/net/http/HttpDate.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 import java.text.DateFormat;
 import java.text.ParseException;
@@ -26,7 +26,7 @@
 /**
  * Best-effort parser for HTTP dates.
  */
-public final class HttpDate {
+final class HttpDate {
 
     /**
      * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
diff --git a/src/main/java/libcore/net/http/HttpEngine.java b/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
similarity index 83%
rename from src/main/java/libcore/net/http/HttpEngine.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
index af3b831..22483ac 100644
--- a/src/main/java/libcore/net/http/HttpEngine.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
@@ -15,9 +15,19 @@
  *  limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
-import com.squareup.okhttp.OkHttpConnection;
+import com.squareup.okhttp.Address;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.OkResponseCache;
+import com.squareup.okhttp.ResponseSource;
+import com.squareup.okhttp.TunnelRequest;
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
+import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
+import static com.squareup.okhttp.internal.Util.getDefaultPort;
+import static com.squareup.okhttp.internal.Util.getEffectivePort;
+import com.squareup.okhttp.internal.Dns;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,11 +35,12 @@
 import java.net.CacheRequest;
 import java.net.CacheResponse;
 import java.net.CookieHandler;
+import java.net.HttpURLConnection;
 import java.net.Proxy;
-import java.net.ResponseCache;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.net.UnknownHostException;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -38,11 +49,6 @@
 import java.util.zip.GZIPInputStream;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLSocketFactory;
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
-import libcore.util.ExtendedResponseCache;
-import libcore.util.Libcore;
-import libcore.util.ResponseSource;
 
 /**
  * Handles a single HTTP request/response pair. Each HTTP engine follows this
@@ -75,19 +81,9 @@
             return result;
         }
         @Override public InputStream getBody() throws IOException {
-            return new ByteArrayInputStream(EmptyArray.BYTE);
+            return new ByteArrayInputStream(EMPTY_BYTE_ARRAY);
         }
     };
-    public static final int DEFAULT_CHUNK_LENGTH = 1024;
-
-    public static final String OPTIONS = "OPTIONS";
-    public static final String GET = "GET";
-    public static final String HEAD = "HEAD";
-    public static final String POST = "POST";
-    public static final String PUT = "PUT";
-    public static final String DELETE = "DELETE";
-    public static final String TRACE = "TRACE";
-
     public static final int HTTP_CONTINUE = 100;
 
     protected final HttpURLConnectionImpl policy;
@@ -96,7 +92,8 @@
 
     private ResponseSource responseSource;
 
-    protected HttpConnection connection;
+    protected Connection connection;
+    protected RouteSelector routeSelector;
     private OutputStream requestBodyOut;
 
     private Transport transport;
@@ -104,7 +101,6 @@
     private InputStream responseTransferIn;
     private InputStream responseBodyIn;
 
-    private final ResponseCache responseCache = ResponseCache.getDefault();
     private CacheResponse cacheResponse;
     private CacheRequest cacheRequest;
 
@@ -151,14 +147,14 @@
      *     release it when it is unneeded.
      */
     public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
-            HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException {
+            Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
         this.policy = policy;
         this.method = method;
         this.connection = connection;
         this.requestBodyOut = requestBodyOut;
 
         try {
-            uri = Libcore.toUriLenient(policy.getURL());
+            uri = Platform.get().toUriLenient(policy.getURL());
         } catch (URISyntaxException e) {
             throw new IOException(e);
         }
@@ -182,8 +178,8 @@
 
         prepareRawRequestHeaders();
         initResponseSource();
-        if (responseCache instanceof ExtendedResponseCache) {
-            ((ExtendedResponseCache) responseCache).trackResponse(responseSource);
+        if (policy.responseCache instanceof OkResponseCache) {
+            ((OkResponseCache) policy.responseCache).trackResponse(responseSource);
         }
 
         /*
@@ -194,7 +190,7 @@
          */
         if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
             if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
-                IoUtils.closeQuietly(cachedResponseBody);
+                Util.closeQuietly(cachedResponseBody);
             }
             this.responseSource = ResponseSource.CACHE;
             this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
@@ -206,7 +202,7 @@
         if (responseSource.requiresConnection()) {
             sendSocketRequest();
         } else if (connection != null) {
-            HttpConnectionPool.INSTANCE.recycle(connection);
+            policy.connectionPool.recycle(connection);
             connection = null;
         }
     }
@@ -217,11 +213,11 @@
      */
     private void initResponseSource() throws IOException {
         responseSource = ResponseSource.NETWORK;
-        if (!policy.getUseCaches() || responseCache == null) {
+        if (!policy.getUseCaches() || policy.responseCache == null) {
             return;
         }
 
-        CacheResponse candidate = responseCache.get(uri, method,
+        CacheResponse candidate = policy.responseCache.get(uri, method,
                 requestHeaders.getHeaders().toMultimap(false));
         if (candidate == null) {
             return;
@@ -232,7 +228,7 @@
         if (!acceptCacheResponseType(candidate)
                 || responseHeadersMap == null
                 || cachedResponseBody == null) {
-            IoUtils.closeQuietly(cachedResponseBody);
+            Util.closeQuietly(cachedResponseBody);
             return;
         }
 
@@ -246,7 +242,7 @@
         } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
             this.cacheResponse = candidate;
         } else if (responseSource == ResponseSource.NETWORK) {
-            IoUtils.closeQuietly(cachedResponseBody);
+            Util.closeQuietly(cachedResponseBody);
         } else {
             throw new AssertionError();
         }
@@ -261,7 +257,7 @@
             throw new IllegalStateException();
         }
 
-        transport = connection.newTransport(this);
+        transport = (Transport) connection.newTransport(this);
 
         if (hasRequestBody() && requestBodyOut == null) {
             // Create a request body if we don't have one already. We'll already
@@ -273,14 +269,30 @@
     /**
      * Connect to the origin server either directly or via a proxy.
      */
-    protected void connect() throws IOException {
+    protected final void connect() throws IOException {
         if (connection != null) {
             return;
         }
-        connection = HttpConnection.connect(uri, getSslSocketFactory(), getHostnameVerifier(),
-                policy.getProxy(), policy.getConnectTimeout(), policy.getReadTimeout(),
-                getTunnelConfig());
-        Proxy proxy = connection.getAddress().getProxy();
+        if (routeSelector == null) {
+            String uriHost = uri.getHost();
+            if (uriHost == null) {
+                throw new UnknownHostException(uri.toString());
+            }
+            Address address = new Address(uriHost, getEffectivePort(uri),
+                    getSslSocketFactory(), getHostnameVerifier(), policy.getProxy());
+            routeSelector = new RouteSelector(
+                    address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT);
+        }
+        connection = routeSelector.next();
+        if (!connection.isRecycled()) {
+            connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(),
+                    getTunnelConfig());
+            if (connection.isSpdy()) {
+                policy.connectionPool.share(connection);
+            }
+        }
+        connected(connection);
+        Proxy proxy = connection.getProxy();
         if (proxy != null) {
             policy.setProxy(proxy);
             // Add the authority to the request line when we're using a proxy.
@@ -289,6 +301,13 @@
     }
 
     /**
+     * Called after a socket connection has been created or retrieved from the
+     * pool. Subclasses use this hook to get a reference to the TLS data.
+     */
+    protected void connected(Connection connection) {
+    }
+
+    /**
      * @param body the response body, or null if it doesn't exist or isn't
      *     available.
      */
@@ -303,7 +322,7 @@
     }
 
     boolean hasRequestBody() {
-        return method == POST || method == PUT;
+        return method.equals("POST") || method.equals("PUT");
     }
 
     /**
@@ -349,7 +368,7 @@
         return cacheResponse;
     }
 
-    public final HttpConnection getConnection() {
+    public final Connection getConnection() {
         return connection;
     }
 
@@ -368,7 +387,7 @@
 
     private void maybeCache() throws IOException {
         // Are we caching at all?
-        if (!policy.getUseCaches() || responseCache == null) {
+        if (!policy.getUseCaches() || policy.responseCache == null) {
             return;
         }
 
@@ -378,10 +397,10 @@
         }
 
         // Offer this request to the cache.
-        cacheRequest = responseCache.put(uri, getHttpConnectionToCache());
+        cacheRequest = policy.responseCache.put(uri, getHttpConnectionToCache());
     }
 
-    protected OkHttpConnection getHttpConnectionToCache() {
+    protected HttpURLConnection getHttpConnectionToCache() {
         return policy;
     }
 
@@ -394,7 +413,7 @@
     public final void automaticallyReleaseConnectionToPool() {
         automaticallyReleaseConnectionToPool = true;
         if (connection != null && connectionReleased) {
-            HttpConnectionPool.INSTANCE.recycle(connection);
+            policy.connectionPool.recycle(connection);
             connection = null;
         }
     }
@@ -407,17 +426,17 @@
     public final void release(boolean reusable) {
         // If the response body comes from the cache, close it.
         if (responseBodyIn == cachedResponseBody) {
-            IoUtils.closeQuietly(responseBodyIn);
+            Util.closeQuietly(responseBodyIn);
         }
 
         if (!connectionReleased && connection != null) {
             connectionReleased = true;
 
             if (!reusable || !transport.makeReusable(requestBodyOut, responseTransferIn)) {
-                connection.closeSocketAndStreams();
+                Util.closeQuietly(connection);
                 connection = null;
             } else if (automaticallyReleaseConnectionToPool) {
-                HttpConnectionPool.INSTANCE.recycle(connection);
+                policy.connectionPool.recycle(connection);
                 connection = null;
             }
         }
@@ -445,7 +464,7 @@
         int responseCode = responseHeaders.getHeaders().getResponseCode();
 
         // HEAD requests never yield a body regardless of the response headers.
-        if (method == HEAD) {
+        if (method.equals("HEAD")) {
             return false;
         }
 
@@ -484,15 +503,13 @@
             requestHeaders.setHost(getOriginAddress(policy.getURL()));
         }
 
-        // TODO: this shouldn't be set for SPDY (it's ignored)
-        if ((connection == null || connection.httpMinorVersion != 0)
+        if ((connection == null || connection.getHttpMinorVersion() != 0)
                 && requestHeaders.getConnection() == null) {
             requestHeaders.setConnection("Keep-Alive");
         }
 
         if (requestHeaders.getAcceptEncoding() == null) {
             transparentGzip = true;
-            // TODO: this shouldn't be set for SPDY (it isn't necessary)
             requestHeaders.setAcceptEncoding("gzip");
         }
 
@@ -505,7 +522,7 @@
             requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
         }
 
-        CookieHandler cookieHandler = CookieHandler.getDefault();
+        CookieHandler cookieHandler = policy.cookieHandler;
         if (cookieHandler != null) {
             requestHeaders.addCookies(
                     cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
@@ -518,7 +535,7 @@
      * it needs to be set even if the transport is SPDY.
      */
     String getRequestLine() {
-        String protocol = (connection == null || connection.httpMinorVersion != 0)
+        String protocol = (connection == null || connection.getHttpMinorVersion() != 0)
                 ? "HTTP/1.1"
                 : "HTTP/1.0";
         return method + " " + requestString() + " " + protocol;
@@ -577,7 +594,7 @@
         return null;
     }
 
-    public static final String getDefaultUserAgent() {
+    public static String getDefaultUserAgent() {
         String agent = System.getProperty("http.agent");
         return agent != null ? agent : ("Java" + System.getProperty("java.version"));
     }
@@ -585,7 +602,7 @@
     public static String getOriginAddress(URL url) {
         int port = url.getPort();
         String result = url.getHost();
-        if (port > 0 && port != Libcore.getDefaultPort(url.getProtocol())) {
+        if (port > 0 && port != getDefaultPort(url.getProtocol())) {
             result = result + ":" + port;
         }
         return result;
@@ -633,14 +650,15 @@
                 release(true);
                 ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
                 setResponse(combinedHeaders, cachedResponseBody);
-                if (responseCache instanceof ExtendedResponseCache) {
-                    ExtendedResponseCache httpResponseCache = (ExtendedResponseCache) responseCache;
+                if (policy.responseCache instanceof OkResponseCache) {
+                    OkResponseCache httpResponseCache
+                            = (OkResponseCache) policy.responseCache;
                     httpResponseCache.trackConditionalCacheHit();
                     httpResponseCache.update(cacheResponse, getHttpConnectionToCache());
                 }
                 return;
             } else {
-                IoUtils.closeQuietly(cachedResponseBody);
+                Util.closeQuietly(cachedResponseBody);
             }
         }
 
@@ -651,7 +669,7 @@
         initContentStream(transport.getTransferStream(cacheRequest));
     }
 
-    protected HttpConnection.TunnelConfig getTunnelConfig() {
+    protected TunnelRequest getTunnelConfig() {
         return null;
     }
 }
diff --git a/src/main/java/libcore/net/http/HttpResponseCache.java b/src/main/java/com/squareup/okhttp/internal/http/HttpResponseCache.java
similarity index 92%
rename from src/main/java/libcore/net/http/HttpResponseCache.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpResponseCache.java
index 653a920..4bdd8fc 100644
--- a/src/main/java/libcore/net/http/HttpResponseCache.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpResponseCache.java
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
-import com.squareup.okhttp.OkHttpConnection;
-import com.squareup.okhttp.OkHttpsConnection;
-
+import com.squareup.okhttp.OkResponseCache;
+import com.squareup.okhttp.ResponseSource;
+import com.squareup.okhttp.internal.Util;
+import static com.squareup.okhttp.internal.Util.US_ASCII;
+import static com.squareup.okhttp.internal.Util.UTF_8;
+import com.squareup.okhttp.internal.Base64;
+import com.squareup.okhttp.internal.DiskLruCache;
+import com.squareup.okhttp.internal.StrictLineReader;
 import java.io.BufferedWriter;
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -32,6 +37,7 @@
 import java.io.Writer;
 import java.net.CacheRequest;
 import java.net.CacheResponse;
+import java.net.HttpURLConnection;
 import java.net.ResponseCache;
 import java.net.SecureCacheResponse;
 import java.net.URI;
@@ -47,22 +53,19 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLPeerUnverifiedException;
-import libcore.io.Base64;
-import libcore.io.DiskLruCache;
-import libcore.io.IoUtils;
-import libcore.io.StrictLineReader;
-import libcore.util.Charsets;
-import libcore.util.ExtendedResponseCache;
-import libcore.util.IntegralToString;
-import libcore.util.ResponseSource;
 
 /**
  * Cache responses in a directory on the file system. Most clients should use
  * {@code android.net.HttpResponseCache}, the stable, documented front end for
  * this.
  */
-public final class HttpResponseCache extends ResponseCache implements ExtendedResponseCache {
+public final class HttpResponseCache extends ResponseCache implements OkResponseCache {
+    private static final char[] DIGITS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
     // TODO: add APIs to iterate the cache?
     private static final int VERSION = 201105;
     private static final int ENTRY_METADATA = 0;
@@ -86,7 +89,7 @@
         try {
             MessageDigest messageDigest = MessageDigest.getInstance("MD5");
             byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8"));
-            return IntegralToString.bytesToHexString(md5bytes, false);
+            return bytesToHexString(md5bytes);
         } catch (NoSuchAlgorithmException e) {
             throw new AssertionError(e);
         } catch (UnsupportedEncodingException e) {
@@ -94,6 +97,17 @@
         }
     }
 
+    private static String bytesToHexString(byte[] bytes) {
+        char[] digits = DIGITS;
+        char[] buf = new char[bytes.length * 2];
+        int c = 0;
+        for (byte b : bytes) {
+            buf[c++] = digits[(b >> 4) & 0xf];
+            buf[c++] = digits[b & 0xf];
+        }
+        return new String(buf);
+    }
+
     @Override public CacheResponse get(URI uri, String requestMethod,
             Map<String, List<String>> requestHeaders) {
         String key = uriToKey(uri);
@@ -121,24 +135,24 @@
     }
 
     @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
-        if (!(urlConnection instanceof OkHttpConnection)) {
+        if (!(urlConnection instanceof HttpURLConnection)) {
             return null;
         }
 
-        OkHttpConnection httpConnection = (OkHttpConnection) urlConnection;
+        HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
         String requestMethod = httpConnection.getRequestMethod();
         String key = uriToKey(uri);
 
-        if (requestMethod.equals(HttpEngine.POST)
-                || requestMethod.equals(HttpEngine.PUT)
-                || requestMethod.equals(HttpEngine.DELETE)) {
+        if (requestMethod.equals("POST")
+                || requestMethod.equals("PUT")
+                || requestMethod.equals("DELETE")) {
             try {
                 cache.remove(key);
             } catch (IOException ignored) {
                 // The cache cannot be written.
             }
             return null;
-        } else if (!requestMethod.equals(HttpEngine.GET)) {
+        } else if (!requestMethod.equals("GET")) {
             /*
              * Don't cache non-GET responses. We're technically allowed to cache
              * HEAD requests and some POST requests, but the complexity of doing
@@ -181,8 +195,8 @@
      * not updated. If the stored response has changed since {@code
      * conditionalCacheHit} was returned, this does nothing.
      */
-    @Override public void update(CacheResponse conditionalCacheHit, OkHttpConnection httpConnection)
-            throws IOException {
+    @Override public void update(CacheResponse conditionalCacheHit,
+            HttpURLConnection httpConnection) throws IOException {
         HttpEngine httpEngine = getHttpEngine(httpConnection);
         URI uri = httpEngine.getUri();
         ResponseHeaders response = httpEngine.getResponseHeaders();
@@ -305,7 +319,7 @@
                 done = true;
                 writeAbortCount++;
             }
-            IoUtils.closeQuietly(cacheOut);
+            Util.closeQuietly(cacheOut);
             try {
                 editor.abort();
             } catch (IOException ignored) {
@@ -374,7 +388,7 @@
          */
         public Entry(InputStream in) throws IOException {
             try {
-                StrictLineReader reader = new StrictLineReader(in, Charsets.US_ASCII);
+                StrictLineReader reader = new StrictLineReader(in, US_ASCII);
                 uri = reader.readLine();
                 requestMethod = reader.readLine();
                 varyHeaders = new RawHeaders();
@@ -408,7 +422,7 @@
             }
         }
 
-        public Entry(URI uri, RawHeaders varyHeaders, OkHttpConnection httpConnection)
+        public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection)
                 throws IOException {
             this.uri = uri.toString();
             this.varyHeaders = varyHeaders;
@@ -416,8 +430,7 @@
             this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
 
             if (isHttps()) {
-                OkHttpsConnection httpsConnection
-                        = (OkHttpsConnection) httpConnection;
+                HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
                 cipherSuite = httpsConnection.getCipherSuite();
                 Certificate[] peerCertificatesNonFinal = null;
                 try {
@@ -435,7 +448,7 @@
 
         public void writeTo(DiskLruCache.Editor editor) throws IOException {
             OutputStream out = editor.newOutputStream(ENTRY_METADATA);
-            Writer writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.UTF_8));
+            Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
 
             writer.write(uri + '\n');
             writer.write(requestMethod + '\n');
diff --git a/src/main/java/libcore/net/http/HttpTransport.java b/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
similarity index 85%
rename from src/main/java/libcore/net/http/HttpTransport.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
index 83440d8..1c73423 100644
--- a/src/main/java/libcore/net/http/HttpTransport.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.internal.Util;
+import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -23,10 +26,10 @@
 import java.io.OutputStream;
 import java.net.CacheRequest;
 import java.net.CookieHandler;
-import libcore.io.Streams;
-import libcore.util.Libcore;
+import java.net.ProtocolException;
+import java.net.Socket;
 
-final class HttpTransport implements Transport {
+public final class HttpTransport implements Transport {
     /**
      * The maximum number of bytes to buffer when sending headers and a request
      * body. When the headers and body can be sent in a single write, the
@@ -35,6 +38,15 @@
      */
     private static final int MAX_REQUEST_BUFFER_LENGTH = 32768;
 
+    /**
+     * The timeout to use while discarding a stream of input data. Since this is
+     * used for connection reuse, this timeout should be significantly less than
+     * the time it takes to establish a new connection.
+     */
+    private static final int DISCARD_STREAM_TIMEOUT_MILLIS = 30;
+
+    public static final int DEFAULT_CHUNK_LENGTH = 1024;
+
     private final HttpEngine httpEngine;
     private final InputStream socketIn;
     private final OutputStream socketOut;
@@ -59,7 +71,7 @@
         boolean chunked = httpEngine.requestHeaders.isChunked();
         if (!chunked
                 && httpEngine.policy.getChunkLength() > 0
-                && httpEngine.connection.httpMinorVersion != 0) {
+                && httpEngine.connection.getHttpMinorVersion() != 0) {
             httpEngine.requestHeaders.setChunked();
             chunked = true;
         }
@@ -68,7 +80,7 @@
         if (chunked) {
             int chunkLength = httpEngine.policy.getChunkLength();
             if (chunkLength == -1) {
-                chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH;
+                chunkLength = DEFAULT_CHUNK_LENGTH;
             }
             writeRequestHeaders();
             return new ChunkedOutputStream(requestOut, chunkLength);
@@ -135,13 +147,13 @@
 
     @Override public ResponseHeaders readResponseHeaders() throws IOException {
         RawHeaders headers = RawHeaders.fromBytes(socketIn);
-        httpEngine.connection.httpMinorVersion = headers.getHttpMinorVersion();
+        httpEngine.connection.setHttpMinorVersion(headers.getHttpMinorVersion());
         receiveHeaders(headers);
         return new ResponseHeaders(httpEngine.uri, headers);
     }
 
     private void receiveHeaders(RawHeaders headers) throws IOException {
-        CookieHandler cookieHandler = CookieHandler.getDefault();
+        CookieHandler cookieHandler = httpEngine.policy.cookieHandler;
         if (cookieHandler != null) {
             cookieHandler.put(httpEngine.uri, headers.toMultimap(true));
         }
@@ -168,17 +180,36 @@
         }
 
         if (responseBodyIn != null) {
-            // Discard the response body before the connection can be reused.
-            try {
-                Streams.skipAll(responseBodyIn);
-            } catch (IOException e) {
-                return false;
-            }
+            return discardStream(httpEngine, responseBodyIn);
         }
 
         return true;
     }
 
+    /**
+     * Discards the response body so that the connection can be reused. This
+     * needs to be done judiciously, since it delays the current request in
+     * order to speed up a potential future request that may never occur.
+     */
+    private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) {
+        Connection connection = httpEngine.connection;
+        if (connection == null) return false;
+        Socket socket = connection.getSocket();
+        if (socket == null) return false;
+        try {
+            int socketTimeout = socket.getSoTimeout();
+            socket.setSoTimeout(DISCARD_STREAM_TIMEOUT_MILLIS);
+            try {
+                Util.skipAll(responseBodyIn);
+                return true;
+            } finally {
+                socket.setSoTimeout(socketTimeout);
+            }
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
     @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
         if (!httpEngine.hasResponseBody()) {
             return new FixedLengthInputStream(socketIn, cacheRequest, httpEngine, 0);
@@ -194,9 +225,9 @@
         }
 
         /*
-         * Wrap the input stream from the HttpConnection (rather than
-         * just returning "socketIn" directly here), so that we can control
-         * its use after the reference escapes.
+         * Wrap the input stream from the connection (rather than just returning
+         * "socketIn" directly here), so that we can control its use after the
+         * reference escapes.
          */
         return new UnknownLengthHttpInputStream(socketIn, cacheRequest, httpEngine);
     }
@@ -215,9 +246,9 @@
 
         @Override public void write(byte[] buffer, int offset, int count) throws IOException {
             checkNotClosed();
-            Libcore.checkOffsetAndCount(buffer.length, offset, count);
+            checkOffsetAndCount(buffer.length, offset, count);
             if (count > bytesRemaining) {
-                throw new IOException("expected " + bytesRemaining
+                throw new ProtocolException("expected " + bytesRemaining
                         + " bytes but received " + count);
             }
             socketOut.write(buffer, offset, count);
@@ -237,7 +268,7 @@
             }
             closed = true;
             if (bytesRemaining > 0) {
-                throw new IOException("unexpected end of stream");
+                throw new ProtocolException("unexpected end of stream");
             }
         }
     }
@@ -283,7 +314,7 @@
         @Override public synchronized void write(byte[] buffer, int offset, int count)
                 throws IOException {
             checkNotClosed();
-            Libcore.checkOffsetAndCount(buffer.length, offset, count);
+            checkOffsetAndCount(buffer.length, offset, count);
 
             while (count > 0) {
                 int numBytesWritten;
@@ -368,7 +399,7 @@
         }
 
         @Override public int read(byte[] buffer, int offset, int count) throws IOException {
-            Libcore.checkOffsetAndCount(buffer.length, offset, count);
+            checkOffsetAndCount(buffer.length, offset, count);
             checkNotClosed();
             if (bytesRemaining == 0) {
                 return -1;
@@ -376,7 +407,7 @@
             int read = in.read(buffer, offset, Math.min(count, bytesRemaining));
             if (read == -1) {
                 unexpectedEndOfInput(); // the server didn't supply the promised content length
-                throw new IOException("unexpected end of stream");
+                throw new ProtocolException("unexpected end of stream");
             }
             bytesRemaining -= read;
             cacheWrite(buffer, offset, read);
@@ -395,10 +426,10 @@
             if (closed) {
                 return;
             }
-            closed = true;
-            if (bytesRemaining != 0) {
+            if (bytesRemaining != 0 && !discardStream(httpEngine, this)) {
                 unexpectedEndOfInput();
             }
+            closed = true;
         }
     }
 
@@ -406,7 +437,6 @@
      * An HTTP body with alternating chunk sizes and chunk bodies.
      */
     private static class ChunkedInputStream extends AbstractHttpInputStream {
-        private static final int MIN_LAST_CHUNK_LENGTH = "\r\n0\r\n\r\n".length();
         private static final int NO_CHUNK_YET = -1;
         private final HttpTransport transport;
         private int bytesRemainingInChunk = NO_CHUNK_YET;
@@ -419,7 +449,7 @@
         }
 
         @Override public int read(byte[] buffer, int offset, int count) throws IOException {
-            Libcore.checkOffsetAndCount(buffer.length, offset, count);
+            checkOffsetAndCount(buffer.length, offset, count);
             checkNotClosed();
 
             if (!hasMoreChunks) {
@@ -438,27 +468,15 @@
             }
             bytesRemainingInChunk -= read;
             cacheWrite(buffer, offset, read);
-
-            /*
-             * If we're at the end of a chunk and the next chunk size is readable,
-             * read it! Reading the last chunk causes the underlying connection to
-             * be recycled and we want to do that as early as possible. Otherwise
-             * self-delimiting streams like gzip will never be recycled.
-             * http://code.google.com/p/android/issues/detail?id=7059
-             */
-            if (bytesRemainingInChunk == 0 && in.available() >= MIN_LAST_CHUNK_LENGTH) {
-                readChunkSize();
-            }
-
             return read;
         }
 
         private void readChunkSize() throws IOException {
             // read the suffix of the previous chunk
             if (bytesRemainingInChunk != NO_CHUNK_YET) {
-                Streams.readAsciiLine(in);
+                Util.readAsciiLine(in);
             }
-            String chunkSizeString = Streams.readAsciiLine(in);
+            String chunkSizeString = Util.readAsciiLine(in);
             int index = chunkSizeString.indexOf(";");
             if (index != -1) {
                 chunkSizeString = chunkSizeString.substring(0, index);
@@ -466,7 +484,7 @@
             try {
                 bytesRemainingInChunk = Integer.parseInt(chunkSizeString.trim(), 16);
             } catch (NumberFormatException e) {
-                throw new IOException("Expected a hex chunk size, but was " + chunkSizeString);
+                throw new ProtocolException("Expected a hex chunk size but was " + chunkSizeString);
             }
             if (bytesRemainingInChunk == 0) {
                 hasMoreChunks = false;
@@ -489,11 +507,10 @@
             if (closed) {
                 return;
             }
-
-            closed = true;
-            if (hasMoreChunks) {
+            if (hasMoreChunks && !discardStream(httpEngine, this)) {
                 unexpectedEndOfInput();
             }
+            closed = true;
         }
     }
 
@@ -509,7 +526,7 @@
         }
 
         @Override public int read(byte[] buffer, int offset, int count) throws IOException {
-            Libcore.checkOffsetAndCount(buffer.length, offset, count);
+            checkOffsetAndCount(buffer.length, offset, count);
             checkNotClosed();
             if (in == null || inputExhausted) {
                 return -1;
diff --git a/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
similarity index 79%
rename from src/main/java/libcore/net/http/HttpURLConnectionImpl.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
index 0806680..aec7b4e 100644
--- a/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
@@ -15,24 +15,31 @@
  *  limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
-import com.squareup.okhttp.OkHttpConnection;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.ConnectionPool;
+import com.squareup.okhttp.internal.Util;
+import static com.squareup.okhttp.internal.Util.getEffectivePort;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.CookieHandler;
 import java.net.HttpRetryException;
+import java.net.HttpURLConnection;
 import java.net.InetSocketAddress;
 import java.net.ProtocolException;
 import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.ResponseCache;
 import java.net.SocketPermission;
 import java.net.URL;
 import java.security.Permission;
+import java.security.cert.CertificateException;
 import java.util.List;
 import java.util.Map;
-import libcore.io.IoUtils;
-import libcore.util.Libcore;
+import javax.net.ssl.SSLHandshakeException;
 
 /**
  * This implementation uses HttpEngine to send requests and receive responses.
@@ -48,7 +55,7 @@
  * connection} field on this class for null/non-null to determine of an instance
  * is currently connected to a server.
  */
-public class HttpURLConnectionImpl extends OkHttpConnection {
+public class HttpURLConnectionImpl extends HttpURLConnection {
     /**
      * HTTP 1.1 doesn't specify how many redirects to follow, but HTTP/1.0
      * recommended 5. http://www.w3.org/Protocols/HTTP/1.0/spec.html#Code3xx
@@ -58,6 +65,10 @@
     private final int defaultPort;
 
     private Proxy proxy;
+    final ProxySelector proxySelector;
+    final CookieHandler cookieHandler;
+    final ResponseCache responseCache;
+    final ConnectionPool connectionPool;
 
     private final RawHeaders rawRequestHeaders = new RawHeaders();
 
@@ -66,24 +77,24 @@
     protected IOException httpEngineFailure;
     protected HttpEngine httpEngine;
 
-    public HttpURLConnectionImpl(URL url, int port) {
+    public HttpURLConnectionImpl(URL url, int defaultPort, Proxy proxy, ProxySelector proxySelector,
+            CookieHandler cookieHandler, ResponseCache responseCache,
+            ConnectionPool connectionPool) {
         super(url);
-        defaultPort = port;
-    }
-
-    public HttpURLConnectionImpl(URL url, int port, Proxy proxy) {
-        this(url, port);
+        this.defaultPort = defaultPort;
         this.proxy = proxy;
+        this.proxySelector = proxySelector;
+        this.cookieHandler = cookieHandler;
+        this.responseCache = responseCache;
+        this.connectionPool = connectionPool;
     }
 
     @Override public final void connect() throws IOException {
         initHttpEngine();
-        try {
-            httpEngine.sendRequest();
-        } catch (IOException e) {
-            httpEngineFailure = e;
-            throw e;
-        }
+        boolean success;
+        do {
+            success = execute(false);
+        } while (!success);
     }
 
     @Override public final void disconnect() {
@@ -95,7 +106,7 @@
             // However the response body can be a GZIPInputStream that
             // still has unread data.
             if (httpEngine.hasResponse()) {
-                IoUtils.closeQuietly(httpEngine.getResponseBody());
+                Util.closeQuietly(httpEngine.getResponseBody());
             }
             httpEngine.release(false);
         }
@@ -189,7 +200,8 @@
 
         InputStream result = response.getResponseBody();
         if (result == null) {
-            throw new IOException("No response body exists; responseCode=" + getResponseCode());
+            throw new ProtocolException("No response body exists; responseCode="
+                    + getResponseCode());
         }
         return result;
     }
@@ -242,10 +254,10 @@
         connected = true;
         try {
             if (doOutput) {
-                if (method == HttpEngine.GET) {
+                if (method.equals("GET")) {
                     // they are requesting a stream to write to. This implies a POST method
-                    method = HttpEngine.POST;
-                } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {
+                    method = "POST";
+                } else if (!method.equals("POST") && !method.equals("PUT")) {
                     // If the request method is neither POST nor PUT, then you're not writing
                     throw new ProtocolException(method + " does not support writing");
                 }
@@ -262,7 +274,7 @@
      * overridden by HttpsURLConnectionImpl.
      */
     protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
-            HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
+            Connection connection, RetryableOutputStream requestBody) throws IOException {
         return new HttpEngine(this, method, requestHeaders, connection, requestBody);
     }
 
@@ -279,24 +291,8 @@
         }
 
         while (true) {
-            try {
-                httpEngine.sendRequest();
-                httpEngine.readResponse();
-            } catch (IOException e) {
-                /*
-                 * If the connection was recycled, its staleness may have caused
-                 * the failure. Silently retry with a different connection.
-                 */
-                OutputStream requestBody = httpEngine.getRequestBody();
-                if (httpEngine.hasRecycledConnection()
-                        && (requestBody == null || requestBody instanceof RetryableOutputStream)) {
-                    httpEngine.release(false);
-                    httpEngine = newHttpEngine(method, rawRequestHeaders, null,
-                            (RetryableOutputStream) requestBody);
-                    continue;
-                }
-                httpEngineFailure = e;
-                throw e;
+            if (!execute(true)) {
+                continue;
             }
 
             Retry retry = processResponseHeaders();
@@ -319,7 +315,7 @@
             int responseCode = getResponseCode();
             if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM
                     || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) {
-                retryMethod = HttpEngine.GET;
+                retryMethod = "GET";
                 requestBody = null;
             }
 
@@ -339,6 +335,50 @@
         }
     }
 
+    /**
+     * Sends a request and optionally reads a response. Returns true if the
+     * request was successfully executed, and false if the request can be
+     * retried. Throws an exception if the request failed permanently.
+     */
+    private boolean execute(boolean readResponse) throws IOException {
+        try {
+            httpEngine.sendRequest();
+            if (readResponse) {
+                httpEngine.readResponse();
+            }
+            return true;
+        } catch (IOException e) {
+            RouteSelector routeSelector = httpEngine.routeSelector;
+            if (routeSelector == null) {
+                throw e; // Without a route selector, we can't retry.
+            } else if (httpEngine.connection != null) {
+                routeSelector.connectFailed(httpEngine.connection, e);
+            }
+
+            // The connection failure isn't fatal if there's another route to attempt.
+            OutputStream requestBody = httpEngine.getRequestBody();
+            if (routeSelector.hasNext() && isRecoverable(e)
+                    && (requestBody == null || requestBody instanceof RetryableOutputStream)) {
+                httpEngine.release(false);
+                httpEngine = newHttpEngine(method, rawRequestHeaders, null,
+                        (RetryableOutputStream) requestBody);
+                httpEngine.routeSelector = routeSelector; // Keep the same routeSelector.
+                return false;
+            }
+            httpEngineFailure = e;
+            throw e;
+        }
+    }
+
+    private boolean isRecoverable(IOException e) {
+        // If the problem was a CertificateException from the X509TrustManager,
+        // do not retry, we didn't have an abrupt server initiated exception.
+        boolean sslFailure = e instanceof SSLHandshakeException
+                && e.getCause() instanceof CertificateException;
+        boolean protocolFailure = e instanceof ProtocolException;
+        return !sslFailure && !protocolFailure;
+    }
+
     HttpEngine getHttpEngine() {
         return httpEngine;
     }
@@ -358,7 +398,7 @@
         switch (getResponseCode()) {
         case HTTP_PROXY_AUTH:
             if (!usingProxy()) {
-                throw new IOException(
+                throw new ProtocolException(
                         "Received HTTP_PROXY_AUTH (407) code while not using proxy");
             }
             // fall-through
@@ -387,7 +427,7 @@
                 return Retry.NONE; // the scheme changed; don't retry.
             }
             if (previousUrl.getHost().equals(url.getHost())
-                    && Libcore.getEffectivePort(previousUrl) == Libcore.getEffectivePort(url)) {
+                    && getEffectivePort(previousUrl) == getEffectivePort(url)) {
                 return Retry.SAME_CONNECTION;
             } else {
                 return Retry.DIFFERENT_CONNECTION;
diff --git a/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java b/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
similarity index 84%
rename from src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
rename to src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
index 2aaeaf4..b0fc73b 100644
--- a/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
@@ -14,16 +14,22 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
-import com.squareup.okhttp.OkHttpConnection;
-import com.squareup.okhttp.OkHttpsConnection;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.ConnectionPool;
+import com.squareup.okhttp.TunnelRequest;
+import static com.squareup.okhttp.internal.Util.getEffectivePort;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.CacheResponse;
+import java.net.CookieHandler;
+import java.net.HttpURLConnection;
 import java.net.ProtocolException;
 import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.ResponseCache;
 import java.net.SecureCacheResponse;
 import java.net.URL;
 import java.security.Permission;
@@ -32,23 +38,22 @@
 import java.util.List;
 import java.util.Map;
 import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
-public final class HttpsURLConnectionImpl extends OkHttpsConnection {
+public final class HttpsURLConnectionImpl extends HttpsURLConnection {
 
     /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
     private final HttpUrlConnectionDelegate delegate;
 
-    public HttpsURLConnectionImpl(URL url, int port) {
+    public HttpsURLConnectionImpl(URL url, int defaultPort, Proxy proxy,
+            ProxySelector proxySelector, CookieHandler cookieHandler, ResponseCache responseCache,
+            ConnectionPool connectionPool) {
         super(url);
-        delegate = new HttpUrlConnectionDelegate(url, port);
-    }
-
-    public HttpsURLConnectionImpl(URL url, int port, Proxy proxy) {
-        super(url);
-        delegate = new HttpUrlConnectionDelegate(url, port, proxy);
+        delegate = new HttpUrlConnectionDelegate(url, defaultPort, proxy, proxySelector,
+                cookieHandler, responseCache, connectionPool);
     }
 
     private void checkConnected() {
@@ -256,8 +261,8 @@
     }
 
     @Override
-    public String getHeaderFieldKey(int posn) {
-        return delegate.getHeaderFieldKey(posn);
+    public String getHeaderFieldKey(int position) {
+        return delegate.getHeaderFieldKey(position);
     }
 
     @Override
@@ -371,16 +376,15 @@
     }
 
     private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
-        private HttpUrlConnectionDelegate(URL url, int port) {
-            super(url, port);
-        }
-
-        private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) {
-            super(url, port, proxy);
+        private HttpUrlConnectionDelegate(URL url, int defaultPort, Proxy proxy,
+                ProxySelector proxySelector, CookieHandler cookieHandler,
+                ResponseCache responseCache, ConnectionPool connectionPool) {
+            super(url, defaultPort, proxy, proxySelector, cookieHandler, responseCache,
+                    connectionPool);
         }
 
         @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
-                HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
+                Connection connection, RetryableOutputStream requestBody) throws IOException {
             return new HttpsEngine(this, method, requestHeaders, connection, requestBody,
                     HttpsURLConnectionImpl.this);
         }
@@ -397,14 +401,9 @@
     }
 
     private static final class HttpsEngine extends HttpEngine {
-
         /**
-         * Local stash of HttpsEngine.connection.sslSocket for answering
-         * queries such as getCipherSuite even after
-         * httpsEngine.Connection has been recycled. It's presence is also
-         * used to tell if the HttpsURLConnection is considered connected,
-         * as opposed to the connected field of URLConnection or the a
-         * non-null connect in HttpURLConnectionImpl
+         * Stash of HttpsEngine.connection.socket to implement requests like
+         * {@link #getCipherSuite} even after the connection has been recycled.
          */
         private SSLSocket sslSocket;
 
@@ -415,13 +414,17 @@
          * @param enclosing the HttpsURLConnection with HTTPS features
          */
         private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
-                HttpConnection connection, RetryableOutputStream requestBody,
+                Connection connection, RetryableOutputStream requestBody,
                 HttpsURLConnectionImpl enclosing) throws IOException {
             super(policy, method, requestHeaders, connection, requestBody);
             this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null;
             this.enclosing = enclosing;
         }
 
+        @Override protected void connected(Connection connection) {
+            this.sslSocket = (SSLSocket) connection.getSocket();
+        }
+
         @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
             return cacheResponse instanceof SecureCacheResponse;
         }
@@ -439,24 +442,19 @@
             return enclosing.getHostnameVerifier();
         }
 
-        @Override protected OkHttpConnection getHttpConnectionToCache() {
+        @Override protected HttpURLConnection getHttpConnectionToCache() {
             return enclosing;
         }
 
-        @Override protected HttpConnection.TunnelConfig getTunnelConfig() {
-            String host = requestHeaders.getHost();
-            if (host == null) {
-                host = HttpEngine.getOriginAddress(policy.getURL());
-            }
-
+        @Override protected TunnelRequest getTunnelConfig() {
             String userAgent = requestHeaders.getUserAgent();
             if (userAgent == null) {
-                userAgent = HttpEngine.getDefaultUserAgent();
+                userAgent = getDefaultUserAgent();
             }
 
-            String proxyAuthorization = requestHeaders.getProxyAuthorization();
-            return new HttpConnection.TunnelConfig(
-                    policy.getURL(), host, userAgent, proxyAuthorization);
+            URL url = policy.getURL();
+            return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent,
+                    requestHeaders.getProxyAuthorization());
         }
     }
 }
diff --git a/src/main/java/libcore/net/http/RawHeaders.java b/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
similarity index 93%
rename from src/main/java/libcore/net/http/RawHeaders.java
rename to src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
index ba315d6..edb1436 100644
--- a/src/main/java/libcore/net/http/RawHeaders.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java
@@ -15,11 +15,14 @@
  *  limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
+import java.net.ProtocolException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -30,8 +33,6 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
-import libcore.io.Streams;
-import libcore.util.Libcore;
 
 /**
  * The HTTP status and unparsed header fields of a single HTTP message. Values
@@ -101,17 +102,17 @@
         if (!statusLine.startsWith("HTTP/1.")
                 || statusLine.charAt(8) != ' '
                 || statusLine.charAt(12) != ' ') {
-            throw new IOException("Unexpected status line: " + statusLine);
+            throw new ProtocolException("Unexpected status line: " + statusLine);
         }
         int httpMinorVersion = statusLine.charAt(7) - '0';
         if (httpMinorVersion < 0 || httpMinorVersion > 9) {
-            throw new IOException("Unexpected status line: " + statusLine);
+            throw new ProtocolException("Unexpected status line: " + statusLine);
         }
         int responseCode;
         try {
             responseCode = Integer.parseInt(statusLine.substring(9, 12));
         } catch (NumberFormatException e) {
-            throw new IOException("Unexpected status line: " + statusLine);
+            throw new ProtocolException("Unexpected status line: " + statusLine);
         }
         this.responseMessage = statusLine.substring(13);
         this.responseCode = responseCode;
@@ -131,7 +132,7 @@
             }
         }
         if (status == null || version == null) {
-            throw new IOException("Expected 'status' and 'version' headers not present");
+            throw new ProtocolException("Expected 'status' and 'version' headers not present");
         }
         setStatusLine(version + " " + status);
     }
@@ -202,7 +203,8 @@
              * "Accept\r\n". For platform compatibility and HTTP compliance, we
              * print a warning and ignore null values.
              */
-            Libcore.logW("Ignoring HTTP header field '" + fieldName + "' because its value is null");
+            Platform.get().logW("Ignoring HTTP header field '"
+                    + fieldName + "' because its value is null");
             return;
         }
         namesAndValues.add(fieldName);
@@ -309,7 +311,7 @@
         RawHeaders headers;
         do {
             headers = new RawHeaders();
-            headers.setStatusLine(Streams.readAsciiLine(in));
+            headers.setStatusLine(Util.readAsciiLine(in));
             readHeaders(in, headers);
         } while (headers.getResponseCode() == HttpEngine.HTTP_CONTINUE);
         return headers;
@@ -321,7 +323,7 @@
     public static void readHeaders(InputStream in, RawHeaders out) throws IOException {
         // parse the result headers until the first blank line
         String line;
-        while ((line = Streams.readAsciiLine(in)).length() != 0) {
+        while ((line = Util.readAsciiLine(in)).length() != 0) {
             out.addLine(line);
         }
     }
@@ -391,6 +393,11 @@
                 throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
             }
 
+            // Drop headers that are ignored when layering HTTP over SPDY.
+            if (name.equals("connection") || name.equals("accept-encoding")) {
+                continue;
+            }
+
             // If we haven't seen this name before, add the pair to the end of the list...
             if (names.add(name)) {
                 result.add(name);
diff --git a/src/main/java/libcore/net/http/RequestHeaders.java b/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
similarity index 98%
rename from src/main/java/libcore/net/http/RequestHeaders.java
rename to src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
index a84437e..2c12b95 100644
--- a/src/main/java/libcore/net/http/RequestHeaders.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 import java.net.URI;
 import java.util.Date;
@@ -24,7 +24,7 @@
 /**
  * Parsed HTTP request headers.
  */
-public final class RequestHeaders {
+final class RequestHeaders {
     private final URI uri;
     private final RawHeaders headers;
 
diff --git a/src/main/java/libcore/net/http/ResponseHeaders.java b/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
similarity index 98%
rename from src/main/java/libcore/net/http/ResponseHeaders.java
rename to src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
index 0d89fe5..d333d8b 100644
--- a/src/main/java/libcore/net/http/ResponseHeaders.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.ResponseSource;
+import static com.squareup.okhttp.internal.Util.equal;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URI;
@@ -26,13 +28,11 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
-import libcore.util.Objects;
-import libcore.util.ResponseSource;
 
 /**
  * Parsed HTTP response headers.
  */
-public final class ResponseHeaders {
+final class ResponseHeaders {
 
     /** HTTP header name for the local time when the request was sent. */
     private static final String SENT_MILLIS = "X-Android-Sent-Millis";
@@ -368,7 +368,7 @@
     public boolean varyMatches(Map<String, List<String>> cachedRequest,
             Map<String, List<String>> newRequest) {
         for (String field : varyFields) {
-            if (!Objects.equal(cachedRequest.get(field), newRequest.get(field))) {
+            if (!equal(cachedRequest.get(field), newRequest.get(field))) {
                 return false;
             }
         }
diff --git a/src/main/java/libcore/net/http/RetryableOutputStream.java b/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java
similarity index 79%
rename from src/main/java/libcore/net/http/RetryableOutputStream.java
rename to src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java
index c8110be..a5c5842 100644
--- a/src/main/java/libcore/net/http/RetryableOutputStream.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import libcore.util.Libcore;
+import java.net.ProtocolException;
 
 /**
  * An HTTP request body that's completely buffered in memory. This allows
@@ -40,13 +41,13 @@
         this.content = new ByteArrayOutputStream();
     }
 
-    @Override public synchronized void close() {
+    @Override public synchronized void close() throws IOException {
         if (closed) {
             return;
         }
         closed = true;
         if (content.size() < limit) {
-            throw new IllegalStateException("content-length promised "
+            throw new ProtocolException("content-length promised "
                     + limit + " bytes, but received " + content.size());
         }
     }
@@ -54,14 +55,14 @@
     @Override public synchronized void write(byte[] buffer, int offset, int count)
             throws IOException {
         checkNotClosed();
-        Libcore.checkOffsetAndCount(buffer.length, offset, count);
+        checkOffsetAndCount(buffer.length, offset, count);
         if (limit != -1 && content.size() > limit - count) {
-            throw new IOException("exceeded content-length limit of " + limit + " bytes");
+            throw new ProtocolException("exceeded content-length limit of " + limit + " bytes");
         }
         content.write(buffer, offset, count);
     }
 
-    public synchronized int contentLength() {
+    public synchronized int contentLength() throws IOException {
         close();
         return content.size();
     }
diff --git a/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java b/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java
new file mode 100644
index 0000000..ac4bb6c
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.http;
+
+import com.squareup.okhttp.Address;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.ConnectionPool;
+import com.squareup.okhttp.internal.Dns;
+import static com.squareup.okhttp.internal.Util.getEffectivePort;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Selects routes to connect to an origin server. Each connection requires a
+ * choice of proxy server, IP address, and TLS mode. Connections may also be
+ * recycled.
+ */
+final class RouteSelector {
+    /** Uses {@link com.squareup.okhttp.internal.Platform#enableTlsExtensions}. */
+    private static final int TLS_MODE_MODERN = 1;
+    /** Uses {@link com.squareup.okhttp.internal.Platform#supportTlsIntolerantServer}. */
+    private static final int TLS_MODE_COMPATIBLE = 0;
+    /** No TLS mode. */
+    private static final int TLS_MODE_NULL = -1;
+
+    private final Address address;
+    private final URI uri;
+    private final ProxySelector proxySelector;
+    private final ConnectionPool pool;
+    private final Dns dns;
+
+    /* The most recently attempted route. */
+    private Proxy lastProxy;
+    private InetSocketAddress lastInetSocketAddress;
+
+    /* State for negotiating the next proxy to use. */
+    private boolean hasNextProxy;
+    private Proxy userSpecifiedProxy;
+    private Iterator<Proxy> proxySelectorProxies;
+
+    /* State for negotiating the next InetSocketAddress to use. */
+    private InetAddress[] socketAddresses;
+    private int nextSocketAddressIndex;
+    private String socketHost;
+    private int socketPort;
+
+    /* State for negotiating the next TLS configuration */
+    private int nextTlsMode = TLS_MODE_NULL;
+
+    public RouteSelector(Address address, URI uri, ProxySelector proxySelector,
+            ConnectionPool pool, Dns dns) {
+        this.address = address;
+        this.uri = uri;
+        this.proxySelector = proxySelector;
+        this.pool = pool;
+        this.dns = dns;
+
+        resetNextProxy(uri, address.getProxy());
+    }
+
+    /**
+     * Returns true if there's another route to attempt. Every address has at
+     * least one route.
+     */
+    public boolean hasNext() {
+        return hasNextTlsMode() || hasNextInetSocketAddress() || hasNextProxy();
+    }
+
+    /**
+     * Returns the next route address to attempt.
+     *
+     * @throws NoSuchElementException if there are no more routes to attempt.
+     */
+    public Connection next() throws IOException {
+        // Always prefer pooled connections over new connections.
+        Connection pooled = pool.get(address);
+        if (pooled != null) {
+            return pooled;
+        }
+
+        // Compute the next route to attempt.
+        if (!hasNextTlsMode()) {
+            if (!hasNextInetSocketAddress()) {
+                if (!hasNextProxy()) {
+                    throw new NoSuchElementException();
+                }
+                lastProxy = nextProxy();
+                resetNextInetSocketAddress(lastProxy);
+            }
+            lastInetSocketAddress = nextInetSocketAddress();
+            resetNextTlsMode();
+        }
+        boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
+
+        return new Connection(address, lastProxy, lastInetSocketAddress, modernTls);
+    }
+
+    /**
+     * Clients should invoke this method when they encounter a connectivity
+     * failure on a connection returned by this route selector.
+     */
+    public void connectFailed(Connection connection, IOException failure) {
+        if (connection.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) {
+            // Tell the proxy selector when we fail to connect on a fresh connection.
+            proxySelector.connectFailed(uri, connection.getProxy().address(), failure);
+        }
+    }
+
+    /** Resets {@link #nextProxy} to the first option. */
+    private void resetNextProxy(URI uri, Proxy proxy) {
+        this.hasNextProxy = true; // This includes NO_PROXY!
+        if (proxy != null) {
+            this.userSpecifiedProxy = proxy;
+        } else {
+            List<Proxy> proxyList = proxySelector.select(uri);
+            if (proxyList != null) {
+                this.proxySelectorProxies = proxyList.iterator();
+            }
+        }
+    }
+
+    /** Returns true if there's another proxy to try. */
+    private boolean hasNextProxy() {
+        return hasNextProxy;
+    }
+
+    /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
+    private Proxy nextProxy() {
+        // If the user specifies a proxy, try that and only that.
+        if (userSpecifiedProxy != null) {
+            hasNextProxy = false;
+            return userSpecifiedProxy;
+        }
+
+        // Try each of the ProxySelector choices until one connection succeeds. If none succeed
+        // then we'll try a direct connection below.
+        if (proxySelectorProxies != null) {
+            while (proxySelectorProxies.hasNext()) {
+                Proxy candidate = proxySelectorProxies.next();
+                if (candidate.type() != Proxy.Type.DIRECT) {
+                    return candidate;
+                }
+            }
+        }
+
+        // Finally try a direct connection.
+        hasNextProxy = false;
+        return Proxy.NO_PROXY;
+    }
+
+    /** Resets {@link #nextInetSocketAddress} to the first option. */
+    private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
+        socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
+
+        if (proxy.type() == Proxy.Type.DIRECT) {
+            socketHost = uri.getHost();
+            socketPort = getEffectivePort(uri);
+        } else {
+            SocketAddress proxyAddress = proxy.address();
+            if (!(proxyAddress instanceof InetSocketAddress)) {
+                throw new IllegalArgumentException("Proxy.address() is not an "
+                        + "InetSocketAddress: " + proxyAddress.getClass());
+            }
+            InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
+            socketHost = proxySocketAddress.getHostName();
+            socketPort = proxySocketAddress.getPort();
+        }
+
+        // Try each address for best behavior in mixed IPv4/IPv6 environments.
+        socketAddresses = dns.getAllByName(socketHost);
+        nextSocketAddressIndex = 0;
+    }
+
+    /** Returns true if there's another socket address to try. */
+    private boolean hasNextInetSocketAddress() {
+        return socketAddresses != null;
+    }
+
+    /** Returns the next socket address to try. */
+    private InetSocketAddress nextInetSocketAddress() throws UnknownHostException {
+        InetSocketAddress result = new InetSocketAddress(
+                socketAddresses[nextSocketAddressIndex++], socketPort);
+        if (nextSocketAddressIndex == socketAddresses.length) {
+            socketAddresses = null; // So that hasNextInetSocketAddress() returns false.
+            nextSocketAddressIndex = 0;
+        }
+
+        return result;
+    }
+
+    /** Resets {@link #nextTlsMode} to the first option. */
+    private void resetNextTlsMode() {
+        nextTlsMode = (address.getSslSocketFactory() != null)
+                ? TLS_MODE_MODERN
+                : TLS_MODE_COMPATIBLE;
+    }
+
+    /** Returns true if there's another TLS mode to try. */
+    private boolean hasNextTlsMode() {
+        return nextTlsMode != TLS_MODE_NULL;
+    }
+
+    /** Returns the next TLS mode to try. */
+    private int nextTlsMode() {
+        if (nextTlsMode == TLS_MODE_MODERN) {
+            nextTlsMode = TLS_MODE_COMPATIBLE;
+            return TLS_MODE_MODERN;
+        } else if (nextTlsMode == TLS_MODE_COMPATIBLE) {
+            nextTlsMode = TLS_MODE_NULL;  // So that hasNextTlsMode() returns false.
+            return TLS_MODE_COMPATIBLE;
+        } else {
+            throw new AssertionError();
+        }
+    }
+}
diff --git a/src/main/java/libcore/net/http/SpdyTransport.java b/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
similarity index 75%
rename from src/main/java/libcore/net/http/SpdyTransport.java
rename to src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
index 16ad452..28a47ab 100644
--- a/src/main/java/libcore/net/http/SpdyTransport.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.internal.spdy.SpdyConnection;
+import com.squareup.okhttp.internal.spdy.SpdyStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InterruptedIOException;
 import java.io.OutputStream;
 import java.net.CacheRequest;
 import java.util.List;
-import libcore.net.spdy.SpdyConnection;
-import libcore.net.spdy.SpdyStream;
 
-final class SpdyTransport implements Transport {
+public final class SpdyTransport implements Transport {
     private final HttpEngine httpEngine;
     private final SpdyConnection spdyConnection;
     private SpdyStream stream;
@@ -33,7 +32,7 @@
     // TODO: set sentMillis
     // TODO: set cookie stuff
 
-    SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) {
+    public SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) {
         this.httpEngine = httpEngine;
         this.spdyConnection = spdyConnection;
     }
@@ -49,13 +48,14 @@
             return;
         }
         RawHeaders requestHeaders = httpEngine.requestHeaders.getHeaders();
-        String version = httpEngine.connection.httpMinorVersion == 1 ? "HTTP/1.1" : "HTTP/1.0";
+        String version = httpEngine.connection.getHttpMinorVersion() == 1 ? "HTTP/1.1" : "HTTP/1.0";
         requestHeaders.addSpdyRequestHeaders(httpEngine.method, httpEngine.uri.getScheme(),
                 HttpEngine.requestPath(httpEngine.policy.getURL()), version);
         boolean hasRequestBody = httpEngine.hasRequestBody();
         boolean hasResponseBody = true;
         stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(),
                 hasRequestBody, hasResponseBody);
+        stream.setReadTimeout(httpEngine.policy.getReadTimeout());
     }
 
     @Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException {
@@ -68,16 +68,10 @@
 
     @Override public ResponseHeaders readResponseHeaders() throws IOException {
         // TODO: fix the SPDY implementation so this throws a (buffered) IOException
-        try {
-            List<String> nameValueBlock = stream.getResponseHeaders();
-            RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
-            rawHeaders.computeResponseStatusLineFromSpdyHeaders();
-            return new ResponseHeaders(httpEngine.uri, rawHeaders);
-        } catch (InterruptedException e) {
-            InterruptedIOException rethrow = new InterruptedIOException();
-            rethrow.initCause(e);
-            throw rethrow;
-        }
+        List<String> nameValueBlock = stream.getResponseHeaders();
+        RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
+        rawHeaders.computeResponseStatusLineFromSpdyHeaders();
+        return new ResponseHeaders(httpEngine.uri, rawHeaders);
     }
 
     @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
diff --git a/src/main/java/libcore/net/http/Transport.java b/src/main/java/com/squareup/okhttp/internal/http/Transport.java
similarity index 97%
rename from src/main/java/libcore/net/http/Transport.java
rename to src/main/java/com/squareup/okhttp/internal/http/Transport.java
index 3d4c8dd..f1212d5 100644
--- a/src/main/java/libcore/net/http/Transport.java
+++ b/src/main/java/com/squareup/okhttp/internal/http/Transport.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/src/main/java/libcore/net/spdy/IncomingStreamHandler.java b/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
similarity index 83%
rename from src/main/java/libcore/net/spdy/IncomingStreamHandler.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
index 69cc8e1..fc554f4 100644
--- a/src/main/java/libcore/net/spdy/IncomingStreamHandler.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
 import java.io.IOException;
 
@@ -30,9 +30,9 @@
 
     /**
      * Handle a new stream from this connection's peer. Implementations should
-     * respond by either {@link SpdyStream#reply(java.util.List) replying to the
-     * stream} or {@link SpdyStream#close(int) closing it}. This response does
-     * not need to be synchronous.
+     * respond by either {@link SpdyStream#reply replying to the stream} or
+     * {@link SpdyStream#close closing it}. This response does not need to be
+     * synchronous.
      */
     void receive(SpdyStream stream) throws IOException;
 }
diff --git a/src/main/java/libcore/net/spdy/Ping.java b/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java
similarity index 80%
rename from src/main/java/libcore/net/spdy/Ping.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/Ping.java
index 8eb5ebe..1fc3979 100644
--- a/src/main/java/libcore/net/spdy/Ping.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -40,9 +40,16 @@
         latch.countDown();
     }
 
+    void cancel() {
+        if (received != -1 || sent == -1) throw new IllegalStateException();
+        received = sent - 1;
+        latch.countDown();
+    }
+
     /**
      * Returns the round trip time for this ping in nanoseconds, waiting for the
-     * response to arrive if necessary.
+     * response to arrive if necessary. Returns -1 if the response was
+     * cancelled.
      */
     public long roundTripTime() throws InterruptedException {
         latch.await();
@@ -51,13 +58,14 @@
 
     /**
      * Returns the round trip time for this ping in nanoseconds, or -1 if the
-     * timeout elapsed before the round trip completed.
+     * response was cancelled, or -2 if the timeout elapsed before the round
+     * trip completed.
      */
     public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException {
         if (latch.await(timeout, unit)) {
             return received - sent;
         } else {
-            return -1;
+            return -2;
         }
     }
 }
diff --git a/src/main/java/libcore/net/spdy/Settings.java b/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
similarity index 98%
rename from src/main/java/libcore/net/spdy/Settings.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
index 0e3e40c..f4136e6 100644
--- a/src/main/java/libcore/net/spdy/Settings.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
 final class Settings {
     /** Peer request to clear durable settings. */
diff --git a/src/main/java/libcore/net/spdy/SpdyConnection.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
similarity index 69%
rename from src/main/java/libcore/net/spdy/SpdyConnection.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
index 0aa81fe..a67e3e8 100644
--- a/src/main/java/libcore/net/spdy/SpdyConnection.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
+import com.squareup.okhttp.internal.Util;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.ProtocolException;
 import java.net.Socket;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -29,9 +32,6 @@
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import libcore.io.Streams;
-
-import static libcore.net.spdy.Threads.newThreadFactory;
 
 /**
  * A socket connection to a remote peer. A connection hosts streams which can
@@ -90,7 +90,9 @@
     private final ExecutorService callbackExecutor;
 
     private final Map<Integer, SpdyStream> streams = new HashMap<Integer, SpdyStream>();
+    private int lastGoodStreamId;
     private int nextStreamId;
+    private boolean shutdown;
 
     /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */
     private Map<Integer, Ping> pings;
@@ -109,15 +111,23 @@
 
         String prefix = builder.client ? "Spdy Client " : "Spdy Server ";
         readExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
-                new SynchronousQueue<Runnable>(), newThreadFactory(prefix + "Reader", false));
+                new SynchronousQueue<Runnable>(), Threads.newThreadFactory(prefix + "Reader", false));
         writeExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS,
-                new LinkedBlockingQueue<Runnable>(), newThreadFactory(prefix + "Writer", false));
+                new LinkedBlockingQueue<Runnable>(), Threads.newThreadFactory(prefix + "Writer", false));
         callbackExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
-                new SynchronousQueue<Runnable>(), newThreadFactory(prefix + "Callbacks", false));
+                new SynchronousQueue<Runnable>(), Threads.newThreadFactory(prefix + "Callbacks", false));
 
         readExecutor.execute(new Reader());
     }
 
+    /**
+     * Returns the number of {@link SpdyStream#isOpen() open streams} on this
+     * connection.
+     */
+    public synchronized int openStreamCount() {
+        return streams.size();
+    }
+
     private synchronized SpdyStream getStream(int id) {
         return streams.get(id);
     }
@@ -144,10 +154,15 @@
 
         synchronized (spdyWriter) {
             synchronized (this) {
+                if (shutdown) {
+                    throw new IOException("shutdown");
+                }
                 streamId = nextStreamId;
                 nextStreamId += 2;
                 stream = new SpdyStream(streamId, this, requestHeaders, flags);
-                streams.put(streamId, stream);
+                if (stream.isOpen()) {
+                    streams.put(streamId, stream);
+                }
             }
 
             spdyWriter.synStream(flags, streamId, associatedStreamId, priority, requestHeaders);
@@ -157,9 +172,7 @@
     }
 
     void writeSynReply(int streamId, int flags, List<String> alternating) throws IOException {
-        synchronized (spdyWriter) {
-            spdyWriter.synReply(flags, streamId, alternating);
-        }
+        spdyWriter.synReply(flags, streamId, alternating);
     }
 
     /** Writes a complete data frame. */
@@ -181,9 +194,7 @@
     }
 
     void writeSynReset(int streamId, int statusCode) throws IOException {
-        synchronized (spdyWriter) {
-            spdyWriter.synReset(streamId, statusCode);
-        }
+        spdyWriter.synReset(streamId, statusCode);
     }
 
     /**
@@ -194,6 +205,9 @@
         Ping ping = new Ping();
         int pingId;
         synchronized (this) {
+            if (shutdown) {
+                throw new IOException("shutdown");
+            }
             pingId = nextPingId;
             nextPingId += 2;
             if (pings == null) pings = new HashMap<Integer, Ping>();
@@ -230,9 +244,7 @@
      * Sends a noop frame to the peer.
      */
     public void noop() throws IOException {
-        synchronized (spdyWriter) {
-            spdyWriter.noop();
-        }
+        spdyWriter.noop();
     }
 
     public void flush() throws IOException {
@@ -241,17 +253,66 @@
         }
     }
 
-    @Override public void close() throws IOException {
-        close(null);
+    /**
+     * Degrades this connection such that new streams can neither be created
+     * locally, nor accepted from the remote peer. Existing streams are not
+     * impacted. This is intended to permit an endpoint to gracefully stop
+     * accepting new requests without harming previously established streams.
+     */
+    public void shutdown() throws IOException {
+        synchronized (spdyWriter) {
+            int lastGoodStreamId;
+            synchronized (this) {
+                if (shutdown) {
+                    return;
+                }
+                shutdown = true;
+                lastGoodStreamId = this.lastGoodStreamId;
+            }
+            spdyWriter.goAway(0, lastGoodStreamId);
+        }
     }
 
-    private synchronized void close(Throwable reason) throws IOException {
-        // TODO: forward 'reason' to forced closed streams?
-        // TODO: graceful close; send RST frames
-        // TODO: close all streams to release waiting readers
+    /**
+     * Closes this connection. This cancels all open streams and unanswered
+     * pings. It closes the underlying input and output streams and shuts down
+     * internal executor services.
+     */
+    @Override public void close() throws IOException {
+        shutdown();
+
+        SpdyStream[] streamsToClose = null;
+        Ping[] pingsToCancel = null;
+        synchronized (this) {
+            if (!streams.isEmpty()) {
+                streamsToClose = streams.values().toArray(new SpdyStream[streams.size()]);
+                streams.clear();
+            }
+            if (pings != null) {
+                pingsToCancel = pings.values().toArray(new Ping[pings.size()]);
+                pings = null;
+            }
+        }
+
+        if (streamsToClose != null) {
+            for (SpdyStream stream : streamsToClose) {
+                try {
+                    stream.close(SpdyStream.RST_CANCEL);
+                } catch (Throwable ignored) {
+                }
+            }
+        }
+
+        if (pingsToCancel != null) {
+            for (Ping ping : pingsToCancel) {
+                ping.cancel();
+            }
+        }
+
         writeExecutor.shutdown();
-        readExecutor.shutdown();
         callbackExecutor.shutdown();
+        readExecutor.shutdown();
+        Util.closeAll(spdyReader, spdyWriter);
     }
 
     public static class Builder {
@@ -290,28 +351,32 @@
 
     private class Reader implements Runnable, SpdyReader.Handler {
         @Override public void run() {
-            Throwable failure = null;
             try {
                 while (spdyReader.nextFrame(this)) {
                 }
-            } catch (Throwable e) {
-                failure = e;
-            }
-
-            try {
-                close(failure);
-            } catch (IOException ignored) {
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            } finally {
+                Util.closeQuietly(SpdyConnection.this);
             }
         }
 
         @Override public void data(int flags, int streamId, InputStream in, int length)
                 throws IOException {
             SpdyStream dataStream = getStream(streamId);
-            if (dataStream != null) {
-                dataStream.receiveData(in, flags, length);
-            } else {
+            if (dataStream == null) {
                 writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM);
-                Streams.skipByReading(in, length);
+                Util.skipByReading(in, length);
+                return;
+            }
+            try {
+                dataStream.receiveData(in, length);
+                if ((flags & SpdyConnection.FLAG_FIN) != 0) {
+                    dataStream.receiveFin();
+                }
+            } catch (ProtocolException e) {
+                Util.skipByReading(in, length);
+                dataStream.closeLater(SpdyStream.RST_FLOW_CONTROL_ERROR);
             }
         }
 
@@ -321,17 +386,15 @@
                     nameValueBlock, flags);
             final SpdyStream previous;
             synchronized (SpdyConnection.this) {
+                if (shutdown) {
+                    return;
+                }
+                lastGoodStreamId = streamId;
                 previous = streams.put(streamId, synStream);
             }
             if (previous != null) {
-                writeExecutor.execute(new Runnable() {
-                    @Override public void run() {
-                        try {
-                            previous.close(SpdyStream.RST_PROTOCOL_ERROR);
-                        } catch (IOException ignored) {
-                        }
-                    }
-                });
+                previous.closeLater(SpdyStream.RST_PROTOCOL_ERROR);
+                removeStream(streamId);
                 return;
             }
             callbackExecutor.execute(new Runnable() {
@@ -348,11 +411,29 @@
         @Override public void synReply(int flags, int streamId, List<String> nameValueBlock)
                 throws IOException {
             SpdyStream replyStream = getStream(streamId);
-            if (replyStream != null) {
-                // TODO: honor incoming FLAG_FIN.
-                replyStream.receiveReply(nameValueBlock);
-            } else {
+            if (replyStream == null) {
                 writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM);
+                return;
+            }
+            try {
+                replyStream.receiveReply(nameValueBlock);
+                if ((flags & SpdyConnection.FLAG_FIN) != 0) {
+                    replyStream.receiveFin();
+                }
+            } catch (ProtocolException e) {
+                replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR);
+            }
+        }
+
+        @Override public void headers(int flags, int streamId, List<String> nameValueBlock)
+                throws IOException {
+            SpdyStream replyStream = getStream(streamId);
+            if (replyStream != null) {
+                try {
+                    replyStream.receiveHeaders(nameValueBlock);
+                } catch (ProtocolException e) {
+                    replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR);
+                }
             }
         }
 
@@ -388,5 +469,22 @@
                 }
             }
         }
+
+        @Override public void goAway(int flags, int lastGoodStreamId) {
+            synchronized (SpdyConnection.this) {
+                shutdown = true;
+
+                // Fail all streams created after the last good stream ID.
+                for (Iterator<Map.Entry<Integer, SpdyStream>> i = streams.entrySet().iterator();
+                        i.hasNext();) {
+                    Map.Entry<Integer, SpdyStream> entry = i.next();
+                    int streamId = entry.getKey();
+                    if (streamId > lastGoodStreamId && entry.getValue().isLocallyInitiated()) {
+                        entry.getValue().receiveRstStream(SpdyStream.RST_REFUSED_STREAM);
+                        i.remove();
+                    }
+                }
+            }
+        }
     }
 }
diff --git a/src/main/java/libcore/net/spdy/SpdyReader.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java
similarity index 87%
rename from src/main/java/libcore/net/spdy/SpdyReader.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java
index 5240cd0..98fc975 100644
--- a/src/main/java/libcore/net/spdy/SpdyReader.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
+import com.squareup.okhttp.internal.Util;
+import java.io.Closeable;
 import java.io.DataInputStream;
-import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
@@ -27,12 +28,11 @@
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 import java.util.zip.InflaterInputStream;
-import libcore.io.Streams;
 
 /**
  * Read version 2 SPDY frames.
  */
-final class SpdyReader {
+final class SpdyReader implements Closeable {
     private static final String DICTIONARY_STRING = ""
             + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
             + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
@@ -73,8 +73,8 @@
         int w1;
         try {
             w1 = in.readInt();
-        } catch (EOFException e) {
-            return false;
+        } catch (IOException e) {
+            return false; // This might be a normal socket close.
         }
         int w2 = in.readInt();
 
@@ -113,9 +113,12 @@
                 return true;
 
             case SpdyConnection.TYPE_GOAWAY:
+                readGoAway(handler, flags, length);
+                return true;
+
             case SpdyConnection.TYPE_HEADERS:
-                Streams.skipByReading(in, length);
-                throw new UnsupportedOperationException("TODO");
+                readHeaders(handler, flags, length);
+                return true;
 
             default:
                 throw new IOException("Unexpected frame");
@@ -154,11 +157,19 @@
         handler.rstStream(flags, streamId, statusCode);
     }
 
+    private void readHeaders(Handler handler, int flags, int length) throws IOException {
+        int w1 = in.readInt();
+        in.readShort(); // unused
+        int streamId = w1 & 0x7fffffff;
+        List<String> nameValueBlock = readNameValueBlock(length - 6);
+        handler.headers(flags, streamId, nameValueBlock);
+    }
+
     private DataInputStream newNameValueBlockStream() {
         // Limit the inflater input stream to only those bytes in the Name/Value block.
         final InputStream throttleStream = new InputStream() {
             @Override public int read() throws IOException {
-                return Streams.readSingleByte(this);
+                return Util.readSingleByte(this);
             }
 
             @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException {
@@ -217,7 +228,7 @@
     private String readString() throws DataFormatException, IOException {
         int length = nameValueBlockIn.readShort();
         byte[] bytes = new byte[length];
-        Streams.readFully(nameValueBlockIn, bytes);
+        Util.readFully(nameValueBlockIn, bytes);
         return new String(bytes, 0, length, "UTF-8");
     }
 
@@ -227,6 +238,12 @@
         handler.ping(flags, id);
     }
 
+    private void readGoAway(Handler handler, int flags, int length) throws IOException {
+        if (length != 4) throw ioException("TYPE_GOAWAY length: %d != 4", length);
+        int lastGoodStreamId = in.readInt() & 0x7fffffff;
+        handler.goAway(flags, lastGoodStreamId);
+    }
+
     private void readSettings(Handler handler, int flags, int length) throws IOException {
         int numberOfEntries = in.readInt();
         if (length != 4 + 8 * numberOfEntries) {
@@ -250,16 +267,20 @@
         throw new IOException(String.format(message, args));
     }
 
+    @Override public void close() throws IOException {
+        Util.closeAll(in, nameValueBlockIn);
+    }
+
     public interface Handler {
         void data(int flags, int streamId, InputStream in, int length) throws IOException;
         void synStream(int flags, int streamId, int associatedStreamId, int priority,
                 List<String> nameValueBlock);
         void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException;
+        void headers(int flags, int streamId, List<String> nameValueBlock) throws IOException;
         void rstStream(int flags, int streamId, int statusCode);
         void settings(int flags, Settings settings);
         void noop();
         void ping(int flags, int streamId);
-        // TODO: goaway
-        // TODO: headers
+        void goAway(int flags, int lastGoodStreamId);
     }
 }
diff --git a/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
new file mode 100644
index 0000000..5c3d971
--- /dev/null
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
@@ -0,0 +1,619 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.Util;
+import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
+import static com.squareup.okhttp.internal.Util.pokeInt;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.ProtocolException;
+import java.net.SocketTimeoutException;
+import static java.nio.ByteOrder.BIG_ENDIAN;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A logical bidirectional stream.
+ */
+public final class SpdyStream {
+
+    /*
+     * Internal state is guarded by this. No long-running or potentially
+     * blocking operations are performed while the lock is held.
+     */
+
+    private static final int DATA_FRAME_HEADER_LENGTH = 8;
+
+    private static final String[] STATUS_CODE_NAMES = {
+            null,
+            "PROTOCOL_ERROR",
+            "INVALID_STREAM",
+            "REFUSED_STREAM",
+            "UNSUPPORTED_VERSION",
+            "CANCEL",
+            "INTERNAL_ERROR",
+            "FLOW_CONTROL_ERROR",
+    };
+
+    public static final int RST_PROTOCOL_ERROR = 1;
+    public static final int RST_INVALID_STREAM = 2;
+    public static final int RST_REFUSED_STREAM = 3;
+    public static final int RST_UNSUPPORTED_VERSION = 4;
+    public static final int RST_CANCEL = 5;
+    public static final int RST_INTERNAL_ERROR = 6;
+    public static final int RST_FLOW_CONTROL_ERROR = 7;
+
+    private final int id;
+    private final SpdyConnection connection;
+    private long readTimeoutMillis = 0;
+
+    /** Headers sent by the stream initiator. Immutable and non null. */
+    private final List<String> requestHeaders;
+
+    /** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */
+    private List<String> responseHeaders;
+
+    private final SpdyDataInputStream in = new SpdyDataInputStream();
+    private final SpdyDataOutputStream out = new SpdyDataOutputStream();
+
+    /**
+     * The reason why this stream was abnormally closed. If there are multiple
+     * reasons to abnormally close this stream (such as both peers closing it
+     * near-simultaneously) then this is the first reason known to this peer.
+     */
+    private int rstStatusCode = -1;
+
+    SpdyStream(int id, SpdyConnection connection, List<String> requestHeaders, int flags) {
+        this.id = id;
+        this.connection = connection;
+        this.requestHeaders = requestHeaders;
+
+        if (isLocallyInitiated()) {
+            // I am the sender
+            in.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
+            out.finished = (flags & SpdyConnection.FLAG_FIN) != 0;
+        } else {
+            // I am the receiver
+            in.finished = (flags & SpdyConnection.FLAG_FIN) != 0;
+            out.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
+        }
+    }
+
+    /**
+     * Returns true if this stream is open. A stream is open until either:
+     * <ul>
+     *   <li>A {@code SYN_RESET} frame abnormally terminates the stream.
+     *   <li>Both input and output streams have transmitted all data.
+     * </ul>
+     * Note that the input stream may continue to yield data even after a stream
+     * reports itself as not open. This is because input data is buffered.
+     */
+    public synchronized boolean isOpen() {
+        if (rstStatusCode != -1) {
+            return false;
+        }
+        if ((in.finished || in.closed) && (out.finished || out.closed)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if this stream was created by this peer.
+     */
+    public boolean isLocallyInitiated() {
+        boolean streamIsClient = (id % 2 == 1);
+        return connection.client == streamIsClient;
+    }
+
+    public SpdyConnection getConnection() {
+        return connection;
+    }
+
+    public List<String> getRequestHeaders() {
+        return requestHeaders;
+    }
+
+    /**
+     * Returns the stream's response headers, blocking if necessary if they
+     * have not been received yet.
+     */
+    public synchronized List<String> getResponseHeaders() throws IOException {
+        try {
+            while (responseHeaders == null && rstStatusCode == -1) {
+                wait();
+            }
+            if (responseHeaders != null) {
+                return responseHeaders;
+            }
+            throw new IOException("stream was reset: " + rstStatusString());
+        } catch (InterruptedException e) {
+            InterruptedIOException rethrow = new InterruptedIOException();
+            rethrow.initCause(e);
+            throw rethrow;
+        }
+    }
+
+    /**
+     * Returns the reason why this stream was closed, or -1 if it closed
+     * normally or has not yet been closed. Valid reasons are {@link
+     * #RST_PROTOCOL_ERROR}, {@link #RST_INVALID_STREAM}, {@link
+     * #RST_REFUSED_STREAM}, {@link #RST_UNSUPPORTED_VERSION}, {@link
+     * #RST_CANCEL}, {@link #RST_INTERNAL_ERROR} and {@link
+     * #RST_FLOW_CONTROL_ERROR}.
+     */
+    public synchronized int getRstStatusCode() {
+        return rstStatusCode;
+    }
+
+    /**
+     * Sends a reply to an incoming stream.
+     *
+     * @param out true to create an output stream that we can use to send data
+     *     to the remote peer. Corresponds to {@code FLAG_FIN}.
+     */
+    public void reply(List<String> responseHeaders, boolean out) throws IOException {
+        assert (!Thread.holdsLock(SpdyStream.this));
+        int flags = 0;
+        synchronized (this) {
+            if (responseHeaders == null) {
+                throw new NullPointerException("responseHeaders == null");
+            }
+            if (isLocallyInitiated()) {
+                throw new IllegalStateException("cannot reply to a locally initiated stream");
+            }
+            if (this.responseHeaders != null) {
+                throw new IllegalStateException("reply already sent");
+            }
+            this.responseHeaders = responseHeaders;
+            if (!out) {
+                this.out.finished = true;
+                flags |= SpdyConnection.FLAG_FIN;
+            }
+        }
+        connection.writeSynReply(id, flags, responseHeaders);
+    }
+
+    /**
+     * Sets the maximum time to wait on input stream reads before failing with a
+     * {@code SocketTimeoutException}, or {@code 0} to wait indefinitely.
+     */
+    public void setReadTimeout(long readTimeoutMillis) {
+        this.readTimeoutMillis = readTimeoutMillis;
+    }
+
+    public long getReadTimeoutMillis() {
+        return readTimeoutMillis;
+    }
+
+    /**
+     * Returns an input stream that can be used to read data from the peer.
+     */
+    public InputStream getInputStream() {
+        return in;
+    }
+
+    /**
+     * Returns an output stream that can be used to write data to the peer.
+     *
+     * @throws IllegalStateException if this stream was initiated by the peer
+     *     and a {@link #reply} has not yet been sent.
+     */
+    public OutputStream getOutputStream() {
+        synchronized (this) {
+            if (responseHeaders == null && !isLocallyInitiated()) {
+                throw new IllegalStateException("reply before requesting the output stream");
+            }
+        }
+        return out;
+    }
+
+    /**
+     * Abnormally terminate this stream.
+     */
+    public void close(int rstStatusCode) throws IOException {
+        if (!closeInternal(rstStatusCode)) {
+            return; // Already closed.
+        }
+        connection.writeSynReset(id, rstStatusCode);
+    }
+
+    void closeLater(int rstStatusCode) {
+        if (!closeInternal(rstStatusCode)) {
+            return; // Already closed.
+        }
+        connection.writeSynResetLater(id, rstStatusCode);
+    }
+
+    /**
+     * Returns true if this stream was closed.
+     */
+    private boolean closeInternal(int rstStatusCode) {
+        assert (!Thread.holdsLock(this));
+        synchronized (this) {
+            if (this.rstStatusCode != -1) {
+                return false;
+            }
+            if (in.finished && out.finished) {
+                return false;
+            }
+            this.rstStatusCode = rstStatusCode;
+            notifyAll();
+        }
+        connection.removeStream(id);
+        return true;
+    }
+
+    void receiveReply(List<String> strings) throws IOException {
+        assert (!Thread.holdsLock(SpdyStream.this));
+        synchronized (this) {
+            if (!isLocallyInitiated() || responseHeaders != null) {
+                throw new ProtocolException();
+            }
+            responseHeaders = strings;
+            notifyAll();
+        }
+    }
+
+    void receiveHeaders(List<String> headers) throws IOException {
+        assert (!Thread.holdsLock(SpdyStream.this));
+        synchronized (this) {
+            if (responseHeaders == null) {
+                throw new ProtocolException();
+            }
+            List<String> newHeaders = new ArrayList<String>();
+            newHeaders.addAll(responseHeaders);
+            newHeaders.addAll(headers);
+            this.responseHeaders = newHeaders;
+        }
+    }
+
+    void receiveData(InputStream in, int length) throws IOException {
+        assert (!Thread.holdsLock(SpdyStream.this));
+        this.in.receive(in, length);
+    }
+
+    void receiveFin() {
+        assert (!Thread.holdsLock(SpdyStream.this));
+        boolean open;
+        synchronized (this) {
+            this.in.finished = true;
+            open = isOpen();
+            notifyAll();
+        }
+        if (!open) {
+            connection.removeStream(id);
+        }
+    }
+
+    synchronized void receiveRstStream(int statusCode) {
+        if (rstStatusCode == -1) {
+            rstStatusCode = statusCode;
+            notifyAll();
+        }
+    }
+
+    private String rstStatusString() {
+        return rstStatusCode > 0 && rstStatusCode < STATUS_CODE_NAMES.length
+                ? STATUS_CODE_NAMES[rstStatusCode]
+                : Integer.toString(rstStatusCode);
+    }
+
+    /**
+     * An input stream that reads the incoming data frames of a stream. Although
+     * this class uses synchronization to safely receive incoming data frames,
+     * it is not intended for use by multiple readers.
+     */
+    private final class SpdyDataInputStream extends InputStream {
+        /*
+         * Store incoming data bytes in a circular buffer. When the buffer is
+         * empty, pos == -1. Otherwise pos is the first byte to read and limit
+         * is the first byte to write.
+         *
+         * { - - - X X X X - - - }
+         *         ^       ^
+         *        pos    limit
+         *
+         * { X X X - - - - X X X }
+         *         ^       ^
+         *       limit    pos
+         */
+
+        private final byte[] buffer = new byte[64 * 1024]; // 64KiB specified by TODO
+
+        /** the next byte to be read, or -1 if the buffer is empty. Never buffer.length */
+        private int pos = -1;
+
+        /** the last byte to be read. Never buffer.length */
+        private int limit;
+
+        /** True if the caller has closed this stream. */
+        private boolean closed;
+
+        /**
+         * True if either side has cleanly shut down this stream. We will
+         * receive no more bytes beyond those already in the buffer.
+         */
+        private boolean finished;
+
+        @Override public int available() throws IOException {
+            synchronized (SpdyStream.this) {
+                checkNotClosed();
+                if (pos == -1) {
+                    return 0;
+                } else if (limit > pos) {
+                    return limit - pos;
+                } else {
+                    return limit + (buffer.length - pos);
+                }
+            }
+        }
+
+        @Override public int read() throws IOException {
+            return Util.readSingleByte(this);
+        }
+
+        @Override public int read(byte[] b, int offset, int count) throws IOException {
+            synchronized (SpdyStream.this) {
+                checkOffsetAndCount(b.length, offset, count);
+                waitUntilReadable();
+                checkNotClosed();
+
+                if (pos == -1) {
+                    return -1;
+                }
+
+                int copied = 0;
+
+                // drain from [pos..buffer.length)
+                if (limit <= pos) {
+                    int bytesToCopy = Math.min(count, buffer.length - pos);
+                    System.arraycopy(buffer, pos, b, offset, bytesToCopy);
+                    pos += bytesToCopy;
+                    copied += bytesToCopy;
+                    if (pos == buffer.length) {
+                        pos = 0;
+                    }
+                }
+
+                // drain from [pos..limit)
+                if (copied < count) {
+                    int bytesToCopy = Math.min(limit - pos, count - copied);
+                    System.arraycopy(buffer, pos, b, offset + copied, bytesToCopy);
+                    pos += bytesToCopy;
+                    copied += bytesToCopy;
+                }
+
+                // TODO: notify peer of flow-control
+
+                if (pos == limit) {
+                    pos = -1;
+                    limit = 0;
+                }
+
+                return copied;
+            }
+        }
+
+        /**
+         * Returns once the input stream is either readable or finished. Throws
+         * a {@link SocketTimeoutException} if the read timeout elapses before
+         * that happens.
+         */
+        private void waitUntilReadable() throws IOException {
+            long start = 0;
+            long remaining = 0;
+            if (readTimeoutMillis != 0) {
+                start = (System.nanoTime() / 1000000);
+                remaining = readTimeoutMillis;
+            }
+            try {
+                while (pos == -1 && !finished && !closed && rstStatusCode == -1) {
+                    if (readTimeoutMillis == 0) {
+                        SpdyStream.this.wait();
+                    } else if (remaining > 0) {
+                        SpdyStream.this.wait(remaining);
+                        remaining = start + readTimeoutMillis - (System.nanoTime() / 1000000);
+                    } else {
+                        throw new SocketTimeoutException();
+                    }
+                }
+            } catch (InterruptedException e) {
+                throw new InterruptedIOException();
+            }
+        }
+
+        void receive(InputStream in, int byteCount) throws IOException {
+            assert (!Thread.holdsLock(SpdyStream.this));
+
+            if (byteCount == 0) {
+                return;
+            }
+
+            int pos;
+            int limit;
+            int firstNewByte;
+            boolean finished;
+            synchronized (SpdyStream.this) {
+                finished = this.finished;
+                pos = this.pos;
+                firstNewByte = this.limit;
+                limit = this.limit;
+                if (byteCount > buffer.length - available()) {
+                    throw new ProtocolException();
+                }
+            }
+
+            // Discard data received after the stream is finished. It's probably a benign race.
+            if (finished) {
+                Util.skipByReading(in, byteCount);
+                return;
+            }
+
+            // Fill the buffer without holding any locks. First fill [limit..buffer.length) if that
+            // won't overwrite unread data. Then fill [limit..pos). We can't hold a lock, otherwise
+            // writes will be blocked until reads complete.
+            if (pos < limit) {
+                int firstCopyCount = Math.min(byteCount, buffer.length - limit);
+                Util.readFully(in, buffer, limit, firstCopyCount);
+                limit += firstCopyCount;
+                byteCount -= firstCopyCount;
+                if (limit == buffer.length) {
+                    limit = 0;
+                }
+            }
+            if (byteCount > 0) {
+                Util.readFully(in, buffer, limit, byteCount);
+                limit += byteCount;
+            }
+
+            synchronized (SpdyStream.this) {
+                // Update the new limit, and mark the position as readable if necessary.
+                this.limit = limit;
+                if (this.pos == -1) {
+                    this.pos = firstNewByte;
+                    SpdyStream.this.notifyAll();
+                }
+            }
+        }
+
+        @Override public void close() throws IOException {
+            synchronized (SpdyStream.this) {
+                closed = true;
+                SpdyStream.this.notifyAll();
+            }
+            cancelStreamIfNecessary();
+        }
+
+        private void checkNotClosed() throws IOException {
+            if (closed) {
+                throw new IOException("stream closed");
+            }
+            if (rstStatusCode != -1) {
+                throw new IOException("stream was reset: " + rstStatusString());
+            }
+        }
+    }
+
+    private void cancelStreamIfNecessary() throws IOException {
+        assert (!Thread.holdsLock(SpdyStream.this));
+        boolean open;
+        boolean cancel;
+        synchronized (this) {
+            cancel = !in.finished && in.closed && (out.finished || out.closed);
+            open = isOpen();
+        }
+        if (cancel) {
+            // RST this stream to prevent additional data from being sent. This
+            // is safe because the input stream is closed (we won't use any
+            // further bytes) and the output stream is either finished or closed
+            // (so RSTing both streams doesn't cause harm).
+            SpdyStream.this.close(RST_CANCEL);
+        } else if (!open) {
+            connection.removeStream(id);
+        }
+    }
+
+    /**
+     * An output stream that writes outgoing data frames of a stream. This class
+     * is not thread safe.
+     */
+    private final class SpdyDataOutputStream extends OutputStream {
+        private final byte[] buffer = new byte[8192];
+        private int pos = DATA_FRAME_HEADER_LENGTH;
+
+        /** True if the caller has closed this stream. */
+        private boolean closed;
+
+        /**
+         * True if either side has cleanly shut down this stream. We shall send
+         * no more bytes.
+         */
+        private boolean finished;
+
+        @Override public void write(int b) throws IOException {
+            Util.writeSingleByte(this, b);
+        }
+
+        @Override public void write(byte[] bytes, int offset, int count) throws IOException {
+            assert (!Thread.holdsLock(SpdyStream.this));
+            checkOffsetAndCount(bytes.length, offset, count);
+            checkNotClosed();
+
+            while (count > 0) {
+                if (pos == buffer.length) {
+                    writeFrame(false);
+                }
+                int bytesToCopy = Math.min(count, buffer.length - pos);
+                System.arraycopy(bytes, offset, buffer, pos, bytesToCopy);
+                pos += bytesToCopy;
+                offset += bytesToCopy;
+                count -= bytesToCopy;
+            }
+        }
+
+        @Override public void flush() throws IOException {
+            assert (!Thread.holdsLock(SpdyStream.this));
+            checkNotClosed();
+            if (pos > DATA_FRAME_HEADER_LENGTH) {
+                writeFrame(false);
+                connection.flush();
+            }
+        }
+
+        @Override public void close() throws IOException {
+            assert (!Thread.holdsLock(SpdyStream.this));
+            synchronized (SpdyStream.this) {
+                if (closed) {
+                    return;
+                }
+                closed = true;
+            }
+            writeFrame(true);
+            connection.flush();
+            cancelStreamIfNecessary();
+        }
+
+        private void writeFrame(boolean last) throws IOException {
+            assert (!Thread.holdsLock(SpdyStream.this));
+            int flags = 0;
+            if (last) {
+                flags |= SpdyConnection.FLAG_FIN;
+            }
+            int length = pos - DATA_FRAME_HEADER_LENGTH;
+            pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN);
+            pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN);
+            connection.writeFrame(buffer, 0, pos);
+            pos = DATA_FRAME_HEADER_LENGTH;
+        }
+
+        private void checkNotClosed() throws IOException {
+            synchronized (SpdyStream.this) {
+                if (closed) {
+                    throw new IOException("stream closed");
+                } else if (finished) {
+                    throw new IOException("stream finished");
+                } else if (rstStatusCode != -1) {
+                    throw new IOException("stream was reset: " + rstStatusString());
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/libcore/net/spdy/SpdyWriter.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java
similarity index 69%
rename from src/main/java/libcore/net/spdy/SpdyWriter.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java
index f1fb620..8cd6ae0 100644
--- a/src/main/java/libcore/net/spdy/SpdyWriter.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java
@@ -14,22 +14,22 @@
  * limitations under the License.
  */
 
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
+import com.squareup.okhttp.internal.Platform;
+import com.squareup.okhttp.internal.Util;
 import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.List;
 import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-
-import static libcore.util.Libcore.newDeflaterOutputStream;
 
 /**
  * Write version 2 SPDY frames.
  */
-final class SpdyWriter {
+final class SpdyWriter implements Closeable {
     final DataOutputStream out;
     private final ByteArrayOutputStream nameValueBlockBuffer;
     private final DataOutputStream nameValueBlockOut;
@@ -41,11 +41,11 @@
         deflater.setDictionary(SpdyReader.DICTIONARY);
         nameValueBlockBuffer = new ByteArrayOutputStream();
         nameValueBlockOut = new DataOutputStream(
-               newDeflaterOutputStream(nameValueBlockBuffer, deflater, true));
+                Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true));
     }
 
-    public void synStream(int flags, int streamId, int associatedStreamId, int priority,
-            List<String> nameValueBlock) throws IOException {
+    public synchronized void synStream(int flags, int streamId, int associatedStreamId,
+            int priority, List<String> nameValueBlock) throws IOException {
         writeNameValueBlockToBuffer(nameValueBlock);
         int length = 10 + nameValueBlockBuffer.size();
         int type = SpdyConnection.TYPE_SYN_STREAM;
@@ -60,7 +60,8 @@
         out.flush();
     }
 
-    public void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException {
+    public synchronized void synReply(
+            int flags, int streamId, List<String> nameValueBlock) throws IOException {
         writeNameValueBlockToBuffer(nameValueBlock);
         int type = SpdyConnection.TYPE_SYN_REPLY;
         int length = nameValueBlockBuffer.size() + 6;
@@ -74,7 +75,22 @@
         out.flush();
     }
 
-    public void synReset(int streamId, int statusCode) throws IOException {
+    public synchronized void headers(
+            int flags, int streamId, List<String> nameValueBlock) throws IOException {
+        writeNameValueBlockToBuffer(nameValueBlock);
+        int type = SpdyConnection.TYPE_HEADERS;
+        int length = nameValueBlockBuffer.size() + 6;
+        int unused = 0;
+
+        out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
+        out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+        out.writeInt(streamId & 0x7fffffff);
+        out.writeShort(unused);
+        nameValueBlockBuffer.writeTo(out);
+        out.flush();
+    }
+
+    public synchronized void synReset(int streamId, int statusCode) throws IOException {
         int flags = 0;
         int type = SpdyConnection.TYPE_RST_STREAM;
         int length = 8;
@@ -85,7 +101,7 @@
         out.flush();
     }
 
-    public void data(int flags, int streamId, byte[] data) throws IOException {
+    public synchronized void data(int flags, int streamId, byte[] data) throws IOException {
         int length = data.length;
         out.writeInt(streamId & 0x7fffffff);
         out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
@@ -104,7 +120,7 @@
         nameValueBlockOut.flush();
     }
 
-    public void settings(int flags, Settings settings) throws IOException {
+    public synchronized void settings(int flags, Settings settings) throws IOException {
         int type = SpdyConnection.TYPE_SETTINGS;
         int size = settings.size();
         int length = 4 + size * 8;
@@ -124,7 +140,7 @@
         out.flush();
     }
 
-    public void noop() throws IOException {
+    public synchronized void noop() throws IOException {
         int type = SpdyConnection.TYPE_NOOP;
         int length = 0;
         int flags = 0;
@@ -133,7 +149,7 @@
         out.flush();
     }
 
-    public void ping(int flags, int id) throws IOException {
+    public synchronized void ping(int flags, int id) throws IOException {
         int type = SpdyConnection.TYPE_PING;
         int length = 4;
         out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
@@ -141,4 +157,17 @@
         out.writeInt(id);
         out.flush();
     }
+
+    public synchronized void goAway(int flags, int lastGoodStreamId) throws IOException {
+        int type = SpdyConnection.TYPE_GOAWAY;
+        int length = 4;
+        out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
+        out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
+        out.writeInt(lastGoodStreamId);
+        out.flush();
+    }
+
+    @Override public void close() throws IOException {
+        Util.closeAll(out, nameValueBlockOut);
+    }
 }
diff --git a/src/main/java/libcore/net/spdy/Threads.java b/src/main/java/com/squareup/okhttp/internal/spdy/Threads.java
similarity index 95%
rename from src/main/java/libcore/net/spdy/Threads.java
rename to src/main/java/com/squareup/okhttp/internal/spdy/Threads.java
index 9e257f3..ef1d4f3 100644
--- a/src/main/java/libcore/net/spdy/Threads.java
+++ b/src/main/java/com/squareup/okhttp/internal/spdy/Threads.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
 import java.util.concurrent.ThreadFactory;
 
diff --git a/src/main/java/libcore/io/AsynchronousCloseMonitor.java b/src/main/java/libcore/io/AsynchronousCloseMonitor.java
deleted file mode 100644
index 62eec24..0000000
--- a/src/main/java/libcore/io/AsynchronousCloseMonitor.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.io.FileDescriptor;
-
-public final class AsynchronousCloseMonitor {
-    private AsynchronousCloseMonitor() {
-    }
-
-    public static native void signalBlockedThreads(FileDescriptor fd);
-}
diff --git a/src/main/java/libcore/io/BufferIterator.java b/src/main/java/libcore/io/BufferIterator.java
deleted file mode 100644
index 7f3ad47..0000000
--- a/src/main/java/libcore/io/BufferIterator.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-/**
- * Iterates over big- or little-endian bytes. See {@link MemoryMappedFile#bigEndianIterator} and
- * {@link MemoryMappedFile#littleEndianIterator}.
- *
- * @hide don't make this public without adding bounds checking.
- */
-public abstract class BufferIterator {
-    /**
-     * Seeks to the absolute position {@code offset}, measured in bytes from the start.
-     */
-    public abstract void seek(int offset);
-
-    /**
-     * Skips forwards or backwards {@code byteCount} bytes from the current position.
-     */
-    public abstract void skip(int byteCount);
-
-    /**
-     * Copies {@code byteCount} bytes from the current position into {@code dst}, starting at
-     * {@code dstOffset}, and advances the current position {@code byteCount} bytes.
-     */
-    public abstract void readByteArray(byte[] dst, int dstOffset, int byteCount);
-
-    /**
-     * Returns the byte at the current position, and advances the current position one byte.
-     */
-    public abstract byte readByte();
-
-    /**
-     * Returns the 32-bit int at the current position, and advances the current position four bytes.
-     */
-    public abstract int readInt();
-
-    /**
-     * Copies {@code intCount} 32-bit ints from the current position into {@code dst}, starting at
-     * {@code dstOffset}, and advances the current position {@code 4 * intCount} bytes.
-     */
-    public abstract void readIntArray(int[] dst, int dstOffset, int intCount);
-
-    /**
-     * Returns the 16-bit short at the current position, and advances the current position two bytes.
-     */
-    public abstract short readShort();
-}
diff --git a/src/main/java/libcore/io/IoUtils.java b/src/main/java/libcore/io/IoUtils.java
deleted file mode 100644
index 307737d..0000000
--- a/src/main/java/libcore/io/IoUtils.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.net.Socket;
-
-public final class IoUtils {
-    private IoUtils() {
-    }
-
-    /**
-     * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
-     */
-    public static void closeQuietly(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    /**
-     * Closes 'socket', ignoring any exceptions. Does nothing if 'socket' is null.
-     */
-    public static void closeQuietly(Socket socket) {
-        if (socket != null) {
-            try {
-                socket.close();
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    /**
-     * Recursively delete everything in {@code dir}.
-     */
-    // TODO: this should specify paths as Strings rather than as Files
-    public static void deleteContents(File dir) throws IOException {
-        File[] files = dir.listFiles();
-        if (files == null) {
-            throw new IllegalArgumentException("not a directory: " + dir);
-        }
-        for (File file : files) {
-            if (file.isDirectory()) {
-                deleteContents(file);
-            }
-            if (!file.delete()) {
-                throw new IOException("failed to delete file: " + file);
-            }
-        }
-    }
-}
diff --git a/src/main/java/libcore/io/OsConstants.java b/src/main/java/libcore/io/OsConstants.java
deleted file mode 100644
index 68a165c..0000000
--- a/src/main/java/libcore/io/OsConstants.java
+++ /dev/null
@@ -1,724 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-public final class OsConstants {
-    private OsConstants() { }
-
-    public static boolean S_ISBLK(int mode) { return (mode & S_IFMT) == S_IFBLK; }
-    public static boolean S_ISCHR(int mode) { return (mode & S_IFMT) == S_IFCHR; }
-    public static boolean S_ISDIR(int mode) { return (mode & S_IFMT) == S_IFDIR; }
-    public static boolean S_ISFIFO(int mode) { return (mode & S_IFMT) == S_IFIFO; }
-    public static boolean S_ISREG(int mode) { return (mode & S_IFMT) == S_IFREG; }
-    public static boolean S_ISLNK(int mode) { return (mode & S_IFMT) == S_IFLNK; }
-    public static boolean S_ISSOCK(int mode) { return (mode & S_IFMT) == S_IFSOCK; }
-
-    public static int WEXITSTATUS(int status) { return (status & 0xff00) >> 8; }
-    public static boolean WCOREDUMP(int status) { return (status & 0x80) != 0; }
-    public static int WTERMSIG(int status) { return status & 0x7f; }
-    public static int WSTOPSIG(int status) { return WEXITSTATUS(status); }
-    public static boolean WIFEXITED(int status) { return (WTERMSIG(status) == 0); }
-    public static boolean WIFSTOPPED(int status) { return (WTERMSIG(status) == 0x7f); }
-    public static boolean WIFSIGNALED(int status) { return (WTERMSIG(status + 1) >= 2); }
-
-    public static final int AF_INET = placeholder();
-    public static final int AF_INET6 = placeholder();
-    public static final int AF_UNIX = placeholder();
-    public static final int AF_UNSPEC = placeholder();
-    public static final int AI_ADDRCONFIG = placeholder();
-    public static final int AI_ALL = placeholder();
-    public static final int AI_CANONNAME = placeholder();
-    public static final int AI_NUMERICHOST = placeholder();
-    public static final int AI_NUMERICSERV = placeholder();
-    public static final int AI_PASSIVE = placeholder();
-    public static final int AI_V4MAPPED = placeholder();
-    public static final int E2BIG = placeholder();
-    public static final int EACCES = placeholder();
-    public static final int EADDRINUSE = placeholder();
-    public static final int EADDRNOTAVAIL = placeholder();
-    public static final int EAFNOSUPPORT = placeholder();
-    public static final int EAGAIN = placeholder();
-    public static final int EAI_AGAIN = placeholder();
-    public static final int EAI_BADFLAGS = placeholder();
-    public static final int EAI_FAIL = placeholder();
-    public static final int EAI_FAMILY = placeholder();
-    public static final int EAI_MEMORY = placeholder();
-    public static final int EAI_NODATA = placeholder();
-    public static final int EAI_NONAME = placeholder();
-    public static final int EAI_OVERFLOW = placeholder();
-    public static final int EAI_SERVICE = placeholder();
-    public static final int EAI_SOCKTYPE = placeholder();
-    public static final int EAI_SYSTEM = placeholder();
-    public static final int EALREADY = placeholder();
-    public static final int EBADF = placeholder();
-    public static final int EBADMSG = placeholder();
-    public static final int EBUSY = placeholder();
-    public static final int ECANCELED = placeholder();
-    public static final int ECHILD = placeholder();
-    public static final int ECONNABORTED = placeholder();
-    public static final int ECONNREFUSED = placeholder();
-    public static final int ECONNRESET = placeholder();
-    public static final int EDEADLK = placeholder();
-    public static final int EDESTADDRREQ = placeholder();
-    public static final int EDOM = placeholder();
-    public static final int EDQUOT = placeholder();
-    public static final int EEXIST = placeholder();
-    public static final int EFAULT = placeholder();
-    public static final int EFBIG = placeholder();
-    public static final int EHOSTUNREACH = placeholder();
-    public static final int EIDRM = placeholder();
-    public static final int EILSEQ = placeholder();
-    public static final int EINPROGRESS = placeholder();
-    public static final int EINTR = placeholder();
-    public static final int EINVAL = placeholder();
-    public static final int EIO = placeholder();
-    public static final int EISCONN = placeholder();
-    public static final int EISDIR = placeholder();
-    public static final int ELOOP = placeholder();
-    public static final int EMFILE = placeholder();
-    public static final int EMLINK = placeholder();
-    public static final int EMSGSIZE = placeholder();
-    public static final int EMULTIHOP = placeholder();
-    public static final int ENAMETOOLONG = placeholder();
-    public static final int ENETDOWN = placeholder();
-    public static final int ENETRESET = placeholder();
-    public static final int ENETUNREACH = placeholder();
-    public static final int ENFILE = placeholder();
-    public static final int ENOBUFS = placeholder();
-    public static final int ENODATA = placeholder();
-    public static final int ENODEV = placeholder();
-    public static final int ENOENT = placeholder();
-    public static final int ENOEXEC = placeholder();
-    public static final int ENOLCK = placeholder();
-    public static final int ENOLINK = placeholder();
-    public static final int ENOMEM = placeholder();
-    public static final int ENOMSG = placeholder();
-    public static final int ENOPROTOOPT = placeholder();
-    public static final int ENOSPC = placeholder();
-    public static final int ENOSR = placeholder();
-    public static final int ENOSTR = placeholder();
-    public static final int ENOSYS = placeholder();
-    public static final int ENOTCONN = placeholder();
-    public static final int ENOTDIR = placeholder();
-    public static final int ENOTEMPTY = placeholder();
-    public static final int ENOTSOCK = placeholder();
-    public static final int ENOTSUP = placeholder();
-    public static final int ENOTTY = placeholder();
-    public static final int ENXIO = placeholder();
-    public static final int EOPNOTSUPP = placeholder();
-    public static final int EOVERFLOW = placeholder();
-    public static final int EPERM = placeholder();
-    public static final int EPIPE = placeholder();
-    public static final int EPROTO = placeholder();
-    public static final int EPROTONOSUPPORT = placeholder();
-    public static final int EPROTOTYPE = placeholder();
-    public static final int ERANGE = placeholder();
-    public static final int EROFS = placeholder();
-    public static final int ESPIPE = placeholder();
-    public static final int ESRCH = placeholder();
-    public static final int ESTALE = placeholder();
-    public static final int ETIME = placeholder();
-    public static final int ETIMEDOUT = placeholder();
-    public static final int ETXTBSY = placeholder();
-    public static final int EWOULDBLOCK = placeholder();
-    public static final int EXDEV = placeholder();
-    public static final int EXIT_FAILURE = placeholder();
-    public static final int EXIT_SUCCESS = placeholder();
-    public static final int FD_CLOEXEC = placeholder();
-    public static final int FIONREAD = placeholder();
-    public static final int F_DUPFD = placeholder();
-    public static final int F_GETFD = placeholder();
-    public static final int F_GETFL = placeholder();
-    public static final int F_GETLK = placeholder();
-    public static final int F_GETLK64 = placeholder();
-    public static final int F_GETOWN = placeholder();
-    public static final int F_OK = placeholder();
-    public static final int F_RDLCK = placeholder();
-    public static final int F_SETFD = placeholder();
-    public static final int F_SETFL = placeholder();
-    public static final int F_SETLK = placeholder();
-    public static final int F_SETLK64 = placeholder();
-    public static final int F_SETLKW = placeholder();
-    public static final int F_SETLKW64 = placeholder();
-    public static final int F_SETOWN = placeholder();
-    public static final int F_UNLCK = placeholder();
-    public static final int F_WRLCK = placeholder();
-    public static final int IFF_ALLMULTI = placeholder();
-    public static final int IFF_AUTOMEDIA = placeholder();
-    public static final int IFF_BROADCAST = placeholder();
-    public static final int IFF_DEBUG = placeholder();
-    public static final int IFF_DYNAMIC = placeholder();
-    public static final int IFF_LOOPBACK = placeholder();
-    public static final int IFF_MASTER = placeholder();
-    public static final int IFF_MULTICAST = placeholder();
-    public static final int IFF_NOARP = placeholder();
-    public static final int IFF_NOTRAILERS = placeholder();
-    public static final int IFF_POINTOPOINT = placeholder();
-    public static final int IFF_PORTSEL = placeholder();
-    public static final int IFF_PROMISC = placeholder();
-    public static final int IFF_RUNNING = placeholder();
-    public static final int IFF_SLAVE = placeholder();
-    public static final int IFF_UP = placeholder();
-    public static final int IPPROTO_ICMP = placeholder();
-    public static final int IPPROTO_IP = placeholder();
-    public static final int IPPROTO_IPV6 = placeholder();
-    public static final int IPPROTO_RAW = placeholder();
-    public static final int IPPROTO_TCP = placeholder();
-    public static final int IPPROTO_UDP = placeholder();
-    public static final int IPV6_CHECKSUM = placeholder();
-    public static final int IPV6_MULTICAST_HOPS = placeholder();
-    public static final int IPV6_MULTICAST_IF = placeholder();
-    public static final int IPV6_MULTICAST_LOOP = placeholder();
-    public static final int IPV6_RECVDSTOPTS = placeholder();
-    public static final int IPV6_RECVHOPLIMIT = placeholder();
-    public static final int IPV6_RECVHOPOPTS = placeholder();
-    public static final int IPV6_RECVPKTINFO = placeholder();
-    public static final int IPV6_RECVRTHDR = placeholder();
-    public static final int IPV6_RECVTCLASS = placeholder();
-    public static final int IPV6_TCLASS = placeholder();
-    public static final int IPV6_UNICAST_HOPS = placeholder();
-    public static final int IPV6_V6ONLY = placeholder();
-    public static final int IP_MULTICAST_IF = placeholder();
-    public static final int IP_MULTICAST_LOOP = placeholder();
-    public static final int IP_MULTICAST_TTL = placeholder();
-    public static final int IP_TOS = placeholder();
-    public static final int IP_TTL = placeholder();
-    public static final int MAP_FIXED = placeholder();
-    public static final int MAP_PRIVATE = placeholder();
-    public static final int MAP_SHARED = placeholder();
-    public static final int MCAST_JOIN_GROUP = placeholder();
-    public static final int MCAST_LEAVE_GROUP = placeholder();
-    public static final int MCL_CURRENT = placeholder();
-    public static final int MCL_FUTURE = placeholder();
-    public static final int MSG_CTRUNC = placeholder();
-    public static final int MSG_DONTROUTE = placeholder();
-    public static final int MSG_EOR = placeholder();
-    public static final int MSG_OOB = placeholder();
-    public static final int MSG_PEEK = placeholder();
-    public static final int MSG_TRUNC = placeholder();
-    public static final int MSG_WAITALL = placeholder();
-    public static final int MS_ASYNC = placeholder();
-    public static final int MS_INVALIDATE = placeholder();
-    public static final int MS_SYNC = placeholder();
-    public static final int NI_DGRAM = placeholder();
-    public static final int NI_NAMEREQD = placeholder();
-    public static final int NI_NOFQDN = placeholder();
-    public static final int NI_NUMERICHOST = placeholder();
-    public static final int NI_NUMERICSERV = placeholder();
-    public static final int O_ACCMODE = placeholder();
-    public static final int O_APPEND = placeholder();
-    public static final int O_CREAT = placeholder();
-    public static final int O_EXCL = placeholder();
-    public static final int O_NOCTTY = placeholder();
-    public static final int O_NONBLOCK = placeholder();
-    public static final int O_RDONLY = placeholder();
-    public static final int O_RDWR = placeholder();
-    public static final int O_SYNC = placeholder();
-    public static final int O_TRUNC = placeholder();
-    public static final int O_WRONLY = placeholder();
-    public static final int POLLERR = placeholder();
-    public static final int POLLHUP = placeholder();
-    public static final int POLLIN = placeholder();
-    public static final int POLLNVAL = placeholder();
-    public static final int POLLOUT = placeholder();
-    public static final int POLLPRI = placeholder();
-    public static final int POLLRDBAND = placeholder();
-    public static final int POLLRDNORM = placeholder();
-    public static final int POLLWRBAND = placeholder();
-    public static final int POLLWRNORM = placeholder();
-    public static final int PROT_EXEC = placeholder();
-    public static final int PROT_NONE = placeholder();
-    public static final int PROT_READ = placeholder();
-    public static final int PROT_WRITE = placeholder();
-    public static final int R_OK = placeholder();
-    public static final int SEEK_CUR = placeholder();
-    public static final int SEEK_END = placeholder();
-    public static final int SEEK_SET = placeholder();
-    public static final int SHUT_RD = placeholder();
-    public static final int SHUT_RDWR = placeholder();
-    public static final int SHUT_WR = placeholder();
-    public static final int SIGABRT = placeholder();
-    public static final int SIGALRM = placeholder();
-    public static final int SIGBUS = placeholder();
-    public static final int SIGCHLD = placeholder();
-    public static final int SIGCONT = placeholder();
-    public static final int SIGFPE = placeholder();
-    public static final int SIGHUP = placeholder();
-    public static final int SIGILL = placeholder();
-    public static final int SIGINT = placeholder();
-    public static final int SIGIO = placeholder();
-    public static final int SIGKILL = placeholder();
-    public static final int SIGPIPE = placeholder();
-    public static final int SIGPROF = placeholder();
-    public static final int SIGPWR = placeholder();
-    public static final int SIGQUIT = placeholder();
-    public static final int SIGRTMAX = placeholder();
-    public static final int SIGRTMIN = placeholder();
-    public static final int SIGSEGV = placeholder();
-    public static final int SIGSTKFLT = placeholder();
-    public static final int SIGSTOP = placeholder();
-    public static final int SIGSYS = placeholder();
-    public static final int SIGTERM = placeholder();
-    public static final int SIGTRAP = placeholder();
-    public static final int SIGTSTP = placeholder();
-    public static final int SIGTTIN = placeholder();
-    public static final int SIGTTOU = placeholder();
-    public static final int SIGURG = placeholder();
-    public static final int SIGUSR1 = placeholder();
-    public static final int SIGUSR2 = placeholder();
-    public static final int SIGVTALRM = placeholder();
-    public static final int SIGWINCH = placeholder();
-    public static final int SIGXCPU = placeholder();
-    public static final int SIGXFSZ = placeholder();
-    public static final int SIOCGIFADDR = placeholder();
-    public static final int SIOCGIFBRDADDR = placeholder();
-    public static final int SIOCGIFDSTADDR = placeholder();
-    public static final int SIOCGIFNETMASK = placeholder();
-    public static final int SOCK_DGRAM = placeholder();
-    public static final int SOCK_RAW = placeholder();
-    public static final int SOCK_SEQPACKET = placeholder();
-    public static final int SOCK_STREAM = placeholder();
-    public static final int SOL_SOCKET = placeholder();
-    public static final int SO_BINDTODEVICE = placeholder();
-    public static final int SO_BROADCAST = placeholder();
-    public static final int SO_DEBUG = placeholder();
-    public static final int SO_DONTROUTE = placeholder();
-    public static final int SO_ERROR = placeholder();
-    public static final int SO_KEEPALIVE = placeholder();
-    public static final int SO_LINGER = placeholder();
-    public static final int SO_OOBINLINE = placeholder();
-    public static final int SO_RCVBUF = placeholder();
-    public static final int SO_RCVLOWAT = placeholder();
-    public static final int SO_RCVTIMEO = placeholder();
-    public static final int SO_REUSEADDR = placeholder();
-    public static final int SO_SNDBUF = placeholder();
-    public static final int SO_SNDLOWAT = placeholder();
-    public static final int SO_SNDTIMEO = placeholder();
-    public static final int SO_TYPE = placeholder();
-    public static final int STDERR_FILENO = placeholder();
-    public static final int STDIN_FILENO = placeholder();
-    public static final int STDOUT_FILENO = placeholder();
-    public static final int S_IFBLK = placeholder();
-    public static final int S_IFCHR = placeholder();
-    public static final int S_IFDIR = placeholder();
-    public static final int S_IFIFO = placeholder();
-    public static final int S_IFLNK = placeholder();
-    public static final int S_IFMT = placeholder();
-    public static final int S_IFREG = placeholder();
-    public static final int S_IFSOCK = placeholder();
-    public static final int S_IRGRP = placeholder();
-    public static final int S_IROTH = placeholder();
-    public static final int S_IRUSR = placeholder();
-    public static final int S_IRWXG = placeholder();
-    public static final int S_IRWXO = placeholder();
-    public static final int S_IRWXU = placeholder();
-    public static final int S_ISGID = placeholder();
-    public static final int S_ISUID = placeholder();
-    public static final int S_ISVTX = placeholder();
-    public static final int S_IWGRP = placeholder();
-    public static final int S_IWOTH = placeholder();
-    public static final int S_IWUSR = placeholder();
-    public static final int S_IXGRP = placeholder();
-    public static final int S_IXOTH = placeholder();
-    public static final int S_IXUSR = placeholder();
-    public static final int TCP_NODELAY = placeholder();
-    public static final int WCONTINUED = placeholder();
-    public static final int WEXITED = placeholder();
-    public static final int WNOHANG = placeholder();
-    public static final int WNOWAIT = placeholder();
-    public static final int WSTOPPED = placeholder();
-    public static final int WUNTRACED = placeholder();
-    public static final int W_OK = placeholder();
-    public static final int X_OK = placeholder();
-    public static final int _SC_2_CHAR_TERM = placeholder();
-    public static final int _SC_2_C_BIND = placeholder();
-    public static final int _SC_2_C_DEV = placeholder();
-    public static final int _SC_2_C_VERSION = placeholder();
-    public static final int _SC_2_FORT_DEV = placeholder();
-    public static final int _SC_2_FORT_RUN = placeholder();
-    public static final int _SC_2_LOCALEDEF = placeholder();
-    public static final int _SC_2_SW_DEV = placeholder();
-    public static final int _SC_2_UPE = placeholder();
-    public static final int _SC_2_VERSION = placeholder();
-    public static final int _SC_AIO_LISTIO_MAX = placeholder();
-    public static final int _SC_AIO_MAX = placeholder();
-    public static final int _SC_AIO_PRIO_DELTA_MAX = placeholder();
-    public static final int _SC_ARG_MAX = placeholder();
-    public static final int _SC_ASYNCHRONOUS_IO = placeholder();
-    public static final int _SC_ATEXIT_MAX = placeholder();
-    public static final int _SC_AVPHYS_PAGES = placeholder();
-    public static final int _SC_BC_BASE_MAX = placeholder();
-    public static final int _SC_BC_DIM_MAX = placeholder();
-    public static final int _SC_BC_SCALE_MAX = placeholder();
-    public static final int _SC_BC_STRING_MAX = placeholder();
-    public static final int _SC_CHILD_MAX = placeholder();
-    public static final int _SC_CLK_TCK = placeholder();
-    public static final int _SC_COLL_WEIGHTS_MAX = placeholder();
-    public static final int _SC_DELAYTIMER_MAX = placeholder();
-    public static final int _SC_EXPR_NEST_MAX = placeholder();
-    public static final int _SC_FSYNC = placeholder();
-    public static final int _SC_GETGR_R_SIZE_MAX = placeholder();
-    public static final int _SC_GETPW_R_SIZE_MAX = placeholder();
-    public static final int _SC_IOV_MAX = placeholder();
-    public static final int _SC_JOB_CONTROL = placeholder();
-    public static final int _SC_LINE_MAX = placeholder();
-    public static final int _SC_LOGIN_NAME_MAX = placeholder();
-    public static final int _SC_MAPPED_FILES = placeholder();
-    public static final int _SC_MEMLOCK = placeholder();
-    public static final int _SC_MEMLOCK_RANGE = placeholder();
-    public static final int _SC_MEMORY_PROTECTION = placeholder();
-    public static final int _SC_MESSAGE_PASSING = placeholder();
-    public static final int _SC_MQ_OPEN_MAX = placeholder();
-    public static final int _SC_MQ_PRIO_MAX = placeholder();
-    public static final int _SC_NGROUPS_MAX = placeholder();
-    public static final int _SC_NPROCESSORS_CONF = placeholder();
-    public static final int _SC_NPROCESSORS_ONLN = placeholder();
-    public static final int _SC_OPEN_MAX = placeholder();
-    public static final int _SC_PAGESIZE = placeholder();
-    public static final int _SC_PAGE_SIZE = placeholder();
-    public static final int _SC_PASS_MAX = placeholder();
-    public static final int _SC_PHYS_PAGES = placeholder();
-    public static final int _SC_PRIORITIZED_IO = placeholder();
-    public static final int _SC_PRIORITY_SCHEDULING = placeholder();
-    public static final int _SC_REALTIME_SIGNALS = placeholder();
-    public static final int _SC_RE_DUP_MAX = placeholder();
-    public static final int _SC_RTSIG_MAX = placeholder();
-    public static final int _SC_SAVED_IDS = placeholder();
-    public static final int _SC_SEMAPHORES = placeholder();
-    public static final int _SC_SEM_NSEMS_MAX = placeholder();
-    public static final int _SC_SEM_VALUE_MAX = placeholder();
-    public static final int _SC_SHARED_MEMORY_OBJECTS = placeholder();
-    public static final int _SC_SIGQUEUE_MAX = placeholder();
-    public static final int _SC_STREAM_MAX = placeholder();
-    public static final int _SC_SYNCHRONIZED_IO = placeholder();
-    public static final int _SC_THREADS = placeholder();
-    public static final int _SC_THREAD_ATTR_STACKADDR = placeholder();
-    public static final int _SC_THREAD_ATTR_STACKSIZE = placeholder();
-    public static final int _SC_THREAD_DESTRUCTOR_ITERATIONS = placeholder();
-    public static final int _SC_THREAD_KEYS_MAX = placeholder();
-    public static final int _SC_THREAD_PRIORITY_SCHEDULING = placeholder();
-    public static final int _SC_THREAD_PRIO_INHERIT = placeholder();
-    public static final int _SC_THREAD_PRIO_PROTECT = placeholder();
-    public static final int _SC_THREAD_SAFE_FUNCTIONS = placeholder();
-    public static final int _SC_THREAD_STACK_MIN = placeholder();
-    public static final int _SC_THREAD_THREADS_MAX = placeholder();
-    public static final int _SC_TIMERS = placeholder();
-    public static final int _SC_TIMER_MAX = placeholder();
-    public static final int _SC_TTY_NAME_MAX = placeholder();
-    public static final int _SC_TZNAME_MAX = placeholder();
-    public static final int _SC_VERSION = placeholder();
-    public static final int _SC_XBS5_ILP32_OFF32 = placeholder();
-    public static final int _SC_XBS5_ILP32_OFFBIG = placeholder();
-    public static final int _SC_XBS5_LP64_OFF64 = placeholder();
-    public static final int _SC_XBS5_LPBIG_OFFBIG = placeholder();
-    public static final int _SC_XOPEN_CRYPT = placeholder();
-    public static final int _SC_XOPEN_ENH_I18N = placeholder();
-    public static final int _SC_XOPEN_LEGACY = placeholder();
-    public static final int _SC_XOPEN_REALTIME = placeholder();
-    public static final int _SC_XOPEN_REALTIME_THREADS = placeholder();
-    public static final int _SC_XOPEN_SHM = placeholder();
-    public static final int _SC_XOPEN_UNIX = placeholder();
-    public static final int _SC_XOPEN_VERSION = placeholder();
-    public static final int _SC_XOPEN_XCU_VERSION = placeholder();
-
-    public static String gaiName(int error) {
-        if (error == EAI_AGAIN) {
-            return "EAI_AGAIN";
-        }
-        if (error == EAI_BADFLAGS) {
-            return "EAI_BADFLAGS";
-        }
-        if (error == EAI_FAIL) {
-            return "EAI_FAIL";
-        }
-        if (error == EAI_FAMILY) {
-            return "EAI_FAMILY";
-        }
-        if (error == EAI_MEMORY) {
-            return "EAI_MEMORY";
-        }
-        if (error == EAI_NODATA) {
-            return "EAI_NODATA";
-        }
-        if (error == EAI_NONAME) {
-            return "EAI_NONAME";
-        }
-        if (error == EAI_OVERFLOW) {
-            return "EAI_OVERFLOW";
-        }
-        if (error == EAI_SERVICE) {
-            return "EAI_SERVICE";
-        }
-        if (error == EAI_SOCKTYPE) {
-            return "EAI_SOCKTYPE";
-        }
-        if (error == EAI_SYSTEM) {
-            return "EAI_SYSTEM";
-        }
-        return null;
-    }
-
-    public static String errnoName(int errno) {
-        if (errno == E2BIG) {
-            return "E2BIG";
-        }
-        if (errno == EACCES) {
-            return "EACCES";
-        }
-        if (errno == EADDRINUSE) {
-            return "EADDRINUSE";
-        }
-        if (errno == EADDRNOTAVAIL) {
-            return "EADDRNOTAVAIL";
-        }
-        if (errno == EAFNOSUPPORT) {
-            return "EAFNOSUPPORT";
-        }
-        if (errno == EAGAIN) {
-            return "EAGAIN";
-        }
-        if (errno == EALREADY) {
-            return "EALREADY";
-        }
-        if (errno == EBADF) {
-            return "EBADF";
-        }
-        if (errno == EBADMSG) {
-            return "EBADMSG";
-        }
-        if (errno == EBUSY) {
-            return "EBUSY";
-        }
-        if (errno == ECANCELED) {
-            return "ECANCELED";
-        }
-        if (errno == ECHILD) {
-            return "ECHILD";
-        }
-        if (errno == ECONNABORTED) {
-            return "ECONNABORTED";
-        }
-        if (errno == ECONNREFUSED) {
-            return "ECONNREFUSED";
-        }
-        if (errno == ECONNRESET) {
-            return "ECONNRESET";
-        }
-        if (errno == EDEADLK) {
-            return "EDEADLK";
-        }
-        if (errno == EDESTADDRREQ) {
-            return "EDESTADDRREQ";
-        }
-        if (errno == EDOM) {
-            return "EDOM";
-        }
-        if (errno == EDQUOT) {
-            return "EDQUOT";
-        }
-        if (errno == EEXIST) {
-            return "EEXIST";
-        }
-        if (errno == EFAULT) {
-            return "EFAULT";
-        }
-        if (errno == EFBIG) {
-            return "EFBIG";
-        }
-        if (errno == EHOSTUNREACH) {
-            return "EHOSTUNREACH";
-        }
-        if (errno == EIDRM) {
-            return "EIDRM";
-        }
-        if (errno == EILSEQ) {
-            return "EILSEQ";
-        }
-        if (errno == EINPROGRESS) {
-            return "EINPROGRESS";
-        }
-        if (errno == EINTR) {
-            return "EINTR";
-        }
-        if (errno == EINVAL) {
-            return "EINVAL";
-        }
-        if (errno == EIO) {
-            return "EIO";
-        }
-        if (errno == EISCONN) {
-            return "EISCONN";
-        }
-        if (errno == EISDIR) {
-            return "EISDIR";
-        }
-        if (errno == ELOOP) {
-            return "ELOOP";
-        }
-        if (errno == EMFILE) {
-            return "EMFILE";
-        }
-        if (errno == EMLINK) {
-            return "EMLINK";
-        }
-        if (errno == EMSGSIZE) {
-            return "EMSGSIZE";
-        }
-        if (errno == EMULTIHOP) {
-            return "EMULTIHOP";
-        }
-        if (errno == ENAMETOOLONG) {
-            return "ENAMETOOLONG";
-        }
-        if (errno == ENETDOWN) {
-            return "ENETDOWN";
-        }
-        if (errno == ENETRESET) {
-            return "ENETRESET";
-        }
-        if (errno == ENETUNREACH) {
-            return "ENETUNREACH";
-        }
-        if (errno == ENFILE) {
-            return "ENFILE";
-        }
-        if (errno == ENOBUFS) {
-            return "ENOBUFS";
-        }
-        if (errno == ENODATA) {
-            return "ENODATA";
-        }
-        if (errno == ENODEV) {
-            return "ENODEV";
-        }
-        if (errno == ENOENT) {
-            return "ENOENT";
-        }
-        if (errno == ENOEXEC) {
-            return "ENOEXEC";
-        }
-        if (errno == ENOLCK) {
-            return "ENOLCK";
-        }
-        if (errno == ENOLINK) {
-            return "ENOLINK";
-        }
-        if (errno == ENOMEM) {
-            return "ENOMEM";
-        }
-        if (errno == ENOMSG) {
-            return "ENOMSG";
-        }
-        if (errno == ENOPROTOOPT) {
-            return "ENOPROTOOPT";
-        }
-        if (errno == ENOSPC) {
-            return "ENOSPC";
-        }
-        if (errno == ENOSR) {
-            return "ENOSR";
-        }
-        if (errno == ENOSTR) {
-            return "ENOSTR";
-        }
-        if (errno == ENOSYS) {
-            return "ENOSYS";
-        }
-        if (errno == ENOTCONN) {
-            return "ENOTCONN";
-        }
-        if (errno == ENOTDIR) {
-            return "ENOTDIR";
-        }
-        if (errno == ENOTEMPTY) {
-            return "ENOTEMPTY";
-        }
-        if (errno == ENOTSOCK) {
-            return "ENOTSOCK";
-        }
-        if (errno == ENOTSUP) {
-            return "ENOTSUP";
-        }
-        if (errno == ENOTTY) {
-            return "ENOTTY";
-        }
-        if (errno == ENXIO) {
-            return "ENXIO";
-        }
-        if (errno == EOPNOTSUPP) {
-            return "EOPNOTSUPP";
-        }
-        if (errno == EOVERFLOW) {
-            return "EOVERFLOW";
-        }
-        if (errno == EPERM) {
-            return "EPERM";
-        }
-        if (errno == EPIPE) {
-            return "EPIPE";
-        }
-        if (errno == EPROTO) {
-            return "EPROTO";
-        }
-        if (errno == EPROTONOSUPPORT) {
-            return "EPROTONOSUPPORT";
-        }
-        if (errno == EPROTOTYPE) {
-            return "EPROTOTYPE";
-        }
-        if (errno == ERANGE) {
-            return "ERANGE";
-        }
-        if (errno == EROFS) {
-            return "EROFS";
-        }
-        if (errno == ESPIPE) {
-            return "ESPIPE";
-        }
-        if (errno == ESRCH) {
-            return "ESRCH";
-        }
-        if (errno == ESTALE) {
-            return "ESTALE";
-        }
-        if (errno == ETIME) {
-            return "ETIME";
-        }
-        if (errno == ETIMEDOUT) {
-            return "ETIMEDOUT";
-        }
-        if (errno == ETXTBSY) {
-            return "ETXTBSY";
-        }
-        if (errno == EWOULDBLOCK) {
-            return "EWOULDBLOCK";
-        }
-        if (errno == EXDEV) {
-            return "EXDEV";
-        }
-        return null;
-    }
-
-    private static native void initConstants();
-
-    // A hack to avoid these constants being inlined by javac...
-    private static int placeholder() { return 0; }
-    // ...because we want to initialize them at runtime.
-    static {
-        initConstants();
-    }
-}
diff --git a/src/main/java/libcore/io/SizeOf.java b/src/main/java/libcore/io/SizeOf.java
deleted file mode 100644
index 728fbfc..0000000
--- a/src/main/java/libcore/io/SizeOf.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-public final class SizeOf {
-    public static final int CHAR = 2;
-    public static final int DOUBLE = 8;
-    public static final int FLOAT = 4;
-    public static final int INT = 4;
-    public static final int LONG = 8;
-    public static final int SHORT = 2;
-
-    private SizeOf() {
-    }
-}
diff --git a/src/main/java/libcore/io/Streams.java b/src/main/java/libcore/io/Streams.java
deleted file mode 100644
index 1ad2356..0000000
--- a/src/main/java/libcore/io/Streams.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.StringWriter;
-import java.util.concurrent.atomic.AtomicReference;
-import libcore.util.Libcore;
-
-public final class Streams {
-    private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
-
-    private Streams() {
-    }
-
-    /**
-     * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int).
-     * InputStream assumes that you implement InputStream.read(int) and provides default
-     * implementations of the others, but often the opposite is more efficient.
-     */
-    public static int readSingleByte(InputStream in) throws IOException {
-        byte[] buffer = new byte[1];
-        int result = in.read(buffer, 0, 1);
-        return (result != -1) ? buffer[0] & 0xff : -1;
-    }
-
-    /**
-     * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int).
-     * OutputStream assumes that you implement OutputStream.write(int) and provides default
-     * implementations of the others, but often the opposite is more efficient.
-     */
-    public static void writeSingleByte(OutputStream out, int b) throws IOException {
-        byte[] buffer = new byte[1];
-        buffer[0] = (byte) (b & 0xff);
-        out.write(buffer);
-    }
-
-    /**
-     * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available.
-     */
-    public static void readFully(InputStream in, byte[] dst) throws IOException {
-        readFully(in, dst, 0, dst.length);
-    }
-
-    /**
-     * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws
-     * EOFException if insufficient bytes are available.
-     *
-     * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}.
-     */
-    public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) throws IOException {
-        if (byteCount == 0) {
-            return;
-        }
-        if (in == null) {
-            throw new NullPointerException("in == null");
-        }
-        if (dst == null) {
-            throw new NullPointerException("dst == null");
-        }
-        Libcore.checkOffsetAndCount(dst.length, offset, byteCount);
-        while (byteCount > 0) {
-            int bytesRead = in.read(dst, offset, byteCount);
-            if (bytesRead < 0) {
-                throw new EOFException();
-            }
-            offset += bytesRead;
-            byteCount -= bytesRead;
-        }
-    }
-
-    /**
-     * Returns a byte[] containing the remainder of 'in', closing it when done.
-     */
-    public static byte[] readFully(InputStream in) throws IOException {
-        try {
-            return readFullyNoClose(in);
-        } finally {
-            in.close();
-        }
-    }
-
-    /**
-     * Returns a byte[] containing the remainder of 'in'.
-     */
-    public static byte[] readFullyNoClose(InputStream in) throws IOException {
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int count;
-        while ((count = in.read(buffer)) != -1) {
-            bytes.write(buffer, 0, count);
-        }
-        return bytes.toByteArray();
-    }
-
-    /**
-     * Returns the remainder of 'reader' as a string, closing it when done.
-     */
-    public static String readFully(Reader reader) throws IOException {
-        try {
-            StringWriter writer = new StringWriter();
-            char[] buffer = new char[1024];
-            int count;
-            while ((count = reader.read(buffer)) != -1) {
-                writer.write(buffer, 0, count);
-            }
-            return writer.toString();
-        } finally {
-            reader.close();
-        }
-    }
-
-    public static void skipAll(InputStream in) throws IOException {
-        do {
-            in.skip(Long.MAX_VALUE);
-        } while (in.read() != -1);
-    }
-
-    /**
-     * Call {@code in.read()} repeatedly until either the stream is exhausted or
-     * {@code byteCount} bytes have been read.
-     *
-     * <p>This method reuses the skip buffer but is careful to never use it at
-     * the same time that another stream is using it. Otherwise streams that use
-     * the caller's buffer for consistency checks like CRC could be clobbered by
-     * other threads. A thread-local buffer is also insufficient because some
-     * streams may call other streams in their skip() method, also clobbering the
-     * buffer.
-     */
-    public static long skipByReading(InputStream in, long byteCount) throws IOException {
-        // acquire the shared skip buffer.
-        byte[] buffer = skipBuffer.getAndSet(null);
-        if (buffer == null) {
-            buffer = new byte[4096];
-        }
-
-        long skipped = 0;
-        while (skipped < byteCount) {
-            int toRead = (int) Math.min(byteCount - skipped, buffer.length);
-            int read = in.read(buffer, 0, toRead);
-            if (read == -1) {
-                break;
-            }
-            skipped += read;
-            if (read < toRead) {
-                break;
-            }
-        }
-
-        // release the shared skip buffer.
-        skipBuffer.set(buffer);
-
-        return skipped;
-    }
-
-    /**
-     * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
-     * Returns the total number of bytes transferred.
-     */
-    public static int copy(InputStream in, OutputStream out) throws IOException {
-        int total = 0;
-        byte[] buffer = new byte[8192];
-        int c;
-        while ((c = in.read(buffer)) != -1) {
-            total += c;
-            out.write(buffer, 0, c);
-        }
-        return total;
-    }
-
-    /**
-     * Returns the ASCII characters up to but not including the next "\r\n", or
-     * "\n".
-     *
-     * @throws java.io.EOFException if the stream is exhausted before the next newline
-     *     character.
-     */
-    public static String readAsciiLine(InputStream in) throws IOException {
-        // TODO: support UTF-8 here instead
-
-        StringBuilder result = new StringBuilder(80);
-        while (true) {
-            int c = in.read();
-            if (c == -1) {
-                throw new EOFException();
-            } else if (c == '\n') {
-                break;
-            }
-
-            result.append((char) c);
-        }
-        int length = result.length();
-        if (length > 0 && result.charAt(length - 1) == '\r') {
-            result.setLength(length - 1);
-        }
-        return result.toString();
-    }
-}
diff --git a/src/main/java/libcore/net/MimeUtils.java b/src/main/java/libcore/net/MimeUtils.java
deleted file mode 100644
index 76193ff..0000000
--- a/src/main/java/libcore/net/MimeUtils.java
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Utilities for dealing with MIME types.
- * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap.
- */
-public final class MimeUtils {
-    private static final Map<String, String> MIME_TYPE_TO_EXTENSION_MAP = new HashMap<String, String>();
-
-    private static final Map<String, String> EXTENSION_TO_MIME_TYPE_MAP = new HashMap<String, String>();
-
-    static {
-        // The following table is based on /etc/mime.types data minus
-        // chemical/* MIME types and MIME types that don't map to any
-        // file extensions. We also exclude top-level domain names to
-        // deal with cases like:
-        //
-        // mail.google.com/a/google.com
-        //
-        // and "active" MIME types (due to potential security issues).
-
-        add("application/andrew-inset", "ez");
-        add("application/dsptype", "tsp");
-        add("application/futuresplash", "spl");
-        add("application/hta", "hta");
-        add("application/mac-binhex40", "hqx");
-        add("application/mac-compactpro", "cpt");
-        add("application/mathematica", "nb");
-        add("application/msaccess", "mdb");
-        add("application/oda", "oda");
-        add("application/ogg", "ogg");
-        add("application/pdf", "pdf");
-        add("application/pgp-keys", "key");
-        add("application/pgp-signature", "pgp");
-        add("application/pics-rules", "prf");
-        add("application/rar", "rar");
-        add("application/rdf+xml", "rdf");
-        add("application/rss+xml", "rss");
-        add("application/zip", "zip");
-        add("application/vnd.android.package-archive", "apk");
-        add("application/vnd.cinderella", "cdy");
-        add("application/vnd.ms-pki.stl", "stl");
-        add("application/vnd.oasis.opendocument.database", "odb");
-        add("application/vnd.oasis.opendocument.formula", "odf");
-        add("application/vnd.oasis.opendocument.graphics", "odg");
-        add("application/vnd.oasis.opendocument.graphics-template", "otg");
-        add("application/vnd.oasis.opendocument.image", "odi");
-        add("application/vnd.oasis.opendocument.spreadsheet", "ods");
-        add("application/vnd.oasis.opendocument.spreadsheet-template", "ots");
-        add("application/vnd.oasis.opendocument.text", "odt");
-        add("application/vnd.oasis.opendocument.text-master", "odm");
-        add("application/vnd.oasis.opendocument.text-template", "ott");
-        add("application/vnd.oasis.opendocument.text-web", "oth");
-        add("application/vnd.google-earth.kml+xml", "kml");
-        add("application/vnd.google-earth.kmz", "kmz");
-        add("application/msword", "doc");
-        add("application/msword", "dot");
-        add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx");
-        add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotx");
-        add("application/vnd.ms-excel", "xls");
-        add("application/vnd.ms-excel", "xlt");
-        add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx");
-        add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xltx");
-        add("application/vnd.ms-powerpoint", "ppt");
-        add("application/vnd.ms-powerpoint", "pot");
-        add("application/vnd.ms-powerpoint", "pps");
-        add("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx");
-        add("application/vnd.openxmlformats-officedocument.presentationml.template", "potx");
-        add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsx");
-        add("application/vnd.rim.cod", "cod");
-        add("application/vnd.smaf", "mmf");
-        add("application/vnd.stardivision.calc", "sdc");
-        add("application/vnd.stardivision.draw", "sda");
-        add("application/vnd.stardivision.impress", "sdd");
-        add("application/vnd.stardivision.impress", "sdp");
-        add("application/vnd.stardivision.math", "smf");
-        add("application/vnd.stardivision.writer", "sdw");
-        add("application/vnd.stardivision.writer", "vor");
-        add("application/vnd.stardivision.writer-global", "sgl");
-        add("application/vnd.sun.xml.calc", "sxc");
-        add("application/vnd.sun.xml.calc.template", "stc");
-        add("application/vnd.sun.xml.draw", "sxd");
-        add("application/vnd.sun.xml.draw.template", "std");
-        add("application/vnd.sun.xml.impress", "sxi");
-        add("application/vnd.sun.xml.impress.template", "sti");
-        add("application/vnd.sun.xml.math", "sxm");
-        add("application/vnd.sun.xml.writer", "sxw");
-        add("application/vnd.sun.xml.writer.global", "sxg");
-        add("application/vnd.sun.xml.writer.template", "stw");
-        add("application/vnd.visio", "vsd");
-        add("application/x-abiword", "abw");
-        add("application/x-apple-diskimage", "dmg");
-        add("application/x-bcpio", "bcpio");
-        add("application/x-bittorrent", "torrent");
-        add("application/x-cdf", "cdf");
-        add("application/x-cdlink", "vcd");
-        add("application/x-chess-pgn", "pgn");
-        add("application/x-cpio", "cpio");
-        add("application/x-debian-package", "deb");
-        add("application/x-debian-package", "udeb");
-        add("application/x-director", "dcr");
-        add("application/x-director", "dir");
-        add("application/x-director", "dxr");
-        add("application/x-dms", "dms");
-        add("application/x-doom", "wad");
-        add("application/x-dvi", "dvi");
-        add("application/x-flac", "flac");
-        add("application/x-font", "pfa");
-        add("application/x-font", "pfb");
-        add("application/x-font", "gsf");
-        add("application/x-font", "pcf");
-        add("application/x-font", "pcf.Z");
-        add("application/x-freemind", "mm");
-        add("application/x-futuresplash", "spl");
-        add("application/x-gnumeric", "gnumeric");
-        add("application/x-go-sgf", "sgf");
-        add("application/x-graphing-calculator", "gcf");
-        add("application/x-gtar", "gtar");
-        add("application/x-gtar", "tgz");
-        add("application/x-gtar", "taz");
-        add("application/x-hdf", "hdf");
-        add("application/x-ica", "ica");
-        add("application/x-internet-signup", "ins");
-        add("application/x-internet-signup", "isp");
-        add("application/x-iphone", "iii");
-        add("application/x-iso9660-image", "iso");
-        add("application/x-jmol", "jmz");
-        add("application/x-kchart", "chrt");
-        add("application/x-killustrator", "kil");
-        add("application/x-koan", "skp");
-        add("application/x-koan", "skd");
-        add("application/x-koan", "skt");
-        add("application/x-koan", "skm");
-        add("application/x-kpresenter", "kpr");
-        add("application/x-kpresenter", "kpt");
-        add("application/x-kspread", "ksp");
-        add("application/x-kword", "kwd");
-        add("application/x-kword", "kwt");
-        add("application/x-latex", "latex");
-        add("application/x-lha", "lha");
-        add("application/x-lzh", "lzh");
-        add("application/x-lzx", "lzx");
-        add("application/x-maker", "frm");
-        add("application/x-maker", "maker");
-        add("application/x-maker", "frame");
-        add("application/x-maker", "fb");
-        add("application/x-maker", "book");
-        add("application/x-maker", "fbdoc");
-        add("application/x-mif", "mif");
-        add("application/x-ms-wmd", "wmd");
-        add("application/x-ms-wmz", "wmz");
-        add("application/x-msi", "msi");
-        add("application/x-ns-proxy-autoconfig", "pac");
-        add("application/x-nwc", "nwc");
-        add("application/x-object", "o");
-        add("application/x-oz-application", "oza");
-        add("application/x-pkcs12", "p12");
-        add("application/x-pkcs12", "pfx");
-        add("application/x-pkcs7-certreqresp", "p7r");
-        add("application/x-pkcs7-crl", "crl");
-        add("application/x-quicktimeplayer", "qtl");
-        add("application/x-shar", "shar");
-        add("application/x-shockwave-flash", "swf");
-        add("application/x-stuffit", "sit");
-        add("application/x-sv4cpio", "sv4cpio");
-        add("application/x-sv4crc", "sv4crc");
-        add("application/x-tar", "tar");
-        add("application/x-texinfo", "texinfo");
-        add("application/x-texinfo", "texi");
-        add("application/x-troff", "t");
-        add("application/x-troff", "roff");
-        add("application/x-troff-man", "man");
-        add("application/x-ustar", "ustar");
-        add("application/x-wais-source", "src");
-        add("application/x-wingz", "wz");
-        add("application/x-webarchive", "webarchive");
-        add("application/x-webarchive-xml", "webarchivexml");
-        add("application/x-x509-ca-cert", "crt");
-        add("application/x-x509-user-cert", "crt");
-        add("application/x-xcf", "xcf");
-        add("application/x-xfig", "fig");
-        add("application/xhtml+xml", "xhtml");
-        add("audio/3gpp", "3gpp");
-        add("audio/amr", "amr");
-        add("audio/basic", "snd");
-        add("audio/midi", "mid");
-        add("audio/midi", "midi");
-        add("audio/midi", "kar");
-        add("audio/midi", "xmf");
-        add("audio/mobile-xmf", "mxmf");
-        add("audio/mpeg", "mpga");
-        add("audio/mpeg", "mpega");
-        add("audio/mpeg", "mp2");
-        add("audio/mpeg", "mp3");
-        add("audio/mpeg", "m4a");
-        add("audio/mpegurl", "m3u");
-        add("audio/prs.sid", "sid");
-        add("audio/x-aiff", "aif");
-        add("audio/x-aiff", "aiff");
-        add("audio/x-aiff", "aifc");
-        add("audio/x-gsm", "gsm");
-        add("audio/x-mpegurl", "m3u");
-        add("audio/x-ms-wma", "wma");
-        add("audio/x-ms-wax", "wax");
-        add("audio/x-pn-realaudio", "ra");
-        add("audio/x-pn-realaudio", "rm");
-        add("audio/x-pn-realaudio", "ram");
-        add("audio/x-realaudio", "ra");
-        add("audio/x-scpls", "pls");
-        add("audio/x-sd2", "sd2");
-        add("audio/x-wav", "wav");
-        add("image/bmp", "bmp");
-        add("image/gif", "gif");
-        add("image/ico", "cur");
-        add("image/ico", "ico");
-        add("image/ief", "ief");
-        add("image/jpeg", "jpeg");
-        add("image/jpeg", "jpg");
-        add("image/jpeg", "jpe");
-        add("image/pcx", "pcx");
-        add("image/png", "png");
-        add("image/svg+xml", "svg");
-        add("image/svg+xml", "svgz");
-        add("image/tiff", "tiff");
-        add("image/tiff", "tif");
-        add("image/vnd.djvu", "djvu");
-        add("image/vnd.djvu", "djv");
-        add("image/vnd.wap.wbmp", "wbmp");
-        add("image/x-cmu-raster", "ras");
-        add("image/x-coreldraw", "cdr");
-        add("image/x-coreldrawpattern", "pat");
-        add("image/x-coreldrawtemplate", "cdt");
-        add("image/x-corelphotopaint", "cpt");
-        add("image/x-icon", "ico");
-        add("image/x-jg", "art");
-        add("image/x-jng", "jng");
-        add("image/x-ms-bmp", "bmp");
-        add("image/x-photoshop", "psd");
-        add("image/x-portable-anymap", "pnm");
-        add("image/x-portable-bitmap", "pbm");
-        add("image/x-portable-graymap", "pgm");
-        add("image/x-portable-pixmap", "ppm");
-        add("image/x-rgb", "rgb");
-        add("image/x-xbitmap", "xbm");
-        add("image/x-xpixmap", "xpm");
-        add("image/x-xwindowdump", "xwd");
-        add("model/iges", "igs");
-        add("model/iges", "iges");
-        add("model/mesh", "msh");
-        add("model/mesh", "mesh");
-        add("model/mesh", "silo");
-        add("text/calendar", "ics");
-        add("text/calendar", "icz");
-        add("text/comma-separated-values", "csv");
-        add("text/css", "css");
-        add("text/html", "htm");
-        add("text/html", "html");
-        add("text/h323", "323");
-        add("text/iuls", "uls");
-        add("text/mathml", "mml");
-        // add ".txt" first so it will be the default for ExtensionFromMimeType
-        add("text/plain", "txt");
-        add("text/plain", "asc");
-        add("text/plain", "text");
-        add("text/plain", "diff");
-        add("text/plain", "po");     // reserve "pot" for vnd.ms-powerpoint
-        add("text/richtext", "rtx");
-        add("text/rtf", "rtf");
-        add("text/texmacs", "ts");
-        add("text/text", "phps");
-        add("text/tab-separated-values", "tsv");
-        add("text/xml", "xml");
-        add("text/x-bibtex", "bib");
-        add("text/x-boo", "boo");
-        add("text/x-c++hdr", "h++");
-        add("text/x-c++hdr", "hpp");
-        add("text/x-c++hdr", "hxx");
-        add("text/x-c++hdr", "hh");
-        add("text/x-c++src", "c++");
-        add("text/x-c++src", "cpp");
-        add("text/x-c++src", "cxx");
-        add("text/x-chdr", "h");
-        add("text/x-component", "htc");
-        add("text/x-csh", "csh");
-        add("text/x-csrc", "c");
-        add("text/x-dsrc", "d");
-        add("text/x-haskell", "hs");
-        add("text/x-java", "java");
-        add("text/x-literate-haskell", "lhs");
-        add("text/x-moc", "moc");
-        add("text/x-pascal", "p");
-        add("text/x-pascal", "pas");
-        add("text/x-pcs-gcd", "gcd");
-        add("text/x-setext", "etx");
-        add("text/x-tcl", "tcl");
-        add("text/x-tex", "tex");
-        add("text/x-tex", "ltx");
-        add("text/x-tex", "sty");
-        add("text/x-tex", "cls");
-        add("text/x-vcalendar", "vcs");
-        add("text/x-vcard", "vcf");
-        add("video/3gpp", "3gpp");
-        add("video/3gpp", "3gp");
-        add("video/3gpp", "3g2");
-        add("video/dl", "dl");
-        add("video/dv", "dif");
-        add("video/dv", "dv");
-        add("video/fli", "fli");
-        add("video/m4v", "m4v");
-        add("video/mpeg", "mpeg");
-        add("video/mpeg", "mpg");
-        add("video/mpeg", "mpe");
-        add("video/mp4", "mp4");
-        add("video/mpeg", "VOB");
-        add("video/quicktime", "qt");
-        add("video/quicktime", "mov");
-        add("video/vnd.mpegurl", "mxu");
-        add("video/x-la-asf", "lsf");
-        add("video/x-la-asf", "lsx");
-        add("video/x-mng", "mng");
-        add("video/x-ms-asf", "asf");
-        add("video/x-ms-asf", "asx");
-        add("video/x-ms-wm", "wm");
-        add("video/x-ms-wmv", "wmv");
-        add("video/x-ms-wmx", "wmx");
-        add("video/x-ms-wvx", "wvx");
-        add("video/x-msvideo", "avi");
-        add("video/x-sgi-movie", "movie");
-        add("x-conference/x-cooltalk", "ice");
-        add("x-epoc/x-sisx-app", "sisx");
-        applyOverrides();
-    }
-
-    private static void add(String mimeType, String extension) {
-        //
-        // if we have an existing x --> y mapping, we do not want to
-        // override it with another mapping x --> ?
-        // this is mostly because of the way the mime-type map below
-        // is constructed (if a mime type maps to several extensions
-        // the first extension is considered the most popular and is
-        // added first; we do not want to overwrite it later).
-        //
-        if (!MIME_TYPE_TO_EXTENSION_MAP.containsKey(mimeType)) {
-            MIME_TYPE_TO_EXTENSION_MAP.put(mimeType, extension);
-        }
-        EXTENSION_TO_MIME_TYPE_MAP.put(extension, mimeType);
-    }
-
-    private static InputStream getContentTypesPropertiesStream() {
-        // User override?
-        String userTable = System.getProperty("content.types.user.table");
-        if (userTable != null) {
-            File f = new File(userTable);
-            if (f.exists()) {
-                try {
-                    return new FileInputStream(f);
-                } catch (IOException ignored) {
-                }
-            }
-        }
-
-        // Standard location?
-        File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties");
-        if (f.exists()) {
-            try {
-                return new FileInputStream(f);
-            } catch (IOException ignored) {
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your
-     * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins
-     * come from "$JAVA_HOME/lib/content-types.properties".
-     */
-    private static void applyOverrides() {
-        // Get the appropriate InputStream to read overrides from, if any.
-        InputStream stream = getContentTypesPropertiesStream();
-        if (stream == null) {
-            return;
-        }
-
-        try {
-            try {
-                // Read the properties file...
-                Properties overrides = new Properties();
-                overrides.load(stream);
-                // And translate its mapping to ours...
-                for (Map.Entry<Object, Object> entry : overrides.entrySet()) {
-                    String extension = (String) entry.getKey();
-                    String mimeType = (String) entry.getValue();
-                    add(mimeType, extension);
-                }
-            } finally {
-                stream.close();
-            }
-        } catch (IOException ignored) {
-        }
-    }
-
-    private MimeUtils() {
-    }
-
-    /**
-     * Returns true if the given MIME type has an entry in the map.
-     * @param mimeType A MIME type (i.e. text/plain)
-     * @return True iff there is a mimeType entry in the map.
-     */
-    public static boolean hasMimeType(String mimeType) {
-        if (mimeType == null || mimeType.length() == 0) {
-            return false;
-        }
-        return MIME_TYPE_TO_EXTENSION_MAP.containsKey(mimeType);
-    }
-
-    /**
-     * Returns the MIME type for the given extension.
-     * @param extension A file extension without the leading '.'
-     * @return The MIME type for the given extension or null iff there is none.
-     */
-    public static String guessMimeTypeFromExtension(String extension) {
-        if (extension == null || extension.length() == 0) {
-            return null;
-        }
-        return EXTENSION_TO_MIME_TYPE_MAP.get(extension);
-    }
-
-    /**
-     * Returns true if the given extension has a registered MIME type.
-     * @param extension A file extension without the leading '.'
-     * @return True iff there is an extension entry in the map.
-     */
-    public static boolean hasExtension(String extension) {
-        if (extension == null || extension.length() == 0) {
-            return false;
-        }
-        return EXTENSION_TO_MIME_TYPE_MAP.containsKey(extension);
-    }
-
-    /**
-     * Returns the registered extension for the given MIME type. Note that some
-     * MIME types map to multiple extensions. This call will return the most
-     * common extension for the given MIME type.
-     * @param mimeType A MIME type (i.e. text/plain)
-     * @return The extension for the given MIME type or null iff there is none.
-     */
-    public static String guessExtensionFromMimeType(String mimeType) {
-        if (mimeType == null || mimeType.length() == 0) {
-            return null;
-        }
-        return MIME_TYPE_TO_EXTENSION_MAP.get(mimeType);
-    }
-}
diff --git a/src/main/java/libcore/net/http/Challenge.java b/src/main/java/libcore/net/http/Challenge.java
deleted file mode 100644
index d373c0a..0000000
--- a/src/main/java/libcore/net/http/Challenge.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net.http;
-
-/**
- * An RFC 2617 challenge.
- */
-final class Challenge {
-    final String scheme;
-    final String realm;
-
-    Challenge(String scheme, String realm) {
-        this.scheme = scheme;
-        this.realm = realm;
-    }
-
-    @Override public boolean equals(Object o) {
-        return o instanceof Challenge
-                && ((Challenge) o).scheme.equals(scheme)
-                && ((Challenge) o).realm.equals(realm);
-    }
-
-    @Override public int hashCode() {
-        return scheme.hashCode() + 31 * realm.hashCode();
-    }
-}
diff --git a/src/main/java/libcore/net/http/HttpConnection.java b/src/main/java/libcore/net/http/HttpConnection.java
deleted file mode 100644
index efdcf3d..0000000
--- a/src/main/java/libcore/net/http/HttpConnection.java
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package libcore.net.http;
-
-import static com.squareup.okhttp.OkHttpConnection.HTTP_PROXY_AUTH;
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import static java.net.HttpURLConnection.HTTP_OK;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.security.cert.CertificateException;
-import java.util.Arrays;
-import java.util.List;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import libcore.io.IoUtils;
-import libcore.net.spdy.SpdyConnection;
-import libcore.util.Libcore;
-import libcore.util.Objects;
-
-/**
- * Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection,
- * which may be used for multiple HTTP request/response exchanges. Connections
- * may be direct to the origin server or via a proxy. Create an instance using
- * the {@link Address} inner class.
- *
- * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
- * which isn't so much a connection as a single request/response pair.
- */
-final class HttpConnection {
-    private static final byte[] NPN_PROTOCOLS = new byte[] {
-            6, 's', 'p', 'd', 'y', '/', '2',
-            8, 'h', 't', 't', 'p', '/', '1', '.', '1',
-    };
-    private static final byte[] SPDY2 = new byte[] {
-            's', 'p', 'd', 'y', '/', '2',
-    };
-    private static final byte[] HTTP_11 = new byte[] {
-            'h', 't', 't', 'p', '/', '1', '.', '1',
-    };
-
-    private final Address address;
-    private Socket socket;
-    private InputStream in;
-    private OutputStream out;
-    private boolean recycled = false;
-    private SpdyConnection spdyConnection;
-
-    HttpConnection(Address address, Socket socket, InputStream in, OutputStream out) {
-        this.address = address;
-        this.socket = socket;
-        this.in = in;
-        this.out = out;
-    }
-
-    /**
-     * The version this client will use. Either 0 for HTTP/1.0, or 1 for
-     * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client
-     * automatically sets its version to HTTP/1.0.
-     */
-    int httpMinorVersion = 1; // Assume HTTP/1.1
-
-    public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory,
-            HostnameVerifier hostnameVerifier, Proxy proxy, int connectTimeout, int readTimeout,
-            TunnelConfig tunnelConfig) throws IOException {
-        HttpConnection result = getConnection(uri, sslSocketFactory, hostnameVerifier, proxy,
-                connectTimeout, tunnelConfig);
-        result.socket.setSoTimeout(readTimeout);
-        return result;
-    }
-
-    /**
-     * Selects a proxy and gets a connection with that proxy.
-     */
-    private static HttpConnection getConnection(URI uri, SSLSocketFactory sslSocketFactory,
-            HostnameVerifier hostnameVerifier, Proxy proxy, int connectTimeout,
-            TunnelConfig tunnelConfig) throws IOException {
-        // Try an explicitly-specified proxy.
-        if (proxy != null) {
-            Address address = (proxy.type() == Proxy.Type.DIRECT)
-                    ? new Address(uri, sslSocketFactory, hostnameVerifier)
-                    : new Address(uri, sslSocketFactory, hostnameVerifier, proxy);
-            return getConnectionToAddress(address, connectTimeout, tunnelConfig);
-        }
-
-        // Try each proxy provided by the ProxySelector until a connection succeeds.
-        ProxySelector selector = ProxySelector.getDefault();
-        List<Proxy> proxyList = selector.select(uri);
-        if (proxyList != null) {
-            for (Proxy selectedProxy : proxyList) {
-                if (selectedProxy.type() == Proxy.Type.DIRECT) {
-                    // the same as NO_PROXY
-                    // TODO: if the selector recommends a direct connection, attempt that?
-                    continue;
-                }
-                try {
-                    return getConnectionToAddress(new Address(uri, sslSocketFactory,
-                            hostnameVerifier, selectedProxy), connectTimeout, tunnelConfig);
-                } catch (IOException e) {
-                    // failed to connect, tell it to the selector
-                    selector.connectFailed(uri, selectedProxy.address(), e);
-                }
-            }
-        }
-
-        // Try a direct connection. If this fails, this method will throw.
-        return getConnectionToAddress(new Address(uri, sslSocketFactory, hostnameVerifier),
-                connectTimeout, tunnelConfig);
-    }
-
-    /**
-     * Selects a proxy and gets a connection with that proxy.
-     */
-    private static HttpConnection getConnectionToAddress(Address address, int connectTimeout,
-            TunnelConfig tunnelConfig) throws IOException {
-        HttpConnection pooled = HttpConnectionPool.INSTANCE.get(address);
-        if (pooled != null) {
-            return pooled;
-        }
-
-        Socket socket = connectSocket(address, connectTimeout);
-        HttpConnection result = new HttpConnection(
-                address, socket, socket.getInputStream(), socket.getOutputStream());
-
-        if (address.sslSocketFactory != null) {
-            // First try an SSL connection with compression and various TLS
-            // extensions enabled, if it fails (and its not unheard of that it
-            // will) fallback to a barebones connection.
-            try {
-                result.upgradeToTls(true, tunnelConfig);
-            } catch (IOException e) {
-                // If the problem was a CertificateException from the X509TrustManager,
-                // do not retry, we didn't have an abrupt server initiated exception.
-                if (e instanceof SSLHandshakeException
-                        && e.getCause() instanceof CertificateException) {
-                    throw e;
-                }
-                result.closeSocketAndStreams();
-
-                socket = connectSocket(address, connectTimeout);
-                result = new HttpConnection(
-                        address, socket, socket.getInputStream(), socket.getOutputStream());
-                result.upgradeToTls(false, tunnelConfig);
-            }
-        }
-
-        /*
-         * Buffer the socket stream to permit efficient parsing of HTTP headers
-         * and chunk sizes. This also masks SSL InputStream's degenerate
-         * available() implementation. That way we can read the end of a chunked
-         * response without blocking and will recycle connections more reliably.
-         * http://code.google.com/p/android/issues/detail?id=38817
-         */
-        int bufferSize = 128;
-        result.in = new BufferedInputStream(result.in, bufferSize);
-
-        return result;
-    }
-
-    /**
-     * Try each of the host's addresses for best behavior in mixed IPv4/IPv6
-     * environments. See http://b/2876927
-     */
-    private static Socket connectSocket(Address address, int connectTimeout) throws IOException {
-        Socket socket = null;
-        InetAddress[] addresses = InetAddress.getAllByName(address.socketHost);
-        for (int i = 0; i < addresses.length; i++) {
-            socket = (address.proxy != null && address.proxy.type() != Proxy.Type.HTTP)
-                    ? new Socket(address.proxy)
-                    : new Socket();
-            try {
-                socket.connect(
-                        new InetSocketAddress(addresses[i], address.socketPort), connectTimeout);
-                break;
-            } catch (IOException e) {
-                if (i == addresses.length - 1) {
-                    throw e;
-                }
-            }
-        }
-
-        if (socket == null) {
-            throw new IOException();
-        }
-
-        return socket;
-    }
-
-    /**
-     * Create an {@code SSLSocket} and perform the TLS handshake and certificate
-     * validation.
-     *
-     * @param tlsTolerant If true, assume server can handle common TLS
-     *     extensions and SSL deflate compression. If false, use an SSL3 only
-     *     fallback mode without compression.
-     */
-    private void upgradeToTls(boolean tlsTolerant, TunnelConfig tunnelConfig) throws IOException {
-        // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
-        if (address.requiresTunnel()) {
-            makeTunnel(tunnelConfig);
-        }
-
-        // Create the wrapper over connected socket.
-        socket = address.sslSocketFactory.createSocket(
-                socket, address.uriHost, address.uriPort, true /* autoClose */);
-        SSLSocket sslSocket = (SSLSocket) socket;
-        Libcore.makeTlsTolerant(sslSocket, address.uriHost, tlsTolerant);
-
-        if (tlsTolerant) {
-            Libcore.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
-        }
-
-        // Force handshake. This can throw!
-        sslSocket.startHandshake();
-
-        // Verify that the socket's certificates are acceptable for the target host.
-        if (!address.hostnameVerifier.verify(address.uriHost, sslSocket.getSession())) {
-            throw new IOException("Hostname '" + address.uriHost + "' was not verified");
-        }
-
-        out = sslSocket.getOutputStream();
-        in = sslSocket.getInputStream();
-
-        byte[] selectedProtocol;
-        if (tlsTolerant
-                && (selectedProtocol = Libcore.getNpnSelectedProtocol(sslSocket)) != null) {
-            if (Arrays.equals(selectedProtocol, SPDY2)) {
-                spdyConnection = new SpdyConnection.Builder(true, in, out).build();
-                HttpConnectionPool.INSTANCE.share(this);
-            } else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
-                throw new IOException("Unexpected NPN transport "
-                        + new String(selectedProtocol, "ISO-8859-1"));
-            }
-        }
-    }
-
-    public void closeSocketAndStreams() {
-        IoUtils.closeQuietly(out);
-        IoUtils.closeQuietly(in);
-        IoUtils.closeQuietly(socket);
-    }
-
-    public Socket getSocket() {
-        return socket;
-    }
-
-    public Address getAddress() {
-        return address;
-    }
-
-    /**
-     * Returns true if this connection has been used to satisfy an earlier
-     * HTTP request/response pair.
-     */
-    public boolean isRecycled() {
-        return recycled;
-    }
-
-    public void setRecycled() {
-        this.recycled = true;
-    }
-
-    /**
-     * Returns true if this connection is eligible to be reused for another
-     * request/response pair.
-     */
-    protected boolean isEligibleForRecycling() {
-        return !socket.isClosed()
-                && !socket.isInputShutdown()
-                && !socket.isOutputShutdown();
-    }
-
-    /**
-     * Returns the transport appropriate for this connection.
-     */
-    public Transport newTransport(HttpEngine httpEngine) throws IOException {
-        if (spdyConnection != null) {
-            return new SpdyTransport(httpEngine, spdyConnection);
-        } else {
-            return new HttpTransport(httpEngine, out, in);
-        }
-    }
-
-    /**
-     * Returns true if this is a SPDY connection. Such connections can be used
-     * in multiple HTTP requests simultaneously.
-     */
-    public boolean isSpdy() {
-        return spdyConnection != null;
-    }
-
-    public static final class TunnelConfig {
-        private final URL url;
-        private final String host;
-        private final String userAgent;
-        private final String proxyAuthorization;
-
-        public TunnelConfig(URL url, String host, String userAgent, String proxyAuthorization) {
-            if (url == null || host == null || userAgent == null) throw new NullPointerException();
-            this.url = url;
-            this.host = host;
-            this.userAgent = userAgent;
-            this.proxyAuthorization = proxyAuthorization;
-        }
-
-        /**
-         * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), send
-         * only the minimum set of headers. This avoids sending potentially
-         * sensitive data like HTTP cookies to the proxy unencrypted.
-         */
-        RawHeaders getRequestHeaders() {
-            RawHeaders result = new RawHeaders();
-            result.setRequestLine("CONNECT " + url.getHost() + ":"
-                    + Libcore.getEffectivePort(url) + " HTTP/1.1");
-
-            // Always set Host and User-Agent.
-            result.set("Host", host);
-            result.set("User-Agent", userAgent);
-
-            // Copy over the Proxy-Authorization header if it exists.
-            if (proxyAuthorization != null) {
-                result.set("Proxy-Authorization", proxyAuthorization);
-            }
-
-            // Always set the Proxy-Connection to Keep-Alive for the benefit of
-            // HTTP/1.0 proxies like Squid.
-            result.set("Proxy-Connection", "Keep-Alive");
-            return result;
-        }
-    }
-
-    /**
-     * To make an HTTPS connection over an HTTP proxy, send an unencrypted
-     * CONNECT request to create the proxy connection. This may need to be
-     * retried if the proxy requires authorization.
-     */
-    private void makeTunnel(TunnelConfig tunnelConfig) throws IOException {
-        RawHeaders requestHeaders = tunnelConfig.getRequestHeaders();
-        while (true) {
-            out.write(requestHeaders.toBytes());
-            RawHeaders responseHeaders = RawHeaders.fromBytes(in);
-
-            switch (responseHeaders.getResponseCode()) {
-            case HTTP_OK:
-                return;
-            case HTTP_PROXY_AUTH:
-                requestHeaders = new RawHeaders(requestHeaders);
-                boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH,
-                        responseHeaders, requestHeaders, address.proxy, tunnelConfig.url);
-                if (credentialsFound) {
-                    continue;
-                } else {
-                    throw new IOException("Failed to authenticate with proxy");
-                }
-            default:
-                throw new IOException("Unexpected response code for CONNECT: "
-                        + responseHeaders.getResponseCode());
-            }
-        }
-    }
-
-    /**
-     * This address has two parts: the address we connect to directly and the
-     * origin address of the resource. These are the same unless a proxy is
-     * being used. It also includes the SSL socket factory so that a socket will
-     * not be reused if its SSL configuration is different.
-     */
-    public static final class Address {
-        private final Proxy proxy;
-        private final String uriHost;
-        private final int uriPort;
-        private final String socketHost;
-        private final int socketPort;
-        private final SSLSocketFactory sslSocketFactory;
-        private final HostnameVerifier hostnameVerifier;
-
-        public Address(URI uri, SSLSocketFactory sslSocketFactory,
-                HostnameVerifier hostnameVerifier) throws UnknownHostException {
-            this.proxy = null;
-            this.uriHost = uri.getHost();
-            this.uriPort = Libcore.getEffectivePort(uri);
-            this.sslSocketFactory = sslSocketFactory;
-            this.hostnameVerifier = hostnameVerifier;
-            this.socketHost = uriHost;
-            this.socketPort = uriPort;
-            if (uriHost == null) {
-                throw new UnknownHostException(uri.toString());
-            }
-        }
-
-        public Address(URI uri, SSLSocketFactory sslSocketFactory,
-                HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException {
-            this.proxy = proxy;
-            this.uriHost = uri.getHost();
-            this.uriPort = Libcore.getEffectivePort(uri);
-            this.sslSocketFactory = sslSocketFactory;
-            this.hostnameVerifier = hostnameVerifier;
-
-            SocketAddress proxyAddress = proxy.address();
-            if (!(proxyAddress instanceof InetSocketAddress)) {
-                throw new IllegalArgumentException("Proxy.address() is not an InetSocketAddress: "
-                        + proxyAddress.getClass());
-            }
-            InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
-            this.socketHost = proxySocketAddress.getHostName();
-            this.socketPort = proxySocketAddress.getPort();
-            if (uriHost == null) {
-                throw new UnknownHostException(uri.toString());
-            }
-        }
-
-        public Proxy getProxy() {
-            return proxy;
-        }
-
-        @Override public boolean equals(Object other) {
-            if (other instanceof Address) {
-                Address that = (Address) other;
-                return Objects.equal(this.proxy, that.proxy)
-                        && this.uriHost.equals(that.uriHost)
-                        && this.uriPort == that.uriPort
-                        && Objects.equal(this.sslSocketFactory, that.sslSocketFactory)
-                        && Objects.equal(this.hostnameVerifier, that.hostnameVerifier);
-            }
-            return false;
-        }
-
-        @Override public int hashCode() {
-            int result = 17;
-            result = 31 * result + uriHost.hashCode();
-            result = 31 * result + uriPort;
-            result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
-            result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
-            result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
-            return result;
-        }
-
-        /**
-         * Returns true if the HTTP connection needs to tunnel one protocol over
-         * another, such as when using HTTPS through an HTTP proxy. When doing so,
-         * we must avoid buffering bytes intended for the higher-level protocol.
-         */
-        public boolean requiresTunnel() {
-            return sslSocketFactory != null && proxy != null && proxy.type() == Proxy.Type.HTTP;
-        }
-    }
-}
diff --git a/src/main/java/libcore/net/http/HttpConnectionPool.java b/src/main/java/libcore/net/http/HttpConnectionPool.java
deleted file mode 100644
index 0c2a188..0000000
--- a/src/main/java/libcore/net/http/HttpConnectionPool.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package libcore.net.http;
-
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import libcore.util.Libcore;
-
-/**
- * A pool of HTTP and SPDY connections. This class exposes its tuning parameters
- * as system properties:
- * <ul>
- *   <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
- *       pooled at all. Default is true.
- *   <li>{@code http.maxConnections} maximum number of connections to each host.
- *       Default is 5.
- * </ul>
- *
- * <p>This class <i>doesn't</i> adjust its configuration as system properties
- * are changed. This assumes that the applications that set these parameters do
- * so before making HTTP connections, and that this class is initialized lazily.
- */
-final class HttpConnectionPool {
-    public static final HttpConnectionPool INSTANCE = new HttpConnectionPool();
-
-    private final int maxConnections;
-    private final HashMap<HttpConnection.Address, List<HttpConnection>> connectionPool
-            = new HashMap<HttpConnection.Address, List<HttpConnection>>();
-
-    private HttpConnectionPool() {
-        String keepAlive = System.getProperty("http.keepAlive");
-        if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
-            maxConnections = 0;
-            return;
-        }
-
-        String maxConnectionsString = System.getProperty("http.maxConnections");
-        this.maxConnections = maxConnectionsString != null
-                ? Integer.parseInt(maxConnectionsString)
-                : 5;
-    }
-
-    public HttpConnection get(HttpConnection.Address address) {
-        // First try to reuse an existing HTTP connection.
-        synchronized (connectionPool) {
-            List<HttpConnection> connections = connectionPool.get(address);
-            while (connections != null) {
-                HttpConnection connection = connections.get(connections.size() - 1);
-                if (!connection.isSpdy()) {
-                    connections.remove(connections.size() - 1);
-                }
-                if (connections.isEmpty()) {
-                    connectionPool.remove(address);
-                    connections = null;
-                }
-                if (connection.isEligibleForRecycling()) {
-                    // Since Socket is recycled, re-tag before using
-                    Socket socket = connection.getSocket();
-                    Libcore.tagSocket(socket);
-                    return connection;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gives the HTTP/HTTPS connection to the pool. It is an error to use {@code
-     * connection} after calling this method.
-     */
-    public void recycle(HttpConnection connection) {
-        if (connection.isSpdy()) {
-            throw new IllegalArgumentException(); // TODO: just 'return' here?
-        }
-
-        Socket socket = connection.getSocket();
-        try {
-            Libcore.untagSocket(socket);
-        } catch (SocketException e) {
-            // When unable to remove tagging, skip recycling and close
-            Libcore.logW("Unable to untagSocket(): " + e);
-            connection.closeSocketAndStreams();
-            return;
-        }
-
-        if (maxConnections > 0 && connection.isEligibleForRecycling()) {
-            HttpConnection.Address address = connection.getAddress();
-            synchronized (connectionPool) {
-                List<HttpConnection> connections = connectionPool.get(address);
-                if (connections == null) {
-                    connections = new ArrayList<HttpConnection>();
-                    connectionPool.put(address, connections);
-                }
-                if (connections.size() < maxConnections) {
-                    connection.setRecycled();
-                    connections.add(connection);
-                    return; // keep the connection open
-                }
-            }
-        }
-
-        // don't close streams while holding a lock!
-        connection.closeSocketAndStreams();
-    }
-
-    /**
-     * Shares the SPDY connection with the pool. Callers to this method may
-     * continue to use {@code connection}.
-     */
-    public void share(HttpConnection connection) {
-        if (!connection.isSpdy()) {
-            throw new IllegalArgumentException();
-        }
-        if (maxConnections <= 0 || !connection.isEligibleForRecycling()) {
-            return;
-        }
-        HttpConnection.Address address = connection.getAddress();
-        synchronized (connectionPool) {
-            List<HttpConnection> connections = connectionPool.get(address);
-            if (connections == null) {
-                connections = new ArrayList<HttpConnection>(1);
-                connections.add(connection);
-                connectionPool.put(address, connections);
-            }
-        }
-    }
-}
diff --git a/src/main/java/libcore/net/spdy/SpdyServer.java b/src/main/java/libcore/net/spdy/SpdyServer.java
deleted file mode 100644
index d2ad4ec..0000000
--- a/src/main/java/libcore/net/spdy/SpdyServer.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net.spdy;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A basic SPDY server that serves the contents of a local directory. This
- * server will service a single SPDY connection.
- */
-public final class SpdyServer implements IncomingStreamHandler {
-    private final File baseDirectory;
-
-    public SpdyServer(File baseDirectory) {
-        this.baseDirectory = baseDirectory;
-    }
-
-    private void run() throws Exception {
-        ServerSocket serverSocket = new ServerSocket(8888);
-        serverSocket.setReuseAddress(true);
-
-        Socket socket = serverSocket.accept();
-        SpdyConnection connection = new SpdyConnection.Builder(false, socket)
-                .handler(this)
-                .build();
-
-        // Chrome doesn't seem to like pings coming from the server:
-        // https://groups.google.com/forum/?fromgroups=#!topic/spdy-dev/NgTHYUQKWBY
-        // System.out.println("PING RTT TIME " + connection.ping().roundTripTime());
-    }
-
-    @Override public void receive(final SpdyStream stream) throws IOException {
-        List<String> requestHeaders = stream.getRequestHeaders();
-        String path = null;
-        for (int i = 0; i < requestHeaders.size(); i += 2) {
-            String s = requestHeaders.get(i);
-            if ("url".equals(s)) {
-                path = requestHeaders.get(i + 1);
-                break;
-            }
-        }
-
-        if (path == null) {
-            // TODO: send bad request error
-            throw new AssertionError();
-        }
-
-        File file = new File(baseDirectory + path);
-
-        if (file.exists() && !file.isDirectory()) {
-            serveFile(stream, file);
-        } else {
-            send404(stream, path);
-        }
-    }
-
-    private void send404(SpdyStream stream, String path) throws IOException {
-        List<String> responseHeaders = Arrays.asList(
-                "status", "404",
-                "version", "HTTP/1.1",
-                "content-type", "text/plain"
-        );
-        stream.reply(responseHeaders, true);
-        OutputStream out = stream.getOutputStream();
-        String text = "Not found: " + path;
-        out.write(text.getBytes("UTF-8"));
-        out.close();
-    }
-
-    private void serveFile(SpdyStream stream, File file) throws IOException {
-        InputStream in = new FileInputStream(file);
-        byte[] buffer = new byte[8192];
-        stream.reply(Arrays.asList(
-                "status", "200",
-                "version", "HTTP/1.1",
-                "content-type", contentType(file)
-        ), true);
-        OutputStream out = stream.getOutputStream();
-        int count;
-        while ((count = in.read(buffer)) != -1) {
-            out.write(buffer, 0, count);
-        }
-        out.close();
-    }
-
-    private String contentType(File file) {
-        return file.getName().endsWith(".html") ? "text/html" : "text/plain";
-    }
-
-    public static void main(String... args) throws Exception {
-        if (args.length != 1 || args[0].startsWith("-")) {
-            System.out.println("Usage: SpdyServer <base directory>");
-            return;
-        }
-
-        new SpdyServer(new File(args[0])).run();
-    }
-}
diff --git a/src/main/java/libcore/net/spdy/SpdyStream.java b/src/main/java/libcore/net/spdy/SpdyStream.java
deleted file mode 100644
index 2e336de..0000000
--- a/src/main/java/libcore/net/spdy/SpdyStream.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net.spdy;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.util.List;
-import libcore.io.Streams;
-import libcore.util.Libcore;
-
-import static java.nio.ByteOrder.BIG_ENDIAN;
-
-/**
- * A logical bidirectional stream.
- */
-public final class SpdyStream {
-
-    /*
-     * Internal state is guarded by this. No long-running or potentially
-     * blocking operations are performed while the lock is held.
-     */
-
-    private static final int DATA_FRAME_HEADER_LENGTH = 8;
-
-    public static final int RST_PROTOCOL_ERROR = 1;
-    public static final int RST_INVALID_STREAM = 2;
-    public static final int RST_REFUSED_STREAM = 3;
-    public static final int RST_UNSUPPORTED_VERSION = 4;
-    public static final int RST_CANCEL = 5;
-    public static final int RST_INTERNAL_ERROR = 6;
-    public static final int RST_FLOW_CONTROL_ERROR = 7;
-
-    private final int id;
-    private final SpdyConnection connection;
-
-    /** Headers sent by the stream initiator. Immutable and non null. */
-    private final List<String> requestHeaders;
-
-    /** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */
-    private List<String> responseHeaders;
-
-    private final SpdyDataInputStream in = new SpdyDataInputStream();
-    private final SpdyDataOutputStream out = new SpdyDataOutputStream();
-
-    /**
-     * The reason why this stream was abnormally closed. If there are multiple
-     * reasons to abnormally close this stream (such as both peers closing it
-     * near-simultaneously) then this is the first reason known to this peer.
-     */
-    private int rstStatusCode = -1;
-
-    SpdyStream(int id, SpdyConnection connection, List<String> requestHeaders, int flags) {
-        this.id = id;
-        this.connection = connection;
-        this.requestHeaders = requestHeaders;
-
-        if (isLocallyInitiated()) {
-            // I am the sender
-            in.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
-            out.finished = (flags & SpdyConnection.FLAG_FIN) != 0;
-        } else {
-            // I am the receiver
-            in.finished = (flags & SpdyConnection.FLAG_FIN) != 0;
-            out.finished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
-        }
-    }
-
-    /**
-     * Returns true if this stream was created by this peer.
-     */
-    public boolean isLocallyInitiated() {
-        boolean streamIsClient = (id % 2 == 1);
-        return connection.client == streamIsClient;
-    }
-
-    public SpdyConnection getConnection() {
-        return connection;
-    }
-
-    public List<String> getRequestHeaders() {
-        return requestHeaders;
-    }
-
-    /**
-     * Returns the stream's response headers, blocking if necessary if they
-     * have not been received yet.
-     */
-    public synchronized List<String> getResponseHeaders() throws InterruptedException {
-        while (responseHeaders == null && rstStatusCode == -1) {
-            wait();
-        }
-        // TODO: throw InterruptedIOException?
-        // TODO: throw if responseHeaders == null
-        return responseHeaders;
-    }
-
-    /**
-     * Returns the reason why this stream was closed, or -1 if it closed
-     * normally or has not yet been closed. Valid reasons are {@link
-     * #RST_PROTOCOL_ERROR}, {@link #RST_INVALID_STREAM}, {@link
-     * #RST_REFUSED_STREAM}, {@link #RST_UNSUPPORTED_VERSION}, {@link
-     * #RST_CANCEL}, {@link #RST_INTERNAL_ERROR} and {@link
-     * #RST_FLOW_CONTROL_ERROR}.
-     */
-    public synchronized int getRstStatusCode() {
-        return rstStatusCode;
-    }
-
-    /**
-     * Sends a reply to an incoming stream.
-     *
-     * @param out true to create an output stream that we can use to send data
-     *     to the remote peer. Corresponds to {@code FLAG_FIN}.
-     */
-    public void reply(List<String> responseHeaders, boolean out) throws IOException {
-        int flags = 0;
-        synchronized (this) {
-            if (responseHeaders == null) {
-                throw new NullPointerException("responseHeaders == null");
-            }
-            if (isLocallyInitiated()) {
-                throw new IllegalStateException("cannot reply to a locally initiated stream");
-            }
-            if (this.responseHeaders != null) {
-                throw new IllegalStateException("reply already sent");
-            }
-            this.responseHeaders = responseHeaders;
-            if (!out) {
-                this.out.finished = true;
-                flags |= SpdyConnection.FLAG_FIN;
-            }
-        }
-        connection.writeSynReply(id, flags, responseHeaders);
-    }
-
-    /**
-     * Returns an input stream that can be used to read data from the peer.
-     */
-    public InputStream getInputStream() {
-        return in;
-    }
-
-    /**
-     * Returns an output stream that can be used to write data to the peer.
-     *
-     * @throws IllegalStateException if this stream was initiated by the peer
-     *     and a {@link #reply} has not yet been sent.
-     */
-    public OutputStream getOutputStream() {
-        synchronized (this) {
-            if (responseHeaders == null && !isLocallyInitiated()) {
-                throw new IllegalStateException("reply before requesting the output stream");
-            }
-        }
-        return out;
-    }
-
-    /**
-     * Abnormally terminate this stream.
-     */
-    public void close(int rstStatusCode) throws IOException {
-        synchronized (this) {
-            // TODO: no-op if inFinished == true and outFinished == true ?
-            if (this.rstStatusCode != -1) {
-                return; // Already closed.
-            }
-            this.rstStatusCode = rstStatusCode;
-            in.finished = true;
-            out.finished = true;
-            notifyAll();
-        }
-        connection.writeSynReset(id, rstStatusCode);
-        connection.removeStream(id);
-    }
-
-    synchronized void receiveReply(List<String> strings) throws IOException {
-        if (!isLocallyInitiated() || responseHeaders != null) {
-            throw new IOException(); // TODO: send RST
-        }
-        responseHeaders = strings;
-        notifyAll();
-    }
-
-    // TODO: locking here broken. Writing threads are blocked by potentially slow reads.
-    synchronized void receiveData(InputStream in, int flags, int length) throws IOException {
-        this.in.receive(in, length);
-        if ((flags & SpdyConnection.FLAG_FIN) != 0) {
-            this.in.finished = true;
-            streamStateChanged();
-            notifyAll();
-        }
-    }
-
-    synchronized void receiveRstStream(int statusCode) {
-        if (rstStatusCode == -1) {
-            rstStatusCode = statusCode;
-            in.finished = true;
-            out.finished = true;
-            notifyAll();
-        }
-    }
-
-    /**
-     * An input stream that reads the incoming data frames of a stream. Although
-     * this class uses synchronization to safely receive incoming data frames,
-     * it is not intended for use by multiple readers.
-     */
-    private final class SpdyDataInputStream extends InputStream {
-        /*
-         * Store incoming data bytes in a circular buffer. When the buffer is
-         * empty, pos == -1. Otherwise pos is the first byte to read and limit
-         * is the first byte to write.
-         *
-         * { - - - X X X X - - - }
-         *         ^       ^
-         *        pos    limit
-         *
-         * { X X X - - - - X X X }
-         *         ^       ^
-         *       limit    pos
-         */
-
-        private final byte[] buffer = new byte[64 * 1024]; // 64KiB specified by TODO
-
-        /** the next byte to be read, or -1 if the buffer is empty. Never buffer.length */
-        private int pos = -1;
-
-        /** the last byte to be read. Never buffer.length */
-        private int limit;
-
-        /** True if the caller has closed this stream. */
-        private boolean closed;
-
-        /**
-         * True if either side has shut down this stream. We will receive no
-         * more bytes beyond those already in the buffer.
-         */
-        private boolean finished;
-
-        @Override public int available() throws IOException {
-            synchronized (SpdyStream.this) {
-                checkNotClosed();
-                if (pos == -1) {
-                    return 0;
-                } else if (limit > pos) {
-                    return limit - pos;
-                } else {
-                    return limit + (buffer.length - pos);
-                }
-            }
-        }
-
-        @Override public int read() throws IOException {
-            return Streams.readSingleByte(this);
-        }
-
-        @Override public int read(byte[] b, int offset, int count) throws IOException {
-            synchronized (SpdyStream.this) {
-                checkNotClosed();
-                Libcore.checkOffsetAndCount(b.length, offset, count);
-
-                while (pos == -1 && !finished) {
-                    try {
-                        SpdyStream.this.wait();
-                    } catch (InterruptedException e) {
-                        throw new InterruptedIOException();
-                    }
-                }
-
-                if (pos == -1) {
-                    return -1;
-                }
-
-                int copied = 0;
-
-                // drain from [pos..buffer.length)
-                if (limit <= pos) {
-                    int bytesToCopy = Math.min(count, buffer.length - pos);
-                    System.arraycopy(buffer, pos, b, offset, bytesToCopy);
-                    pos += bytesToCopy;
-                    copied += bytesToCopy;
-                    if (pos == buffer.length) {
-                        pos = 0;
-                    }
-                }
-
-                // drain from [pos..limit)
-                if (copied < count) {
-                    int bytesToCopy = Math.min(limit - pos, count - copied);
-                    System.arraycopy(buffer, pos, b, offset + copied, bytesToCopy);
-                    pos += bytesToCopy;
-                    copied += bytesToCopy;
-                }
-
-                // TODO: notify peer of flow-control
-
-                if (pos == limit) {
-                    pos = -1;
-                    limit = 0;
-                }
-
-                return copied;
-            }
-        }
-
-        void receive(InputStream in, int byteCount) throws IOException {
-            if (finished) {
-                return; // ignore this; probably a benign race
-            }
-            if (byteCount == 0) {
-                return;
-            }
-
-            if (byteCount > buffer.length - available()) {
-                throw new IOException(); // TODO: RST the stream
-            }
-
-            // fill [limit..buffer.length)
-            if (pos < limit) {
-                int firstCopyCount = Math.min(byteCount, buffer.length - limit);
-                Streams.readFully(in, buffer, limit, firstCopyCount);
-                limit += firstCopyCount;
-                byteCount -= firstCopyCount;
-                if (limit == buffer.length) {
-                    limit = 0;
-                }
-            }
-
-            // fill [limit..pos)
-            if (byteCount > 0) {
-                Streams.readFully(in, buffer, limit, byteCount);
-                limit += byteCount;
-            }
-
-            if (pos == -1) {
-                pos = 0;
-                SpdyStream.this.notifyAll();
-            }
-        }
-
-        @Override public void close() throws IOException {
-            synchronized (SpdyStream.this) {
-                closed = true;
-                streamStateChanged();
-            }
-        }
-
-        private void checkNotClosed() throws IOException {
-            if (closed) {
-                throw new IOException("stream closed");
-            }
-        }
-    }
-
-    private synchronized void streamStateChanged() throws IOException {
-        // If we closed the input stream before bytes ran out, we want to cancel
-        // it. But we can only cancel it once the output stream's bytes have all
-        // been sent, otherwise we'll terminate that innocent bystander.
-        if (in.closed && !in.finished && (out.finished || out.closed)) {
-            in.finished = true;
-            SpdyStream.this.close(RST_CANCEL);
-        }
-    }
-
-    /**
-     * An output stream that writes outgoing data frames of a stream. This class
-     * is not thread safe.
-     */
-    private final class SpdyDataOutputStream extends OutputStream {
-        private final byte[] buffer = new byte[8192];
-        private int pos = DATA_FRAME_HEADER_LENGTH;
-
-        /** True if the caller has closed this stream. */
-        private boolean closed;
-
-        /**
-         * True if either side has shut down this stream. We shall send no more
-         * bytes.
-         */
-        private boolean finished;
-
-        @Override public void write(int b) throws IOException {
-            Streams.writeSingleByte(this, b);
-        }
-
-        @Override public void write(byte[] bytes, int offset, int count) throws IOException {
-            Libcore.checkOffsetAndCount(bytes.length, offset, count);
-            checkNotClosed();
-
-            while (count > 0) {
-                if (pos == buffer.length) {
-                    writeFrame(false);
-                }
-                int bytesToCopy = Math.min(count, buffer.length - pos);
-                System.arraycopy(bytes, offset, buffer, pos, bytesToCopy);
-                pos += bytesToCopy;
-                offset += bytesToCopy;
-                count -= bytesToCopy;
-            }
-        }
-
-        @Override public void flush() throws IOException {
-            checkNotClosed();
-            if (pos > DATA_FRAME_HEADER_LENGTH) {
-                writeFrame(false);
-                connection.flush();
-            }
-        }
-
-        @Override public void close() throws IOException {
-            synchronized (SpdyStream.this) {
-                if (closed) {
-                    return;
-                }
-                closed = true;
-            }
-            writeFrame(true);
-            connection.flush();
-            streamStateChanged();
-        }
-
-        private void writeFrame(boolean last) throws IOException {
-            int flags = 0;
-            if (last) {
-                flags |= SpdyConnection.FLAG_FIN;
-            }
-            int length = pos - DATA_FRAME_HEADER_LENGTH;
-            Libcore.pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN);
-            Libcore.pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN);
-            connection.writeFrame(buffer, 0, pos);
-            pos = DATA_FRAME_HEADER_LENGTH;
-        }
-
-        private void checkNotClosed() throws IOException {
-            synchronized (SpdyStream.this) {
-                if (closed) {
-                    throw new IOException("stream closed");
-                }
-                if (finished) {
-                    throw new IOException("output stream finished "
-                            + "(RST status code=" + rstStatusCode + ")");
-                }
-            }
-        }
-    }
-}
diff --git a/src/main/java/libcore/util/BasicLruCache.java b/src/main/java/libcore/util/BasicLruCache.java
deleted file mode 100644
index b5f6fdf..0000000
--- a/src/main/java/libcore/util/BasicLruCache.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * A minimal least-recently-used cache for libcore. Prefer {@code
- * android.util.LruCache} where that is available.
- */
-public class BasicLruCache<K, V> {
-    private final LinkedHashMap<K, V> map;
-    private final int maxSize;
-
-    public BasicLruCache(int maxSize) {
-        if (maxSize <= 0) {
-            throw new IllegalArgumentException("maxSize <= 0");
-        }
-        this.maxSize = maxSize;
-        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
-    }
-
-    /**
-     * Returns the value for {@code key} if it exists in the cache or can be
-     * created by {@code #create}. If a value was returned, it is moved to the
-     * head of the queue. This returns null if a value is not cached and cannot
-     * be created.
-     */
-    public synchronized final V get(K key) {
-        if (key == null) {
-            throw new NullPointerException();
-        }
-
-        V result = map.get(key);
-        if (result != null) {
-            return result;
-        }
-
-        result = create(key);
-
-        if (result != null) {
-            map.put(key, result);
-            trimToSize(maxSize);
-        }
-        return result;
-    }
-
-    /**
-     * Caches {@code value} for {@code key}. The value is moved to the head of
-     * the queue.
-     *
-     * @return the previous value mapped by {@code key}. Although that entry is
-     *     no longer cached, it has not been passed to {@link #entryEvicted}.
-     */
-    public synchronized final V put(K key, V value) {
-        if (key == null || value == null) {
-            throw new NullPointerException();
-        }
-
-        V previous = map.put(key, value);
-        trimToSize(maxSize);
-        return previous;
-    }
-
-    private void trimToSize(int maxSize) {
-        while (map.size() > maxSize) {
-            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
-
-            K key = toEvict.getKey();
-            V value = toEvict.getValue();
-            map.remove(key);
-
-            entryEvicted(key, value);
-        }
-    }
-
-    /**
-     * Called for entries that have reached the tail of the least recently used
-     * queue and are be removed. The default implementation does nothing.
-     */
-    protected void entryEvicted(K key, V value) {
-    }
-
-    /**
-     * Called after a cache miss to compute a value for the corresponding key.
-     * Returns the computed value or null if no value can be computed. The
-     * default implementation returns null.
-     */
-    protected V create(K key) {
-        return null;
-    }
-
-    /**
-     * Returns a copy of the current contents of the cache, ordered from least
-     * recently accessed to most recently accessed.
-     */
-    public synchronized final Map<K, V> snapshot() {
-        return new LinkedHashMap<K, V>(map);
-    }
-
-    /**
-     * Clear the cache, calling {@link #entryEvicted} on each removed entry.
-     */
-    public synchronized final void evictAll() {
-        trimToSize(0);
-    }
-}
diff --git a/src/main/java/libcore/util/Charsets.java b/src/main/java/libcore/util/Charsets.java
deleted file mode 100644
index 95848ee..0000000
--- a/src/main/java/libcore/util/Charsets.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import java.nio.charset.Charset;
-
-/**
- * Provides convenient access to the most important built-in charsets. Saves a hash lookup and
- * unnecessary handling of UnsupportedEncodingException at call sites, compared to using the
- * charset's name.
- *
- * Also various special-case charset conversions (for performance).
- *
- * @hide internal use only
- */
-public final class Charsets {
-    /**
-     * A cheap and type-safe constant for the ISO-8859-1 Charset.
-     */
-    public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
-
-    /**
-     * A cheap and type-safe constant for the US-ASCII Charset.
-     */
-    public static final Charset US_ASCII = Charset.forName("US-ASCII");
-
-    /**
-     * A cheap and type-safe constant for the UTF-8 Charset.
-     */
-    public static final Charset UTF_8 = Charset.forName("UTF-8");
-
-    private Charsets() {
-    }
-}
diff --git a/src/main/java/libcore/util/CollectionUtils.java b/src/main/java/libcore/util/CollectionUtils.java
deleted file mode 100644
index 45edf4f..0000000
--- a/src/main/java/libcore/util/CollectionUtils.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import java.lang.ref.Reference;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
-public final class CollectionUtils {
-    private CollectionUtils() {
-    }
-
-    /**
-     * Returns an iterator over the values referenced by the elements of {@code
-     * iterable}.
-     *
-     * @param trim true to remove reference objects from the iterable after
-     *     their referenced values have been cleared.
-     */
-    public static <T> Iterable<T> dereferenceIterable(
-            final Iterable<? extends Reference<T>> iterable, final boolean trim) {
-        return new Iterable<T>() {
-            public Iterator<T> iterator() {
-                return new Iterator<T>() {
-                    private final Iterator<? extends Reference<T>> delegate = iterable.iterator();
-                    private boolean removeIsOkay;
-                    private T next;
-
-                    private void computeNext() {
-                        removeIsOkay = false;
-                        while (next == null && delegate.hasNext()) {
-                            next = delegate.next().get();
-                            if (trim && next == null) {
-                                delegate.remove();
-                            }
-                        }
-                    }
-
-                    @Override public boolean hasNext() {
-                        computeNext();
-                        return next != null;
-                    }
-
-                    @Override public T next() {
-                        if (!hasNext()) {
-                            throw new IllegalStateException();
-                        }
-                        T result = next;
-                        removeIsOkay = true;
-                        next = null;
-                        return result;
-                    }
-
-                    public void remove() {
-                        if (!removeIsOkay) {
-                            throw new IllegalStateException();
-                        }
-                        delegate.remove();
-                    }
-                };
-            }
-        };
-    }
-
-    /**
-     * Sorts and removes duplicate elements from {@code list}. This method does
-     * not use {@link Object#equals}: only the comparator defines equality.
-     */
-    public static <T> void removeDuplicates(List<T> list, Comparator<? super T> comparator) {
-        Collections.sort(list, comparator);
-        int j = 1;
-        for (int i = 1; i < list.size(); i++) {
-            if (comparator.compare(list.get(j - 1), list.get(i)) != 0) {
-                T object = list.get(i);
-                list.set(j++, object);
-            }
-        }
-        if (j < list.size()) {
-            list.subList(j, list.size()).clear();
-        }
-    }
-}
diff --git a/src/main/java/libcore/util/DefaultFileNameMap.java b/src/main/java/libcore/util/DefaultFileNameMap.java
deleted file mode 100644
index e817a72..0000000
--- a/src/main/java/libcore/util/DefaultFileNameMap.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import java.net.FileNameMap;
-import java.util.Locale;
-import libcore.net.MimeUtils;
-
-/**
- * Implements {@link java.net.FileNameMap} in terms of {@link libcore.net.MimeUtils}.
- */
-class DefaultFileNameMap implements FileNameMap {
-    public String getContentTypeFor(String filename) {
-        if (filename.endsWith("/")) {
-            // a directory, return html
-            return MimeUtils.guessMimeTypeFromExtension("html");
-        }
-        int lastCharInExtension = filename.lastIndexOf('#');
-        if (lastCharInExtension < 0) {
-            lastCharInExtension = filename.length();
-        }
-        int firstCharInExtension = filename.lastIndexOf('.') + 1;
-        String ext = "";
-        if (firstCharInExtension > filename.lastIndexOf('/')) {
-            ext = filename.substring(firstCharInExtension, lastCharInExtension);
-        }
-        return MimeUtils.guessMimeTypeFromExtension(ext.toLowerCase(Locale.US));
-    }
-}
diff --git a/src/main/java/libcore/util/EmptyArray.java b/src/main/java/libcore/util/EmptyArray.java
deleted file mode 100644
index 0f919c5..0000000
--- a/src/main/java/libcore/util/EmptyArray.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class EmptyArray {
-    private EmptyArray() {
-    }
-
-    public static final boolean[] BOOLEAN = new boolean[0];
-    public static final byte[] BYTE = new byte[0];
-    public static final char[] CHAR = new char[0];
-    public static final double[] DOUBLE = new double[0];
-    public static final int[] INT = new int[0];
-
-    public static final Class<?>[] CLASS = new Class[0];
-    public static final Object[] OBJECT = new Object[0];
-    public static final String[] STRING = new String[0];
-    public static final Throwable[] THROWABLE = new Throwable[0];
-    public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];
-}
diff --git a/src/main/java/libcore/util/IntegralToString.java b/src/main/java/libcore/util/IntegralToString.java
deleted file mode 100644
index 1b66e51..0000000
--- a/src/main/java/libcore/util/IntegralToString.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-/**
- * Converts integral types to strings. This class is public but hidden so that it can also be
- * used by java.util.Formatter to speed up %d. This class is in java.lang so that it can take
- * advantage of the package-private String constructor.
- *
- * The most important methods are appendInt/appendLong and intToString(int)/longToString(int).
- * The former are used in the implementation of StringBuilder, StringBuffer, and Formatter, while
- * the latter are used by Integer.toString and Long.toString.
- *
- * The append methods take AbstractStringBuilder rather than Appendable because the latter requires
- * CharSequences, while we only have raw char[]s. Since much of the savings come from not creating
- * any garbage, we can't afford temporary CharSequence instances.
- *
- * One day the performance advantage of the binary/hex/octal specializations will be small enough
- * that we can lose the duplication, but until then this class offers the full set.
- *
- * @hide
- */
-public final class IntegralToString {
-    /**
-     * The digits for every supported radix.
-     */
-    private static final char[] DIGITS = {
-        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
-        'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
-        'u', 'v', 'w', 'x', 'y', 'z'
-    };
-
-    private static final char[] UPPER_CASE_DIGITS = {
-        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
-        'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
-        'U', 'V', 'W', 'X', 'Y', 'Z'
-    };
-
-    private IntegralToString() {
-    }
-
-    public static String bytesToHexString(byte[] bytes, boolean upperCase) {
-        char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
-        char[] buf = new char[bytes.length * 2];
-        int c = 0;
-        for (byte b : bytes) {
-            buf[c++] = digits[(b >> 4) & 0xf];
-            buf[c++] = digits[b & 0xf];
-        }
-        return new String(buf);
-    }
-}
diff --git a/src/main/java/libcore/util/Libcore.java b/src/main/java/libcore/util/Libcore.java
deleted file mode 100644
index 7053187..0000000
--- a/src/main/java/libcore/util/Libcore.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.List;
-import javax.net.ssl.SSLSocket;
-import org.eclipse.jetty.npn.NextProtoNego;
-
-/**
- * APIs for interacting with Android's core library. This mostly emulates the
- * Android core library for interoperability with other runtimes.
- */
-public final class Libcore {
-
-    private Libcore() {
-    }
-
-    private static boolean useAndroidTlsApis;
-    private static Class<?> openSslSocketClass;
-    private static Method setUseSessionTickets;
-    private static Method setHostname;
-    private static boolean android23TlsOptionsAvailable;
-    private static Method setNpnProtocols;
-    private static Method getNpnSelectedProtocol;
-    private static boolean android41TlsOptionsAvailable;
-
-    static {
-        try {
-            openSslSocketClass = Class.forName(
-                    "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
-            useAndroidTlsApis = true;
-            setUseSessionTickets = openSslSocketClass.getMethod(
-                    "setUseSessionTickets", boolean.class);
-            setHostname = openSslSocketClass.getMethod("setHostname", String.class);
-            android23TlsOptionsAvailable = true;
-            setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
-            getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
-            android41TlsOptionsAvailable = true;
-        } catch (ClassNotFoundException ignored) {
-            // This isn't an Android runtime.
-        } catch (NoSuchMethodException ignored) {
-            // This Android runtime is missing some optional TLS options.
-        }
-    }
-
-    public static void makeTlsTolerant(SSLSocket socket, String uriHost, boolean tlsTolerant) {
-        if (!tlsTolerant) {
-            socket.setEnabledProtocols(new String[] {"SSLv3"});
-            return;
-        }
-
-        if (android23TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) {
-            // This is Android: use reflection on OpenSslSocketImpl.
-            try {
-                setUseSessionTickets.invoke(socket, true);
-                setHostname.invoke(socket, uriHost);
-            } catch (InvocationTargetException e) {
-                throw new RuntimeException(e);
-            } catch (IllegalAccessException e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-
-    /**
-     * Returns the negotiated protocol, or null if no protocol was negotiated.
-     */
-    public static byte[] getNpnSelectedProtocol(SSLSocket socket) {
-        if (useAndroidTlsApis) {
-            // This is Android: use reflection on OpenSslSocketImpl.
-            if (android41TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) {
-                try {
-                    return (byte[]) getNpnSelectedProtocol.invoke(socket);
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException(e);
-                } catch (IllegalAccessException e) {
-                    throw new AssertionError(e);
-                }
-            }
-            return null;
-        } else {
-            // This is OpenJDK: use JettyNpnProvider.
-            JettyNpnProvider provider = (JettyNpnProvider) NextProtoNego.get(socket);
-            if (!provider.unsupported && provider.selected == null) {
-                throw new IllegalStateException(
-                        "No callback received. Is NPN configured properly?");
-            }
-            try {
-                return provider.unsupported
-                        ? null
-                        : provider.selected.getBytes("US-ASCII");
-            } catch (UnsupportedEncodingException e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-
-    public static void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
-        if (useAndroidTlsApis) {
-            // This is Android: use reflection on OpenSslSocketImpl.
-            if (android41TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) {
-                try {
-                    setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
-                } catch (IllegalAccessException e) {
-                    throw new AssertionError(e);
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        } else {
-            // This is OpenJDK: use JettyNpnProvider.
-            try {
-                List<String> strings = new ArrayList<String>();
-                for (int i = 0; i < npnProtocols.length;) {
-                    int length = npnProtocols[i++];
-                    strings.add(new String(npnProtocols, i, length, "US-ASCII"));
-                    i += length;
-                }
-                JettyNpnProvider provider = new JettyNpnProvider();
-                provider.protocols = strings;
-                NextProtoNego.put(socket, provider);
-            } catch (UnsupportedEncodingException e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-
-    private static class JettyNpnProvider
-            implements NextProtoNego.ClientProvider, NextProtoNego.ServerProvider {
-        List<String> protocols;
-        boolean unsupported;
-        String selected;
-
-        @Override public boolean supports() {
-            return true;
-        }
-        @Override public List<String> protocols() {
-            return protocols;
-        }
-        @Override public void unsupported() {
-            this.unsupported = true;
-        }
-        @Override public void protocolSelected(String selected) {
-            this.selected = selected;
-        }
-        @Override public String selectProtocol(List<String> strings) {
-            // TODO: use OpenSSL's algorithm which uses 2 lists
-            System.out.println("CLIENT PROTOCOLS: " + protocols + " SERVER PROTOCOLS: " + strings);
-            String selected = protocols.get(0);
-            protocolSelected(selected);
-            return selected;
-        }
-    }
-
-    public static void deleteIfExists(File file) throws IOException {
-        // okhttp-changed: was Libcore.os.remove() in a try/catch block
-        file.delete();
-    }
-
-    public static void logW(String warning) {
-        // okhttp-changed: was System.logw()
-        System.out.println(warning);
-    }
-
-    public static int getEffectivePort(URI uri) {
-        return getEffectivePort(uri.getScheme(), uri.getPort());
-    }
-
-    public static int getEffectivePort(URL url) {
-        return getEffectivePort(url.getProtocol(), url.getPort());
-    }
-
-    private static int getEffectivePort(String scheme, int specifiedPort) {
-        return specifiedPort != -1
-                ? specifiedPort
-                : getDefaultPort(scheme);
-    }
-
-    public static int getDefaultPort(String scheme) {
-        if ("http".equalsIgnoreCase(scheme)) {
-            return 80;
-        } else if ("https".equalsIgnoreCase(scheme)) {
-            return 443;
-        } else {
-            return -1;
-        }
-    }
-
-    public static void checkOffsetAndCount(int arrayLength, int offset, int count) {
-        if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-    }
-
-    public static void tagSocket(Socket socket) {
-    }
-
-    public static void untagSocket(Socket socket) throws SocketException {
-    }
-
-    public static URI toUriLenient(URL url) throws URISyntaxException {
-        return url.toURI(); // this isn't as good as the built-in toUriLenient
-    }
-
-    public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) {
-        if (order == ByteOrder.BIG_ENDIAN) {
-            dst[offset++] = (byte) ((value >> 24) & 0xff);
-            dst[offset++] = (byte) ((value >> 16) & 0xff);
-            dst[offset++] = (byte) ((value >>  8) & 0xff);
-            dst[offset  ] = (byte) ((value >>  0) & 0xff);
-        } else {
-            dst[offset++] = (byte) ((value >>  0) & 0xff);
-            dst[offset++] = (byte) ((value >>  8) & 0xff);
-            dst[offset++] = (byte) ((value >> 16) & 0xff);
-            dst[offset  ] = (byte) ((value >> 24) & 0xff);
-        }
-    }
-}
diff --git a/src/main/java/libcore/util/MutableBoolean.java b/src/main/java/libcore/util/MutableBoolean.java
deleted file mode 100644
index 359a8f9..0000000
--- a/src/main/java/libcore/util/MutableBoolean.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableBoolean {
-    public boolean value;
-
-    public MutableBoolean(boolean value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableByte.java b/src/main/java/libcore/util/MutableByte.java
deleted file mode 100644
index 13f780b..0000000
--- a/src/main/java/libcore/util/MutableByte.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableByte {
-    public byte value;
-
-    public MutableByte(byte value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableChar.java b/src/main/java/libcore/util/MutableChar.java
deleted file mode 100644
index 1cafc3c..0000000
--- a/src/main/java/libcore/util/MutableChar.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableChar {
-    public char value;
-
-    public MutableChar(char value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableDouble.java b/src/main/java/libcore/util/MutableDouble.java
deleted file mode 100644
index 4473ae6..0000000
--- a/src/main/java/libcore/util/MutableDouble.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableDouble {
-    public double value;
-
-    public MutableDouble(double value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableFloat.java b/src/main/java/libcore/util/MutableFloat.java
deleted file mode 100644
index f81fba5..0000000
--- a/src/main/java/libcore/util/MutableFloat.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableFloat {
-    public float value;
-
-    public MutableFloat(float value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableInt.java b/src/main/java/libcore/util/MutableInt.java
deleted file mode 100644
index c8feb3a..0000000
--- a/src/main/java/libcore/util/MutableInt.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableInt {
-    public int value;
-
-    public MutableInt(int value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableLong.java b/src/main/java/libcore/util/MutableLong.java
deleted file mode 100644
index ad9b78e..0000000
--- a/src/main/java/libcore/util/MutableLong.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableLong {
-    public long value;
-
-    public MutableLong(long value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/MutableShort.java b/src/main/java/libcore/util/MutableShort.java
deleted file mode 100644
index 78b4c33..0000000
--- a/src/main/java/libcore/util/MutableShort.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class MutableShort {
-    public short value;
-
-    public MutableShort(short value) {
-        this.value = value;
-    }
-}
diff --git a/src/main/java/libcore/util/Objects.java b/src/main/java/libcore/util/Objects.java
deleted file mode 100644
index 050888d..0000000
--- a/src/main/java/libcore/util/Objects.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-public final class Objects {
-    private Objects() {
-    }
-
-    /**
-     * Returns true if two possibly-null objects are equal.
-     */
-    public static boolean equal(Object a, Object b) {
-        return a == b || (a != null && a.equals(b));
-    }
-
-    public static int hashCode(Object o) {
-        return (o == null) ? 0 : o.hashCode();
-    }
-}
diff --git a/src/main/java/libcore/util/SneakyThrow.java b/src/main/java/libcore/util/SneakyThrow.java
deleted file mode 100644
index f5c077c..0000000
--- a/src/main/java/libcore/util/SneakyThrow.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package libcore.util;
-
-/**
- * Exploits a weakness in the runtime to throw an arbitrary throwable without
- * the traditional declaration. <strong>This is a dangerous API that should be
- * used with great caution.</strong> Typically this is useful when rethrowing
- * throwables that are of a known range of types.
- *
- * <p>The following code must enumerate several types to rethrow:
- * <pre>
- * public void close() throws IOException {
- *     Throwable thrown = null;
- *     ...
- *
- *     if (thrown != null) {
- *         if (thrown instanceof IOException) {
- *             throw (IOException) thrown;
- *         } else if (thrown instanceof RuntimeException) {
- *             throw (RuntimeException) thrown;
- *         } else if (thrown instanceof Error) {
- *             throw (Error) thrown;
- *         } else {
- *             throw new AssertionError();
- *         }
- *     }
- * }</pre>
- * With SneakyThrow, rethrowing is easier:
- * <pre>
- * public void close() throws IOException {
- *     Throwable thrown = null;
- *     ...
- *
- *     if (thrown != null) {
- *         SneakyThrow.sneakyThrow(thrown);
- *     }
- * }</pre>
- */
-public final class SneakyThrow {
-    private SneakyThrow() {
-    }
-
-    public static void sneakyThrow(Throwable t) {
-        SneakyThrow.<Error>sneakyThrow2(t);
-    }
-
-    /**
-     * Exploits unsafety to throw an exception that the compiler wouldn't permit
-     * but that the runtime doesn't check. See Java Puzzlers #43.
-     */
-    @SuppressWarnings("unchecked")
-    private static <T extends Throwable> void sneakyThrow2(Throwable t) throws T {
-        throw (T) t;
-    }
-}
diff --git a/src/test/java/libcore/io/DiskLruCacheTest.java b/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java
similarity index 83%
rename from src/test/java/libcore/io/DiskLruCacheTest.java
rename to src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java
index 87b3e8e..160ec78 100644
--- a/src/test/java/libcore/io/DiskLruCacheTest.java
+++ b/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package libcore.io;
+package com.squareup.okhttp.internal;
 
+import com.squareup.okhttp.internal.DiskLruCache;
+import static com.squareup.okhttp.internal.DiskLruCache.JOURNAL_FILE;
+import static com.squareup.okhttp.internal.DiskLruCache.MAGIC;
+import static com.squareup.okhttp.internal.DiskLruCache.VERSION_1;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
@@ -27,22 +31,24 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import junit.framework.TestCase;
+import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
 
-import static libcore.io.DiskLruCache.JOURNAL_FILE;
-import static libcore.io.DiskLruCache.MAGIC;
-import static libcore.io.DiskLruCache.VERSION_1;
-
-public final class DiskLruCacheTest extends TestCase {
+public final class DiskLruCacheTest {
     private final int appVersion = 100;
     private String javaTmpDir;
     private File cacheDir;
     private File journalFile;
     private DiskLruCache cache;
-//    private final MockOs mockOs = new MockOs();
 
-    @Override public void setUp() throws Exception {
-        super.setUp();
+    @Before public void setUp() throws Exception {
         javaTmpDir = System.getProperty("java.io.tmpdir");
         cacheDir = new File(javaTmpDir, "DiskLruCacheTest");
         cacheDir.mkdir();
@@ -51,21 +57,18 @@
             file.delete();
         }
         cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
-//        mockOs.install();
     }
 
-    @Override protected void tearDown() throws Exception {
-//        mockOs.uninstall();
+    @After public void tearDown() throws Exception {
         cache.close();
-        super.tearDown();
     }
 
-    public void testEmptyCache() throws Exception {
+    @Test public void emptyCache() throws Exception {
         cache.close();
         assertJournalEquals();
     }
 
-    public void testWriteAndReadEntry() throws Exception {
+    @Test public void writeAndReadEntry() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         creator.set(0, "ABC");
         creator.set(1, "DE");
@@ -80,7 +83,7 @@
         assertEquals("DE", snapshot.getString(1));
     }
 
-    public void testReadAndWriteEntryAcrossCacheOpenAndClose() throws Exception {
+    @Test public void readAndWriteEntryAcrossCacheOpenAndClose() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         creator.set(0, "A");
         creator.set(1, "B");
@@ -94,7 +97,7 @@
         snapshot.close();
     }
 
-    public void testJournalWithEditAndPublish() throws Exception {
+    @Test public void journalWithEditAndPublish() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed
         creator.set(0, "AB");
@@ -104,7 +107,7 @@
         assertJournalEquals("DIRTY k1", "CLEAN k1 2 1");
     }
 
-    public void testRevertedNewFileIsRemoveInJournal() throws Exception {
+    @Test public void revertedNewFileIsRemoveInJournal() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed
         creator.set(0, "AB");
@@ -114,13 +117,13 @@
         assertJournalEquals("DIRTY k1", "REMOVE k1");
     }
 
-    public void testUnterminatedEditIsRevertedOnClose() throws Exception {
+    @Test public void unterminatedEditIsRevertedOnClose() throws Exception {
         cache.edit("k1");
         cache.close();
         assertJournalEquals("DIRTY k1", "REMOVE k1");
     }
 
-    public void testJournalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception {
+    @Test public void journalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         assertNull(cache.get("k1"));
         creator.set(0, "A");
@@ -130,7 +133,7 @@
         assertJournalEquals("DIRTY k1", "CLEAN k1 1 2");
     }
 
-    public void testJournalWithEditAndPublishAndRead() throws Exception {
+    @Test public void journalWithEditAndPublishAndRead() throws Exception {
         DiskLruCache.Editor k1Creator = cache.edit("k1");
         k1Creator.set(0, "AB");
         k1Creator.set(1, "C");
@@ -147,7 +150,7 @@
                 "READ k1");
     }
 
-    public void testCannotOperateOnEditAfterPublish() throws Exception {
+    @Test public void cannotOperateOnEditAfterPublish() throws Exception {
         DiskLruCache.Editor editor = cache.edit("k1");
         editor.set(0, "A");
         editor.set(1, "B");
@@ -155,7 +158,7 @@
         assertInoperable(editor);
     }
 
-    public void testCannotOperateOnEditAfterRevert() throws Exception {
+    @Test public void cannotOperateOnEditAfterRevert() throws Exception {
         DiskLruCache.Editor editor = cache.edit("k1");
         editor.set(0, "A");
         editor.set(1, "B");
@@ -163,7 +166,7 @@
         assertInoperable(editor);
     }
 
-    public void testExplicitRemoveAppliedToDiskImmediately() throws Exception {
+    @Test public void explicitRemoveAppliedToDiskImmediately() throws Exception {
         DiskLruCache.Editor editor = cache.edit("k1");
         editor.set(0, "ABC");
         editor.set(1, "B");
@@ -178,7 +181,7 @@
      * Each read sees a snapshot of the file at the time read was called.
      * This means that two reads of the same key can see different data.
      */
-    public void testReadAndWriteOverlapsMaintainConsistency() throws Exception {
+    @Test public void readAndWriteOverlapsMaintainConsistency() throws Exception {
         DiskLruCache.Editor v1Creator = cache.edit("k1");
         v1Creator.set(0, "AAaa");
         v1Creator.set(1, "BBbb");
@@ -205,7 +208,7 @@
         snapshot1.close();
     }
 
-    public void testOpenWithDirtyKeyDeletesAllFilesForThatKey() throws Exception {
+    @Test public void openWithDirtyKeyDeletesAllFilesForThatKey() throws Exception {
         cache.close();
         File cleanFile0 = getCleanFile("k1", 0);
         File cleanFile1 = getCleanFile("k1", 1);
@@ -224,7 +227,7 @@
         assertNull(cache.get("k1"));
     }
 
-    public void testOpenWithInvalidVersionClearsDirectory() throws Exception {
+    @Test public void openWithInvalidVersionClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournalWithHeader(MAGIC, "0", "100", "2", "");
@@ -232,7 +235,7 @@
         assertGarbageFilesAllDeleted();
     }
 
-    public void testOpenWithInvalidAppVersionClearsDirectory() throws Exception {
+    @Test public void openWithInvalidAppVersionClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournalWithHeader(MAGIC, "1", "101", "2", "");
@@ -240,7 +243,7 @@
         assertGarbageFilesAllDeleted();
     }
 
-    public void testOpenWithInvalidValueCountClearsDirectory() throws Exception {
+    @Test public void openWithInvalidValueCountClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournalWithHeader(MAGIC, "1", "100", "1", "");
@@ -248,7 +251,7 @@
         assertGarbageFilesAllDeleted();
     }
 
-    public void testOpenWithInvalidBlankLineClearsDirectory() throws Exception {
+    @Test public void openWithInvalidBlankLineClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournalWithHeader(MAGIC, "1", "100", "2", "x");
@@ -256,7 +259,7 @@
         assertGarbageFilesAllDeleted();
     }
 
-    public void testOpenWithInvalidJournalLineClearsDirectory() throws Exception {
+    @Test public void openWithInvalidJournalLineClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournal("CLEAN k1 1 1", "BOGUS");
@@ -265,7 +268,7 @@
         assertNull(cache.get("k1"));
     }
 
-    public void testOpenWithInvalidFileSizeClearsDirectory() throws Exception {
+    @Test public void openWithInvalidFileSizeClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournal("CLEAN k1 0000x001 1");
@@ -274,7 +277,7 @@
         assertNull(cache.get("k1"));
     }
 
-    public void testOpenWithTruncatedLineDiscardsThatLine() throws Exception {
+    @Test public void openWithTruncatedLineDiscardsThatLine() throws Exception {
         cache.close();
         writeFile(getCleanFile("k1", 0), "A");
         writeFile(getCleanFile("k1", 1), "B");
@@ -285,7 +288,7 @@
         assertNull(cache.get("k1"));
     }
 
-    public void testOpenWithTooManyFileSizesClearsDirectory() throws Exception {
+    @Test public void openWithTooManyFileSizesClearsDirectory() throws Exception {
         cache.close();
         generateSomeGarbageFiles();
         createJournal("CLEAN k1 1 1 1");
@@ -294,7 +297,7 @@
         assertNull(cache.get("k1"));
     }
 
-    public void testKeyWithSpaceNotPermitted() throws Exception {
+    @Test public void keyWithSpaceNotPermitted() throws Exception {
         try {
             cache.edit("my key");
             fail();
@@ -302,7 +305,7 @@
         }
     }
 
-    public void testKeyWithNewlineNotPermitted() throws Exception {
+    @Test public void keyWithNewlineNotPermitted() throws Exception {
         try {
             cache.edit("my\nkey");
             fail();
@@ -310,7 +313,7 @@
         }
     }
 
-    public void testKeyWithCarriageReturnNotPermitted() throws Exception {
+    @Test public void keyWithCarriageReturnNotPermitted() throws Exception {
         try {
             cache.edit("my\rkey");
             fail();
@@ -318,7 +321,7 @@
         }
     }
 
-    public void testNullKeyThrows() throws Exception {
+    @Test public void nullKeyThrows() throws Exception {
         try {
             cache.edit(null);
             fail();
@@ -326,7 +329,7 @@
         }
     }
 
-    public void testCreateNewEntryWithTooFewValuesFails() throws Exception {
+    @Test public void createNewEntryWithTooFewValuesFails() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         creator.set(1, "A");
         try {
@@ -347,7 +350,7 @@
         creator2.commit();
     }
 
-    public void testCreateNewEntryWithMissingFileAborts() throws Exception {
+    @Test public void createNewEntryWithMissingFileAborts() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         creator.set(0, "A");
         creator.set(1, "A");
@@ -369,7 +372,7 @@
         creator2.commit();
     }
 
-    public void testRevertWithTooFewValues() throws Exception {
+    @Test public void revertWithTooFewValues() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         creator.set(1, "A");
         creator.abort();
@@ -380,7 +383,7 @@
         assertNull(cache.get("k1"));
     }
 
-    public void testUpdateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception {
+    @Test public void updateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception {
         DiskLruCache.Editor creator = cache.edit("k1");
         creator.set(0, "A");
         creator.set(1, "B");
@@ -396,7 +399,7 @@
         snapshot.close();
     }
 
-    public void testEvictOnInsert() throws Exception {
+    @Test public void evictOnInsert() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
 
@@ -432,7 +435,7 @@
         assertValue("E", "eeee", "eeee");
     }
 
-    public void testEvictOnUpdate() throws Exception {
+    @Test public void evictOnUpdate() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
 
@@ -450,7 +453,7 @@
         assertValue("C", "c", "cc");
     }
 
-    public void testEvictionHonorsLruFromCurrentSession() throws Exception {
+    @Test public void evictionHonorsLruFromCurrentSession() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
         set("A", "a", "a");
@@ -474,7 +477,7 @@
         assertValue("F", "f", "f");
     }
 
-    public void testEvictionHonorsLruFromPreviousSession() throws Exception {
+    @Test public void evictionHonorsLruFromPreviousSession() throws Exception {
         set("A", "a", "a");
         set("B", "b", "b");
         set("C", "c", "c");
@@ -498,7 +501,7 @@
         assertValue("G", "g", "g");
     }
 
-    public void testCacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception {
+    @Test public void cacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
         set("A", "aaaaa", "aaaaaa"); // size=11
@@ -506,7 +509,7 @@
         assertAbsent("A");
     }
 
-    public void testCacheSingleValueOfSizeGreaterThanMaxSize() throws Exception {
+    @Test public void cacheSingleValueOfSizeGreaterThanMaxSize() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
         set("A", "aaaaaaaaaaa", "a"); // size=12
@@ -514,7 +517,7 @@
         assertAbsent("A");
     }
 
-    public void testConstructorDoesNotAllowZeroCacheSize() throws Exception {
+    @Test public void constructorDoesNotAllowZeroCacheSize() throws Exception {
         try {
             DiskLruCache.open(cacheDir, appVersion, 2, 0);
             fail();
@@ -522,7 +525,7 @@
         }
     }
 
-    public void testConstructorDoesNotAllowZeroValuesPerEntry() throws Exception {
+    @Test public void constructorDoesNotAllowZeroValuesPerEntry() throws Exception {
         try {
             DiskLruCache.open(cacheDir, appVersion, 0, 10);
             fail();
@@ -530,18 +533,18 @@
         }
     }
 
-    public void testRemoveAbsentElement() throws Exception {
+    @Test public void removeAbsentElement() throws Exception {
         cache.remove("A");
     }
 
-    public void testReadingTheSameStreamMultipleTimes() throws Exception {
+    @Test public void readingTheSameStreamMultipleTimes() throws Exception {
         set("A", "a", "b");
         DiskLruCache.Snapshot snapshot = cache.get("A");
         assertSame(snapshot.getInputStream(0), snapshot.getInputStream(0));
         snapshot.close();
     }
 
-    public void testRebuildJournalOnRepeatedReads() throws Exception {
+    @Test public void rebuildJournalOnRepeatedReads() throws Exception {
         set("A", "a", "a");
         set("B", "b", "b");
         long lastJournalLength = 0;
@@ -558,7 +561,7 @@
         }
     }
 
-    public void testRebuildJournalOnRepeatedEdits() throws Exception {
+    @Test public void rebuildJournalOnRepeatedEdits() throws Exception {
         long lastJournalLength = 0;
         while (true) {
             long journalLength = journalFile.length();
@@ -577,7 +580,7 @@
         assertValue("B", "b", "b");
     }
 
-    public void testOpenCreatesDirectoryIfNecessary() throws Exception {
+    @Test public void openCreatesDirectoryIfNecessary() throws Exception {
         cache.close();
         File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary");
         cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE);
@@ -587,42 +590,13 @@
         assertTrue(new File(dir, "journal").exists());
     }
 
-    public void testFileDeletedExternally() throws Exception {
+    @Test public void fileDeletedExternally() throws Exception {
         set("A", "a", "a");
         getCleanFile("A", 1).delete();
         assertNull(cache.get("A"));
     }
 
-//    public void testFileBecomesInaccessibleDuringReadResultsInIoException() throws Exception {
-//        set("A", "aaaaa", "a");
-//        DiskLruCache.Snapshot snapshot = cache.get("A");
-//        InputStream in = snapshot.getInputStream(0);
-//        assertEquals('a', in.read());
-//        mockOs.enqueueFault("read");
-//        try {
-//            in.read();
-//            fail();
-//        } catch (IOException expected) {
-//        }
-//        snapshot.close();
-//    }
-
-//    public void testFileBecomesInaccessibleDuringWriteIsSilentlyDiscarded() throws Exception {
-//        set("A", "a", "a");
-//        DiskLruCache.Editor editor = cache.edit("A");
-//        OutputStream out0 = editor.newOutputStream(0);
-//        out0.write('b');
-//        out0.close();
-//        OutputStream out1 = editor.newOutputStream(1);
-//        out1.write('c');
-//        mockOs.enqueueFault("write");
-//        out1.write('c'); // this doesn't throw...
-//        out1.close();
-//        editor.commit(); // ... but this will abort
-//        assertAbsent("A");
-//    }
-
-    public void testEditSameVersion() throws Exception {
+    @Test public void editSameVersion() throws Exception {
         set("A", "a", "a");
         DiskLruCache.Snapshot snapshot = cache.get("A");
         DiskLruCache.Editor editor = snapshot.edit();
@@ -631,7 +605,7 @@
         assertValue("A", "a", "a2");
     }
 
-    public void testEditSnapshotAfterChangeAborted() throws Exception {
+    @Test public void editSnapshotAfterChangeAborted() throws Exception {
         set("A", "a", "a");
         DiskLruCache.Snapshot snapshot = cache.get("A");
         DiskLruCache.Editor toAbort = snapshot.edit();
@@ -643,7 +617,7 @@
         assertValue("A", "a", "a2");
     }
 
-    public void testEditSnapshotAfterChangeCommitted() throws Exception {
+    @Test public void editSnapshotAfterChangeCommitted() throws Exception {
         set("A", "a", "a");
         DiskLruCache.Snapshot snapshot = cache.get("A");
         DiskLruCache.Editor toAbort = snapshot.edit();
@@ -652,7 +626,7 @@
         assertNull(snapshot.edit());
     }
 
-    public void testEditSinceEvicted() throws Exception {
+    @Test public void editSinceEvicted() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
         set("A", "aa", "aaa"); // size 5
@@ -663,7 +637,7 @@
         assertNull(snapshot.edit());
     }
 
-    public void testEditSinceEvictedAndRecreated() throws Exception {
+    @Test public void editSinceEvictedAndRecreated() throws Exception {
         cache.close();
         cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
         set("A", "aa", "aaa"); // size 5
diff --git a/src/test/java/libcore/net/ssl/SslContextBuilder.java b/src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java
similarity index 97%
rename from src/test/java/libcore/net/ssl/SslContextBuilder.java
rename to src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java
index d88ca9c..c0a520c 100644
--- a/src/test/java/libcore/net/ssl/SslContextBuilder.java
+++ b/src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.net.ssl;
+package com.squareup.okhttp.internal;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -103,7 +103,7 @@
         X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
         X500Principal issuer = new X500Principal("CN=" + hostName);
         X500Principal subject = new X500Principal("CN=" + hostName);
-        generator.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+        generator.setSerialNumber(BigInteger.ONE);
         generator.setIssuerDN(issuer);
         generator.setNotBefore(new Date(notBefore));
         generator.setNotAfter(new Date(notAfter));
diff --git a/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java b/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java
new file mode 100644
index 0000000..5f85b52
--- /dev/null
+++ b/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal;
+
+import static com.squareup.okhttp.internal.Util.US_ASCII;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.InputStream;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+public final class StrictLineReaderTest {
+    @Test public void lineReaderConsistencyWithReadAsciiLine() throws Exception {
+        // Testing with LineReader buffer capacity 32 to check some corner cases.
+        StrictLineReader lineReader = new StrictLineReader(createTestInputStream(), 32, US_ASCII);
+        InputStream refStream = createTestInputStream();
+        while (true) {
+            try {
+                String refLine = Util.readAsciiLine(refStream);
+                try {
+                    String line = lineReader.readLine();
+                    if (!refLine.equals(line)) {
+                        fail("line (\""+line+"\") differs from expected (\""+refLine+"\").");
+                    }
+                } catch (EOFException eof) {
+                    fail("line reader threw EOFException too early.");
+                }
+            } catch (EOFException refEof) {
+                try {
+                    lineReader.readLine();
+                    fail("line reader didn't throw the expected EOFException.");
+                } catch (EOFException eof) {
+                    // OK
+                    break;
+                }
+            }
+        }
+        refStream.close();
+        lineReader.close();
+    }
+
+    private InputStream createTestInputStream() {
+        return new ByteArrayInputStream((
+                /* each source lines below should represent 32 bytes, until the next comment */
+                "12 byte line\n18 byte line......\n" +
+                        "pad\nline spanning two 32-byte bu" +
+                        "ffers\npad......................\n" +
+                        "pad\nline spanning three 32-byte " +
+                        "buffers and ending with LF at th" +
+                        "e end of a 32 byte buffer......\n" +
+                        "pad\nLine ending with CRLF split" +
+                        " at the end of a 32-byte buffer\r" +
+                        "\npad...........................\n" +
+                        /* end of 32-byte lines */
+                        "line ending with CRLF\r\n" +
+                        "this is a long line with embedded CR \r ending with CRLF and having more than " +
+                        "32 characters\r\n" +
+                        "unterminated line - should be dropped"
+        ).getBytes());
+    }
+}
diff --git a/src/test/java/libcore/net/http/ExternalSpdyExample.java b/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java
similarity index 87%
rename from src/test/java/libcore/net/http/ExternalSpdyExample.java
rename to src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java
index dc8aa1e..f93c493 100644
--- a/src/test/java/libcore/net/http/ExternalSpdyExample.java
+++ b/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
-import com.squareup.okhttp.OkHttpConnection;
-import com.squareup.okhttp.OkHttpsConnection;
+import com.squareup.okhttp.OkHttpClient;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.net.URL;
 import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSession;
 
 public final class ExternalSpdyExample {
     public static void main(String[] args) throws Exception {
         URL url = new URL("https://www.google.ca/");
-        OkHttpsConnection connection = (OkHttpsConnection) OkHttpConnection.open(url);
+        HttpsURLConnection connection = (HttpsURLConnection) new OkHttpClient().open(url);
 
         connection.setHostnameVerifier(new HostnameVerifier() {
             @Override public boolean verify(String s, SSLSession sslSession) {
diff --git a/src/test/java/libcore/net/http/HttpResponseCacheTest.java b/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
similarity index 79%
rename from src/test/java/libcore/net/http/HttpResponseCacheTest.java
rename to src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
index 5c675fa..841647d 100644
--- a/src/test/java/libcore/net/http/HttpResponseCacheTest.java
+++ b/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 import com.google.mockwebserver.RecordedRequest;
-import com.squareup.okhttp.OkHttpConnection;
+import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.internal.http.HttpResponseCache;
+import com.squareup.okhttp.internal.SslContextBuilder;
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -34,12 +37,17 @@
 import java.net.CookieManager;
 import java.net.HttpCookie;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
 import java.net.ResponseCache;
 import java.net.SecureCacheResponse;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.security.Principal;
+import java.security.cert.Certificate;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -56,48 +64,69 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.zip.GZIPOutputStream;
-import junit.framework.TestCase;
-
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Android's HttpResponseCacheTest.
  */
-public final class HttpResponseCacheTest extends TestCase {
+public final class HttpResponseCacheTest {
+    private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() {
+        @Override public boolean verify(String s, SSLSession sslSession) {
+            return true;
+        }
+    };
+    private final OkHttpClient client = new OkHttpClient();
     private MockWebServer server = new MockWebServer();
     private HttpResponseCache cache;
-//    private final MockOs mockOs = new MockOs();
     private final CookieManager cookieManager = new CookieManager();
 
-    @Override protected void setUp() throws Exception {
-        super.setUp();
+    private static final SSLContext sslContext;
+    static {
+        try {
+            sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build();
+        } catch (GeneralSecurityException e) {
+            throw new RuntimeException(e);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
 
+    @Before public void setUp() throws Exception {
         String tmp = System.getProperty("java.io.tmpdir");
         File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
         cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
         ResponseCache.setDefault(cache);
-//        mockOs.install();
         CookieHandler.setDefault(cookieManager);
     }
 
-    @Override protected void tearDown() throws Exception {
-//        mockOs.uninstall();
+    @After public void tearDown() throws Exception {
         server.shutdown();
         ResponseCache.setDefault(null);
         cache.getCache().delete();
         CookieHandler.setDefault(null);
-        super.tearDown();
     }
 
-    private static OkHttpConnection openConnection(URL url) {
-        return OkHttpConnection.open(url);
+    private HttpURLConnection openConnection(URL url) {
+        return client.open(url);
     }
 
     /**
      * Test that response caching is consistent with the RI and the spec.
      * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
      */
-    public void testResponseCachingByResponseCode() throws Exception {
+    @Test public void responseCachingByResponseCode() throws Exception {
         // Test each documented HTTP/1.1 code, plus the first unused value in each range.
         // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
 
@@ -137,12 +166,12 @@
      * Response code 407 should only come from proxy servers. Android's client
      * throws if it is sent by an origin server.
      */
-    public void testOriginServerSends407() throws Exception {
+    @Test public void originServerSends407() throws Exception {
         server.enqueue(new MockResponse().setResponseCode(407));
         server.play();
 
         URL url = server.getUrl("/");
-        OkHttpConnection conn = openConnection(url);
+        HttpURLConnection conn = openConnection(url);
         try {
             conn.getResponseCode();
             fail();
@@ -150,7 +179,7 @@
         }
     }
 
-    public void test_responseCaching_410() throws Exception {
+    @Test public void responseCaching_410() throws Exception {
         // the HTTP spec permits caching 410s, but the RI doesn't.
         assertCached(true, 410);
     }
@@ -172,7 +201,7 @@
         server.play();
 
         URL url = server.getUrl("/");
-        OkHttpConnection conn = openConnection(url);
+        HttpURLConnection conn = openConnection(url);
         assertEquals(responseCode, conn.getResponseCode());
 
         // exhaust the content stream
@@ -193,7 +222,7 @@
      * Test that we can interrogate the response when the cache is being
      * populated. http://code.google.com/p/android/issues/detail?id=7787
      */
-    public void testResponseCacheCallbackApis() throws Exception {
+    @Test public void responseCacheCallbackApis() throws Exception {
         final String body = "ABCDE";
         final AtomicInteger cacheCount = new AtomicInteger();
 
@@ -209,7 +238,7 @@
                 return null;
             }
             @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
-                OkHttpConnection httpConnection = (OkHttpConnection) conn;
+                HttpURLConnection httpConnection = (HttpURLConnection) conn;
                 try {
                     httpConnection.getRequestProperties();
                     fail();
@@ -238,21 +267,21 @@
         });
 
         URL url = server.getUrl("/");
-        OkHttpConnection connection = openConnection(url);
+        HttpURLConnection connection = openConnection(url);
         assertEquals(body, readAscii(connection));
         assertEquals(1, cacheCount.get());
     }
 
 
-    public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
+    @Test public void responseCachingAndInputStreamSkipWithFixedLength() throws IOException {
         testResponseCaching(TransferKind.FIXED_LENGTH);
     }
 
-    public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
+    @Test public void responseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
         testResponseCaching(TransferKind.CHUNKED);
     }
 
-    public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
+    @Test public void responseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
         testResponseCaching(TransferKind.END_OF_STREAM);
     }
 
@@ -270,7 +299,7 @@
         server.play();
 
         // Make sure that calling skip() doesn't omit bytes from the cache.
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = openConnection(server.getUrl("/"));
         InputStream in = urlConnection.getInputStream();
         assertEquals("I love ", readAscii(urlConnection, "I love ".length()));
         reliableSkip(in, "puppies but hate ".length());
@@ -295,60 +324,63 @@
         assertEquals(1, cache.getHitCount());
     }
 
-//    public void testSecureResponseCaching() throws IOException {
-//        TestSSLContext testSSLContext = TestSSLContext.create();
-//        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-//        server.enqueue(new MockResponse()
-//                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
-//                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
-//                .setBody("ABC"));
-//        server.play();
-//
-//        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        assertEquals("ABC", readAscii(connection));
-//
-//        // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
-//        String suite = connection.getCipherSuite();
-//        List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
-//        List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
-//        Principal peerPrincipal = connection.getPeerPrincipal();
-//        Principal localPrincipal = connection.getLocalPrincipal();
-//
-//        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        assertEquals("ABC", readAscii(connection));
-//
-//        assertEquals(2, cache.getRequestCount());
-//        assertEquals(1, cache.getNetworkCount());
-//        assertEquals(1, cache.getHitCount());
-//
-//        assertEquals(suite, connection.getCipherSuite());
-//        assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
-//        assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
-//        assertEquals(peerPrincipal, connection.getPeerPrincipal());
-//        assertEquals(localPrincipal, connection.getLocalPrincipal());
-//    }
-//
-//    public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException {
-//        TestSSLContext testSSLContext = TestSSLContext.create();
-//        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-//        server.enqueue(new MockResponse().setBody("ABC"));
-//        server.enqueue(new MockResponse().setBody("DEF"));
-//        server.play();
-//
-//        ResponseCache.setDefault(new InsecureResponseCache());
-//
-//        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        assertEquals("ABC", readAscii(connection));
-//
-//        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        assertEquals("DEF", readAscii(connection));
-//    }
+    @Test public void secureResponseCaching() throws IOException {
+        server.useHttps(sslContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setBody("ABC"));
+        server.play();
 
-    public void testResponseCachingAndRedirects() throws Exception {
+        HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/"));
+        connection.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        assertEquals("ABC", readAscii(connection));
+
+        // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
+        String suite = connection.getCipherSuite();
+        List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
+        List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
+        Principal peerPrincipal = connection.getPeerPrincipal();
+        Principal localPrincipal = connection.getLocalPrincipal();
+
+        connection = (HttpsURLConnection) client.open(server.getUrl("/")); // cached!
+        connection.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        assertEquals("ABC", readAscii(connection));
+
+        assertEquals(2, cache.getRequestCount());
+        assertEquals(1, cache.getNetworkCount());
+        assertEquals(1, cache.getHitCount());
+
+        assertEquals(suite, connection.getCipherSuite());
+        assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
+        assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
+        assertEquals(peerPrincipal, connection.getPeerPrincipal());
+        assertEquals(localPrincipal, connection.getLocalPrincipal());
+    }
+
+    @Test public void cacheReturnsInsecureResponseForSecureRequest() throws IOException {
+        server.useHttps(sslContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse().setBody("ABC"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.play();
+
+        ResponseCache.setDefault(new InsecureResponseCache());
+
+        HttpsURLConnection connection1 = (HttpsURLConnection) client.open(server.getUrl("/"));
+        connection1.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        assertEquals("ABC", readAscii(connection1));
+
+        // Not cached!
+        HttpsURLConnection connection2 = (HttpsURLConnection) client.open(server.getUrl("/"));
+        connection2.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection2.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        assertEquals("DEF", readAscii(connection2));
+    }
+
+    @Test public void responseCachingAndRedirects() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
@@ -361,7 +393,7 @@
         server.enqueue(new MockResponse().setBody("DEF"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = openConnection(server.getUrl("/"));
         assertEquals("ABC", readAscii(connection));
 
         connection = openConnection(server.getUrl("/")); // cached!
@@ -372,7 +404,7 @@
         assertEquals(2, cache.getHitCount());
     }
 
-    public void testRedirectToCachedResult() throws Exception {
+    @Test public void redirectToCachedResult() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .setBody("ABC"));
@@ -399,34 +431,36 @@
         assertEquals(2, request3.getSequenceNumber());
     }
 
-//    public void testSecureResponseCachingAndRedirects() throws IOException {
-//        TestSSLContext testSSLContext = TestSSLContext.create();
-//        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-//        server.enqueue(new MockResponse()
-//                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
-//                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
-//                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
-//                .addHeader("Location: /foo"));
-//        server.enqueue(new MockResponse()
-//                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
-//                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
-//                .setBody("ABC"));
-//        server.enqueue(new MockResponse().setBody("DEF"));
-//        server.play();
-//
-//        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        assertEquals("ABC", readAscii(connection));
-//
-//        connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        assertEquals("ABC", readAscii(connection));
-//
-//        assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4
-//        assertEquals(2, cache.getHitCount());
-//    }
+    @Test public void secureResponseCachingAndRedirects() throws IOException {
+        server.useHttps(sslContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
+                .addHeader("Location: /foo"));
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+                .setBody("ABC"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.play();
 
-    public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
+        HttpsURLConnection connection1 = (HttpsURLConnection) client.open(server.getUrl("/"));
+        connection1.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        assertEquals("ABC", readAscii(connection1));
+
+        // Cached!
+        HttpsURLConnection connection2 = (HttpsURLConnection) client.open(server.getUrl("/"));
+        connection1.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        assertEquals("ABC", readAscii(connection2));
+
+        assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4
+        assertEquals(2, cache.getHitCount());
+    }
+
+    @Test public void responseCacheRequestHeaders() throws IOException, URISyntaxException {
         server.enqueue(new MockResponse().setBody("ABC"));
         server.play();
 
@@ -451,15 +485,15 @@
     }
 
 
-    public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
+    @Test public void serverDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
         testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
     }
 
-    public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
+    @Test public void serverDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
         testServerPrematureDisconnect(TransferKind.CHUNKED);
     }
 
-    public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
+    @Test public void serverDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
         /*
          * Intentionally empty. This case doesn't make sense because there's no
          * such thing as a premature disconnect when the disconnect itself
@@ -493,20 +527,21 @@
         assertEquals(1, cache.getWriteSuccessCount());
     }
 
-    public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
+    @Test public void clientPrematureDisconnectWithContentLengthHeader() throws IOException {
         testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
     }
 
-    public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
+    @Test public void clientPrematureDisconnectWithChunkedEncoding() throws IOException {
         testClientPrematureDisconnect(TransferKind.CHUNKED);
     }
 
-    public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
+    @Test public void clientPrematureDisconnectWithNoLengthHeaders() throws IOException {
         testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
     }
 
     private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
-        MockResponse response = new MockResponse();
+        // Setting a low transfer speed ensures that stream discarding will time out.
+        MockResponse response = new MockResponse().setBytesPerSecond(6);
         transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
         server.enqueue(response);
         server.enqueue(new MockResponse().setBody("Request #2"));
@@ -530,7 +565,7 @@
         assertEquals(1, cache.getWriteSuccessCount());
     }
 
-    public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
+    @Test public void defaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
         //      last modified: 105 seconds ago
         //             served:   5 seconds ago
         //   default lifetime: (105 - 5) / 10 = 10 seconds
@@ -548,7 +583,7 @@
         assertNull(connection.getHeaderField("Warning"));
     }
 
-    public void testDefaultExpirationDateConditionallyCached() throws Exception {
+    @Test public void defaultExpirationDateConditionallyCached() throws Exception {
         //      last modified: 115 seconds ago
         //             served:  15 seconds ago
         //   default lifetime: (115 - 15) / 10 = 10 seconds
@@ -561,7 +596,7 @@
         assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
     }
 
-    public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
+    @Test public void defaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
         //      last modified: 105 days ago
         //             served:   5 days ago
         //   default lifetime: (105 - 5) / 10 = 10 days
@@ -579,7 +614,7 @@
                 connection.getHeaderField("Warning"));
     }
 
-    public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception {
+    @Test public void noDefaultExpirationForUrlsWithQueryString() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
                 .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
@@ -592,7 +627,7 @@
         assertEquals("B", readAscii(openConnection(url)));
     }
 
-    public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception {
+    @Test public void expirationDateInThePastWithLastModifiedHeader() throws Exception {
         String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
         RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
                 .addHeader("Last-Modified: " + lastModifiedDate)
@@ -601,24 +636,24 @@
         assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
     }
 
-    public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception {
+    @Test public void expirationDateInThePastWithNoLastModifiedHeader() throws Exception {
         assertNotCached(new MockResponse()
                 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
     }
 
-    public void testExpirationDateInTheFuture() throws Exception {
+    @Test public void expirationDateInTheFuture() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
     }
 
-    public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception {
+    @Test public void maxAgePreferredWithMaxAgeAndExpires() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
+    @Test public void maxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
         String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
         RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
                 .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
@@ -628,7 +663,7 @@
         assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
     }
 
-    public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
+    @Test public void maxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
         /*
          * Chrome interprets max-age relative to the local clock. Both our cache
          * and Firefox both use the earlier of the local and server's clock.
@@ -638,71 +673,71 @@
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testMaxAgeInTheFutureWithDateHeader() throws Exception {
+    @Test public void maxAgeInTheFutureWithDateHeader() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception {
+    @Test public void maxAgeInTheFutureWithNoDateHeader() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception {
+    @Test public void maxAgeWithLastModifiedButNoServedDate() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
+    @Test public void maxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
                 .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception {
+    @Test public void maxAgePreferredOverLowerSharedMaxAge() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
                 .addHeader("Cache-Control: s-maxage=60")
                 .addHeader("Cache-Control: max-age=180"));
     }
 
-    public void testMaxAgePreferredOverHigherMaxAge() throws Exception {
+    @Test public void maxAgePreferredOverHigherMaxAge() throws Exception {
         assertNotCached(new MockResponse()
                 .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
                 .addHeader("Cache-Control: s-maxage=180")
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testRequestMethodOptionsIsNotCached() throws Exception {
+    @Test public void requestMethodOptionsIsNotCached() throws Exception {
         testRequestMethod("OPTIONS", false);
     }
 
-    public void testRequestMethodGetIsCached() throws Exception {
+    @Test public void requestMethodGetIsCached() throws Exception {
         testRequestMethod("GET", true);
     }
 
-    public void testRequestMethodHeadIsNotCached() throws Exception {
+    @Test public void requestMethodHeadIsNotCached() throws Exception {
         // We could support this but choose not to for implementation simplicity
         testRequestMethod("HEAD", false);
     }
 
-    public void testRequestMethodPostIsNotCached() throws Exception {
+    @Test public void requestMethodPostIsNotCached() throws Exception {
         // We could support this but choose not to for implementation simplicity
         testRequestMethod("POST", false);
     }
 
-    public void testRequestMethodPutIsNotCached() throws Exception {
+    @Test public void requestMethodPutIsNotCached() throws Exception {
         testRequestMethod("PUT", false);
     }
 
-    public void testRequestMethodDeleteIsNotCached() throws Exception {
+    @Test public void requestMethodDeleteIsNotCached() throws Exception {
         testRequestMethod("DELETE", false);
     }
 
-    public void testRequestMethodTraceIsNotCached() throws Exception {
+    @Test public void requestMethodTraceIsNotCached() throws Exception {
         testRequestMethod("TRACE", false);
     }
 
@@ -720,7 +755,7 @@
 
         URL url = server.getUrl("/");
 
-        OkHttpConnection request1 = (OkHttpConnection) openConnection(url);
+        HttpURLConnection request1 = openConnection(url);
         request1.setRequestMethod(requestMethod);
         addRequestBodyIfNecessary(requestMethod, request1);
         assertEquals("1", request1.getHeaderField("X-Response-ID"));
@@ -733,15 +768,15 @@
         }
     }
 
-    public void testPostInvalidatesCache() throws Exception {
+    @Test public void postInvalidatesCache() throws Exception {
         testMethodInvalidates("POST");
     }
 
-    public void testPutInvalidatesCache() throws Exception {
+    @Test public void putInvalidatesCache() throws Exception {
         testMethodInvalidates("PUT");
     }
 
-    public void testDeleteMethodInvalidatesCache() throws Exception {
+    @Test public void deleteMethodInvalidatesCache() throws Exception {
         testMethodInvalidates("DELETE");
     }
 
@@ -761,7 +796,7 @@
 
         assertEquals("A", readAscii(openConnection(url)));
 
-        OkHttpConnection invalidate = openConnection(url);
+        HttpURLConnection invalidate = openConnection(url);
         invalidate.setRequestMethod(requestMethod);
         addRequestBodyIfNecessary(requestMethod, invalidate);
         assertEquals("B", readAscii(invalidate));
@@ -769,13 +804,13 @@
         assertEquals("C", readAscii(openConnection(url)));
     }
 
-    public void testEtag() throws Exception {
+    @Test public void etag() throws Exception {
         RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
                 .addHeader("ETag: v1"));
         assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1"));
     }
 
-    public void testEtagAndExpirationDateInThePast() throws Exception {
+    @Test public void etagAndExpirationDateInThePast() throws Exception {
         String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
         RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
                 .addHeader("ETag: v1")
@@ -786,18 +821,18 @@
         assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
     }
 
-    public void testEtagAndExpirationDateInTheFuture() throws Exception {
+    @Test public void etagAndExpirationDateInTheFuture() throws Exception {
         assertFullyCached(new MockResponse()
                 .addHeader("ETag: v1")
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
     }
 
-    public void testCacheControlNoCache() throws Exception {
+    @Test public void cacheControlNoCache() throws Exception {
         assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache"));
     }
 
-    public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
+    @Test public void cacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
         String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
         RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
                 .addHeader("Last-Modified: " + lastModifiedDate)
@@ -807,11 +842,11 @@
         assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
     }
 
-    public void testPragmaNoCache() throws Exception {
+    @Test public void pragmaNoCache() throws Exception {
         assertNotCached(new MockResponse().addHeader("Pragma: no-cache"));
     }
 
-    public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
+    @Test public void pragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
         String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
         RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
                 .addHeader("Last-Modified: " + lastModifiedDate)
@@ -821,18 +856,18 @@
         assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
     }
 
-    public void testCacheControlNoStore() throws Exception {
+    @Test public void cacheControlNoStore() throws Exception {
         assertNotCached(new MockResponse().addHeader("Cache-Control: no-store"));
     }
 
-    public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
+    @Test public void cacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
         assertNotCached(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
                 .addHeader("Cache-Control: no-store"));
     }
 
-    public void testPartialRangeResponsesDoNotCorruptCache() throws Exception {
+    @Test public void partialRangeResponsesDoNotCorruptCache() throws Exception {
         /*
          * 1. request a range
          * 2. request a full document, expecting a cache miss
@@ -853,7 +888,7 @@
         assertEquals("BB", readAscii(openConnection(url)));
     }
 
-    public void testServerReturnsDocumentOlderThanCache() throws Exception {
+    @Test public void serverReturnsDocumentOlderThanCache() throws Exception {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
@@ -867,13 +902,13 @@
         assertEquals("A", readAscii(openConnection(url)));
     }
 
-    public void testNonIdentityEncodingAndConditionalCache() throws Exception {
+    @Test public void nonIdentityEncodingAndConditionalCache() throws Exception {
         assertNonIdentityEncodingCached(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
     }
 
-    public void testNonIdentityEncodingAndFullCache() throws Exception {
+    @Test public void nonIdentityEncodingAndFullCache() throws Exception {
         assertNonIdentityEncodingCached(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
@@ -890,13 +925,13 @@
         assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/"))));
     }
 
-    public void testExpiresDateBeforeModifiedDate() throws Exception {
+    @Test public void expiresDateBeforeModifiedDate() throws Exception {
         assertConditionallyCached(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS)));
     }
 
-    public void testRequestMaxAge() throws IOException {
+    @Test public void requestMaxAge() throws IOException {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
                 .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))
@@ -911,7 +946,7 @@
         assertEquals("B", readAscii(connection));
     }
 
-    public void testRequestMinFresh() throws IOException {
+    @Test public void requestMinFresh() throws IOException {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
@@ -925,7 +960,7 @@
         assertEquals("B", readAscii(connection));
     }
 
-    public void testRequestMaxStale() throws IOException {
+    @Test public void requestMaxStale() throws IOException {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Cache-Control: max-age=120")
                 .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
@@ -941,7 +976,7 @@
                 connection.getHeaderField("Warning"));
     }
 
-    public void testRequestMaxStaleNotHonoredWithMustRevalidate() throws IOException {
+    @Test public void requestMaxStaleNotHonoredWithMustRevalidate() throws IOException {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Cache-Control: max-age=120, must-revalidate")
                 .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
@@ -955,16 +990,16 @@
         assertEquals("B", readAscii(connection));
     }
 
-    public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
+    @Test public void requestOnlyIfCachedWithNoResponseCached() throws IOException {
         // (no responses enqueued)
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = openConnection(server.getUrl("/"));
         connection.addRequestProperty("Cache-Control", "only-if-cached");
         assertGatewayTimeout(connection);
     }
 
-    public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException {
+    @Test public void requestOnlyIfCachedWithFullResponseCached() throws IOException {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Cache-Control: max-age=30")
                 .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
@@ -976,29 +1011,29 @@
         assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
     }
 
-    public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException {
+    @Test public void requestOnlyIfCachedWithConditionalResponseCached() throws IOException {
         server.enqueue(new MockResponse().setBody("A")
                 .addHeader("Cache-Control: max-age=30")
                 .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)));
         server.play();
 
         assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = openConnection(server.getUrl("/"));
         connection.addRequestProperty("Cache-Control", "only-if-cached");
         assertGatewayTimeout(connection);
     }
 
-    public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
+    @Test public void requestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
 
         assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = openConnection(server.getUrl("/"));
         connection.addRequestProperty("Cache-Control", "only-if-cached");
         assertGatewayTimeout(connection);
     }
 
-    public void testRequestCacheControlNoCache() throws Exception {
+    @Test public void requestCacheControlNoCache() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
                 .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
@@ -1014,7 +1049,7 @@
         assertEquals("B", readAscii(connection));
     }
 
-    public void testRequestPragmaNoCache() throws Exception {
+    @Test public void requestPragmaNoCache() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
                 .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
@@ -1030,7 +1065,7 @@
         assertEquals("B", readAscii(connection));
     }
 
-    public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
+    @Test public void clientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
         MockResponse response = new MockResponse()
                 .addHeader("ETag: v3")
                 .addHeader("Cache-Control: max-age=0");
@@ -1042,7 +1077,7 @@
         assertFalse(headers.contains("If-None-Match: v3"));
     }
 
-    public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
+    @Test public void clientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
         String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES);
         MockResponse response = new MockResponse()
                 .addHeader("Last-Modified: " + lastModifiedDate)
@@ -1064,7 +1099,7 @@
         URL url = server.getUrl("/");
         assertEquals("A", readAscii(openConnection(url)));
 
-        OkHttpConnection connection = openConnection(url);
+        HttpURLConnection connection = openConnection(url);
         connection.addRequestProperty(conditionName, conditionValue);
         assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
         assertEquals("", readAscii(connection));
@@ -1073,7 +1108,7 @@
         return server.takeRequest();
     }
 
-    public void testSetIfModifiedSince() throws Exception {
+    @Test public void setIfModifiedSince() throws Exception {
         Date since = new Date();
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
@@ -1086,19 +1121,19 @@
         assertTrue(request.getHeaders().contains("If-Modified-Since: " + formatDate(since)));
     }
 
-    public void testClientSuppliedConditionWithoutCachedResult() throws Exception {
+    @Test public void clientSuppliedConditionWithoutCachedResult() throws Exception {
         server.enqueue(new MockResponse()
                 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = openConnection(server.getUrl("/"));
         String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS);
         connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince);
         assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
         assertEquals("", readAscii(connection));
     }
 
-    public void testAuthorizationRequestHeaderPreventsCaching() throws Exception {
+    @Test public void authorizationRequestHeaderPreventsCaching() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES))
                 .addHeader("Cache-Control: max-age=60")
@@ -1113,17 +1148,17 @@
         assertEquals("B", readAscii(openConnection(url)));
     }
 
-    public void testAuthorizationResponseCachedWithSMaxAge() throws Exception {
+    @Test public void authorizationResponseCachedWithSMaxAge() throws Exception {
         assertAuthorizationRequestFullyCached(new MockResponse()
                 .addHeader("Cache-Control: s-maxage=60"));
     }
 
-    public void testAuthorizationResponseCachedWithPublic() throws Exception {
+    @Test public void authorizationResponseCachedWithPublic() throws Exception {
         assertAuthorizationRequestFullyCached(new MockResponse()
                 .addHeader("Cache-Control: public"));
     }
 
-    public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception {
+    @Test public void authorizationResponseCachedWithMustRevalidate() throws Exception {
         assertAuthorizationRequestFullyCached(new MockResponse()
                 .addHeader("Cache-Control: must-revalidate"));
     }
@@ -1142,7 +1177,7 @@
         assertEquals("A", readAscii(openConnection(url)));
     }
 
-    public void testContentLocationDoesNotPopulateCache() throws Exception {
+    @Test public void contentLocationDoesNotPopulateCache() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Content-Location: /bar")
@@ -1154,7 +1189,7 @@
         assertEquals("B", readAscii(openConnection(server.getUrl("/bar"))));
     }
 
-    public void testUseCachesFalseDoesNotWriteToCache() throws Exception {
+    @Test public void useCachesFalseDoesNotWriteToCache() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .setBody("A").setBody("A"));
@@ -1167,7 +1202,7 @@
         assertEquals("B", readAscii(openConnection(server.getUrl("/"))));
     }
 
-    public void testUseCachesFalseDoesNotReadFromCache() throws Exception {
+    @Test public void useCachesFalseDoesNotReadFromCache() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .setBody("A").setBody("A"));
@@ -1180,7 +1215,7 @@
         assertEquals("B", readAscii(connection));
     }
 
-    public void testDefaultUseCachesSetsInitialValueOnly() throws Exception {
+    @Test public void defaultUseCachesSetsInitialValueOnly() throws Exception {
         URL url = new URL("http://localhost/");
         URLConnection c1 = openConnection(url);
         URLConnection c2 = openConnection(url);
@@ -1196,7 +1231,7 @@
         }
     }
 
-    public void testConnectionIsReturnedToPoolAfterConditionalSuccess() throws Exception {
+    @Test public void connectionIsReturnedToPoolAfterConditionalSuccess() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Cache-Control: max-age=0")
@@ -1214,7 +1249,7 @@
         assertEquals(2, server.takeRequest().getSequenceNumber());
     }
 
-    public void testStatisticsConditionalCacheMiss() throws Exception {
+    @Test public void statisticsConditionalCacheMiss() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Cache-Control: max-age=0")
@@ -1234,7 +1269,7 @@
         assertEquals(0, cache.getHitCount());
     }
 
-    public void testStatisticsConditionalCacheHit() throws Exception {
+    @Test public void statisticsConditionalCacheHit() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Cache-Control: max-age=0")
@@ -1254,7 +1289,7 @@
         assertEquals(2, cache.getHitCount());
     }
 
-    public void testStatisticsFullCacheHit() throws Exception {
+    @Test public void statisticsFullCacheHit() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .setBody("A"));
@@ -1271,7 +1306,7 @@
         assertEquals(2, cache.getHitCount());
     }
 
-    public void testVaryMatchesChangedRequestHeaderField() throws Exception {
+    @Test public void varyMatchesChangedRequestHeaderField() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Accept-Language")
@@ -1280,16 +1315,16 @@
         server.play();
 
         URL url = server.getUrl("/");
-        OkHttpConnection frConnection = openConnection(url);
+        HttpURLConnection frConnection = openConnection(url);
         frConnection.addRequestProperty("Accept-Language", "fr-CA");
         assertEquals("A", readAscii(frConnection));
 
-        OkHttpConnection enConnection = openConnection(url);
+        HttpURLConnection enConnection = openConnection(url);
         enConnection.addRequestProperty("Accept-Language", "en-US");
         assertEquals("B", readAscii(enConnection));
     }
 
-    public void testVaryMatchesUnchangedRequestHeaderField() throws Exception {
+    @Test public void varyMatchesUnchangedRequestHeaderField() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Accept-Language")
@@ -1306,7 +1341,7 @@
         assertEquals("A", readAscii(connection2));
     }
 
-    public void testVaryMatchesAbsentRequestHeaderField() throws Exception {
+    @Test public void varyMatchesAbsentRequestHeaderField() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Foo")
@@ -1318,7 +1353,7 @@
         assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
     }
 
-    public void testVaryMatchesAddedRequestHeaderField() throws Exception {
+    @Test public void varyMatchesAddedRequestHeaderField() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Foo")
@@ -1332,7 +1367,7 @@
         assertEquals("B", readAscii(fooConnection));
     }
 
-    public void testVaryMatchesRemovedRequestHeaderField() throws Exception {
+    @Test public void varyMatchesRemovedRequestHeaderField() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Foo")
@@ -1346,7 +1381,7 @@
         assertEquals("B", readAscii(openConnection(server.getUrl("/"))));
     }
 
-    public void testVaryFieldsAreCaseInsensitive() throws Exception {
+    @Test public void varyFieldsAreCaseInsensitive() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: ACCEPT-LANGUAGE")
@@ -1363,7 +1398,7 @@
         assertEquals("A", readAscii(connection2));
     }
 
-    public void testVaryMultipleFieldsWithMatch() throws Exception {
+    @Test public void varyMultipleFieldsWithMatch() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Accept-Language, Accept-Charset")
@@ -1385,7 +1420,7 @@
         assertEquals("A", readAscii(connection2));
     }
 
-    public void testVaryMultipleFieldsWithNoMatch() throws Exception {
+    @Test public void varyMultipleFieldsWithNoMatch() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Accept-Language, Accept-Charset")
@@ -1407,7 +1442,7 @@
         assertEquals("B", readAscii(enConnection));
     }
 
-    public void testVaryMultipleFieldValuesWithMatch() throws Exception {
+    @Test public void varyMultipleFieldValuesWithMatch() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Accept-Language")
@@ -1427,7 +1462,7 @@
         assertEquals("A", readAscii(connection2));
     }
 
-    public void testVaryMultipleFieldValuesWithNoMatch() throws Exception {
+    @Test public void varyMultipleFieldValuesWithNoMatch() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: Accept-Language")
@@ -1447,7 +1482,7 @@
         assertEquals("B", readAscii(connection2));
     }
 
-    public void testVaryAsterisk() throws Exception {
+    @Test public void varyAsterisk() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Cache-Control: max-age=60")
                 .addHeader("Vary: *")
@@ -1459,57 +1494,30 @@
         assertEquals("B", readAscii(openConnection(server.getUrl("/"))));
     }
 
-//    public void testVaryAndHttps() throws Exception {
-//        TestSSLContext testSSLContext = TestSSLContext.create();
-//        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-//        server.enqueue(new MockResponse()
-//                .addHeader("Cache-Control: max-age=60")
-//                .addHeader("Vary: Accept-Language")
-//                .setBody("A"));
-//        server.enqueue(new MockResponse().setBody("B"));
-//        server.play();
-//
-//        URL url = server.getUrl("/");
-//        HttpsURLConnection connection1 = (HttpsURLConnection) url.openConnection();
-//        connection1.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        connection1.addRequestProperty("Accept-Language", "en-US");
-//        assertEquals("A", readAscii(connection1));
-//
-//        HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection();
-//        connection2.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        connection2.addRequestProperty("Accept-Language", "en-US");
-//        assertEquals("A", readAscii(connection2));
-//    }
-//
-//    public void testDiskWriteFailureCacheDegradation() throws Exception {
-//        Deque<InvocationHandler> writeHandlers = mockOs.getHandlers("write");
-//        int i = 0;
-//        boolean hasMoreScenarios = true;
-//        while (hasMoreScenarios) {
-//            mockOs.enqueueNormal("write", i++);
-//            mockOs.enqueueFault("write");
-//            exercisePossiblyFaultyCache(false);
-//            hasMoreScenarios = writeHandlers.isEmpty();
-//            writeHandlers.clear();
-//        }
-//        System.out.println("Exercising the cache performs " + (i - 1) + " writes.");
-//    }
-//
-//    public void testDiskReadFailureCacheDegradation() throws Exception {
-//        Deque<InvocationHandler> readHandlers = mockOs.getHandlers("read");
-//        int i = 0;
-//        boolean hasMoreScenarios = true;
-//        while (hasMoreScenarios) {
-//            mockOs.enqueueNormal("read", i++);
-//            mockOs.enqueueFault("read");
-//            exercisePossiblyFaultyCache(true);
-//            hasMoreScenarios = readHandlers.isEmpty();
-//            readHandlers.clear();
-//        }
-//        System.out.println("Exercising the cache performs " + (i - 1) + " reads.");
-//    }
+    @Test public void varyAndHttps() throws Exception {
+        server.useHttps(sslContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Accept-Language")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
 
-    public void testCachePlusCookies() throws Exception {
+        URL url = server.getUrl("/");
+        HttpsURLConnection connection1 = (HttpsURLConnection) client.open(url);
+        connection1.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        connection1.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection1));
+
+        HttpsURLConnection connection2 = (HttpsURLConnection) client.open(url);
+        connection2.setSSLSocketFactory(sslContext.getSocketFactory());
+        connection2.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+        connection2.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection2));
+    }
+
+    @Test public void cachePlusCookies() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Set-Cookie: a=FIRST; domain=" + server.getCookieDomain() + ";")
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
@@ -1527,7 +1535,7 @@
         assertCookies(url, "a=SECOND");
     }
 
-    public void testGetHeadersReturnsNetworkEndToEndHeaders() throws Exception {
+    @Test public void getHeadersReturnsNetworkEndToEndHeaders() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Allow: GET, HEAD")
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
@@ -1547,7 +1555,7 @@
         assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow"));
     }
 
-    public void testGetHeadersReturnsCachedHopByHopHeaders() throws Exception {
+    @Test public void getHeadersReturnsCachedHopByHopHeaders() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Transfer-Encoding: identity")
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
@@ -1567,7 +1575,7 @@
         assertEquals("identity", connection2.getHeaderField("Transfer-Encoding"));
     }
 
-    public void testGetHeadersDeletesCached100LevelWarnings() throws Exception {
+    @Test public void getHeadersDeletesCached100LevelWarnings() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Warning: 199 test danger")
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
@@ -1586,7 +1594,7 @@
         assertEquals(null, connection2.getHeaderField("Warning"));
     }
 
-    public void testGetHeadersRetainsCached200LevelWarnings() throws Exception {
+    @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Warning: 299 test danger")
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
@@ -1613,7 +1621,7 @@
         assertEquals(Arrays.asList(expectedCookies), actualCookies);
     }
 
-    public void testCachePlusRange() throws Exception {
+    @Test public void cachePlusRange() throws Exception {
         assertNotCached(new MockResponse()
                 .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
                 .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
@@ -1621,7 +1629,7 @@
                 .addHeader("Cache-Control: max-age=60"));
     }
 
-    public void testConditionalHitUpdatesCache() throws Exception {
+    @Test public void conditionalHitUpdatesCache() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(0, TimeUnit.SECONDS))
                 .addHeader("Cache-Control: max-age=0")
@@ -1634,18 +1642,18 @@
         server.play();
 
         // cache miss; seed the cache
-        OkHttpConnection connection1 = openConnection(server.getUrl("/a"));
+        HttpURLConnection connection1 = openConnection(server.getUrl("/a"));
         assertEquals("A", readAscii(connection1));
         assertEquals(null, connection1.getHeaderField("Allow"));
 
         // conditional cache hit; update the cache
-        OkHttpConnection connection2 = openConnection(server.getUrl("/a"));
+        HttpURLConnection connection2 = openConnection(server.getUrl("/a"));
         assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
         assertEquals("A", readAscii(connection2));
         assertEquals("GET, HEAD", connection2.getHeaderField("Allow"));
 
         // full cache hit
-        OkHttpConnection connection3 = openConnection(server.getUrl("/a"));
+        HttpURLConnection connection3 = openConnection(server.getUrl("/a"));
         assertEquals("A", readAscii(connection3));
         assertEquals("GET, HEAD", connection3.getHeaderField("Allow"));
 
@@ -1667,7 +1675,7 @@
         return rfc1123.format(date);
     }
 
-    private void addRequestBodyIfNecessary(String requestMethod, OkHttpConnection invalidate)
+    private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate)
             throws IOException {
         if (requestMethod.equals("POST") || requestMethod.equals("PUT")) {
             invalidate.setDoOutput(true);
@@ -1687,31 +1695,6 @@
         assertEquals("B", readAscii(openConnection(url)));
     }
 
-    private void exercisePossiblyFaultyCache(boolean permitReadBodyFailures) throws Exception {
-        server.shutdown();
-        server = new MockWebServer();
-        server.enqueue(new MockResponse()
-                .addHeader("Cache-Control: max-age=60")
-                .setBody("A"));
-        server.enqueue(new MockResponse().setBody("B"));
-        server.play();
-
-        URL url = server.getUrl("/" + UUID.randomUUID());
-        assertEquals("A", readAscii(openConnection(url)));
-
-        URLConnection connection = openConnection(url);
-        InputStream in = connection.getInputStream();
-        try {
-            int bodyChar = in.read();
-            assertTrue(bodyChar == 'A' || bodyChar == 'B');
-            assertEquals(-1, in.read());
-        } catch (IOException e) {
-            if (!permitReadBodyFailures) {
-                throw e;
-            }
-        }
-    }
-
     /**
      * @return the request with the conditional get headers.
      */
@@ -1727,21 +1710,21 @@
         server.play();
 
         URL valid = server.getUrl("/valid");
-        OkHttpConnection connection1 = openConnection(valid);
+        HttpURLConnection connection1 = openConnection(valid);
         assertEquals("A", readAscii(connection1));
         assertEquals(HttpURLConnection.HTTP_OK, connection1.getResponseCode());
         assertEquals("A-OK", connection1.getResponseMessage());
-        OkHttpConnection connection2 = openConnection(valid);
+        HttpURLConnection connection2 = openConnection(valid);
         assertEquals("A", readAscii(connection2));
         assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
         assertEquals("A-OK", connection2.getResponseMessage());
 
         URL invalid = server.getUrl("/invalid");
-        OkHttpConnection connection3 = openConnection(invalid);
+        HttpURLConnection connection3 = openConnection(invalid);
         assertEquals("B", readAscii(connection3));
         assertEquals(HttpURLConnection.HTTP_OK, connection3.getResponseCode());
         assertEquals("B-OK", connection3.getResponseMessage());
-        OkHttpConnection connection4 = openConnection(invalid);
+        HttpURLConnection connection4 = openConnection(invalid);
         assertEquals("C", readAscii(connection4));
         assertEquals(HttpURLConnection.HTTP_OK, connection4.getResponseCode());
         assertEquals("C-OK", connection4.getResponseMessage());
@@ -1780,7 +1763,7 @@
      * characters are returned and the stream is closed.
      */
     private String readAscii(URLConnection connection, int count) throws IOException {
-        OkHttpConnection httpConnection = (OkHttpConnection) connection;
+        HttpURLConnection httpConnection = (HttpURLConnection) connection;
         InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
                 ? connection.getInputStream()
                 : httpConnection.getErrorStream();
@@ -1806,7 +1789,7 @@
         }
     }
 
-    private void assertGatewayTimeout(OkHttpConnection connection) throws IOException {
+    private void assertGatewayTimeout(HttpURLConnection connection) throws IOException {
         try {
             connection.getInputStream();
             fail();
diff --git a/src/test/java/libcore/net/http/RawHeadersTest.java b/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
similarity index 88%
rename from src/test/java/libcore/net/http/RawHeadersTest.java
rename to src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
index 34f0985..377227f 100644
--- a/src/test/java/libcore/net/http/RawHeadersTest.java
+++ b/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java
@@ -13,14 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
+import com.squareup.okhttp.internal.http.RawHeaders;
 import java.util.Arrays;
 import java.util.List;
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
 
-public final class RawHeadersTest extends TestCase {
-    public void testParseNameValueBlock() {
+public final class RawHeadersTest {
+    @Test public void parseNameValueBlock() {
         List<String> nameValueBlock = Arrays.asList(
                 "cache-control",
                 "no-cache, no-store",
@@ -42,7 +44,7 @@
         assertEquals("200 OK", rawHeaders.getValue(3));
     }
 
-    public void testToNameValueBlock() {
+    @Test public void toNameValueBlock() {
         RawHeaders rawHeaders = new RawHeaders();
         rawHeaders.add("cache-control", "no-cache, no-store");
         rawHeaders.add("set-cookie", "Cookie1");
diff --git a/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java b/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java
new file mode 100644
index 0000000..14642e9
--- /dev/null
+++ b/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.http;
+
+import com.squareup.okhttp.Address;
+import com.squareup.okhttp.Connection;
+import com.squareup.okhttp.ConnectionPool;
+import com.squareup.okhttp.internal.Dns;
+import com.squareup.okhttp.internal.SslContextBuilder;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import static java.net.Proxy.NO_PROXY;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+public final class RouteSelectorTest {
+    private static final int proxyAPort = 1001;
+    private static final String proxyAHost = "proxyA";
+    private static final Proxy proxyA
+            = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAHost, proxyAPort));
+    private static final int proxyBPort = 1002;
+    private static final String proxyBHost = "proxyB";
+    private static final Proxy proxyB
+            = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyBHost, proxyBPort));
+    private static final URI uri;
+    private static final String uriHost = "hostA";
+    private static final int uriPort = 80;
+
+    private static final SSLContext sslContext;
+    private static final SSLSocketFactory socketFactory;
+    private static final HostnameVerifier hostnameVerifier;
+    private static final ConnectionPool pool;
+    static {
+        try {
+            uri = new URI("http://" + uriHost + ":" + uriPort + "/path");
+            sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build();
+            socketFactory = sslContext.getSocketFactory();
+            pool = ConnectionPool.getDefault();
+            hostnameVerifier = HttpsURLConnectionImpl.getDefaultHostnameVerifier();
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    private final FakeDns dns = new FakeDns();
+    private final FakeProxySelector proxySelector = new FakeProxySelector();
+
+    @Test public void singleRoute() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, null);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 1);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        dns.assertRequests(uriHost);
+
+        assertFalse(routeSelector.hasNext());
+        try {
+            routeSelector.next();
+            fail();
+        } catch (NoSuchElementException expected) {
+        }
+    }
+
+    @Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, proxyA);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 2);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[0], proxyAPort, false);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[1], proxyAPort, false);
+
+        assertFalse(routeSelector.hasNext());
+        dns.assertRequests(proxyAHost);
+        proxySelector.assertRequests(); // No proxy selector requests!
+    }
+
+    @Test public void explicitDirectProxy() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, NO_PROXY);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 2);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[1], uriPort, false);
+
+        assertFalse(routeSelector.hasNext());
+        dns.assertRequests(uri.getHost());
+        proxySelector.assertRequests(); // No proxy selector requests!
+    }
+
+    @Test public void proxySelectorReturnsNull() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, null);
+
+        proxySelector.proxies = null;
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+        proxySelector.assertRequests(uri);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 1);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        dns.assertRequests(uriHost);
+
+        assertFalse(routeSelector.hasNext());
+    }
+
+    @Test public void proxySelectorReturnsNoProxies() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, null);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 2);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[1], uriPort, false);
+
+        assertFalse(routeSelector.hasNext());
+        dns.assertRequests(uri.getHost());
+        proxySelector.assertRequests(uri);
+    }
+
+    @Test public void proxySelectorReturnsMultipleProxies() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, null);
+
+        proxySelector.proxies.add(proxyA);
+        proxySelector.proxies.add(proxyB);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+        proxySelector.assertRequests(uri);
+
+        // First try the IP addresses of the first proxy, in sequence.
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 2);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[0], proxyAPort, false);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[1], proxyAPort, false);
+        dns.assertRequests(proxyAHost);
+
+        // Next try the IP address of the second proxy.
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(254, 1);
+        assertConnection(routeSelector.next(),
+                address, proxyB, dns.inetAddresses[0], proxyBPort, false);
+        dns.assertRequests(proxyBHost);
+
+        // Finally try the only IP address of the origin server.
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(253, 1);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        dns.assertRequests(uriHost);
+
+        assertFalse(routeSelector.hasNext());
+    }
+
+    @Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, null);
+
+        proxySelector.proxies.add(NO_PROXY);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+        proxySelector.assertRequests(uri);
+
+        // Only the origin server will be attempted.
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 1);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        dns.assertRequests(uriHost);
+
+        assertFalse(routeSelector.hasNext());
+    }
+
+    @Test public void proxyDnsFailureContinuesToNextProxy() throws Exception {
+        Address address = new Address(uriHost, uriPort, null, null, null);
+
+        proxySelector.proxies.add(proxyA);
+        proxySelector.proxies.add(proxyB);
+        proxySelector.proxies.add(proxyA);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+        proxySelector.assertRequests(uri);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 1);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[0], proxyAPort, false);
+        dns.assertRequests(proxyAHost);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = null;
+        try {
+            routeSelector.next();
+            fail();
+        } catch (UnknownHostException expected) {
+        }
+        dns.assertRequests(proxyBHost);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 1);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[0], proxyAPort, false);
+        dns.assertRequests(proxyAHost);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(254, 1);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        dns.assertRequests(uriHost);
+
+        assertFalse(routeSelector.hasNext());
+    }
+
+    @Test public void multipleTlsModes() throws Exception {
+        Address address = new Address(
+                uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+
+        assertTrue(routeSelector.hasNext());
+        dns.inetAddresses = makeFakeAddresses(255, 1);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, true);
+        dns.assertRequests(uriHost);
+
+        assertTrue(routeSelector.hasNext());
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        dns.assertRequests(); // No more DNS requests since the previous!
+
+        assertFalse(routeSelector.hasNext());
+    }
+
+    @Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
+        Address address = new Address(
+                uriHost, uriPort, socketFactory, hostnameVerifier, null);
+        proxySelector.proxies.add(proxyA);
+        proxySelector.proxies.add(proxyB);
+        RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
+
+        // Proxy A
+        dns.inetAddresses = makeFakeAddresses(255, 2);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[0], proxyAPort, true);
+        dns.assertRequests(proxyAHost);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[0], proxyAPort, false);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[1], proxyAPort, true);
+        assertConnection(routeSelector.next(),
+                address, proxyA, dns.inetAddresses[1], proxyAPort, false);
+
+        // Proxy B
+        dns.inetAddresses = makeFakeAddresses(254, 2);
+        assertConnection(routeSelector.next(),
+                address, proxyB, dns.inetAddresses[0], proxyBPort, true);
+        dns.assertRequests(proxyBHost);
+        assertConnection(routeSelector.next(),
+                address, proxyB, dns.inetAddresses[0], proxyBPort, false);
+        assertConnection(routeSelector.next(),
+                address, proxyB, dns.inetAddresses[1], proxyBPort, true);
+        assertConnection(routeSelector.next(),
+                address, proxyB, dns.inetAddresses[1], proxyBPort, false);
+
+        // Origin
+        dns.inetAddresses = makeFakeAddresses(253, 2);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, true);
+        dns.assertRequests(uriHost);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[1], uriPort, true);
+        assertConnection(routeSelector.next(),
+                address, NO_PROXY, dns.inetAddresses[1], uriPort, false);
+
+        assertFalse(routeSelector.hasNext());
+    }
+
+    private void assertConnection(Connection connection, Address address,
+            Proxy proxy, InetAddress socketAddress, int socketPort, boolean modernTls) {
+        assertEquals(address, connection.getAddress());
+        assertEquals(proxy, connection.getProxy());
+        assertEquals(socketAddress, connection.getSocketAddress().getAddress());
+        assertEquals(socketPort, connection.getSocketAddress().getPort());
+        assertEquals(modernTls, connection.isModernTls());
+    }
+
+    private static InetAddress[] makeFakeAddresses(int prefix, int count) {
+        try {
+            InetAddress[] result = new InetAddress[count];
+            for (int i = 0; i < count; i++) {
+                result[i] = InetAddress.getByAddress(
+                        new byte[] { (byte) prefix, (byte) 0, (byte) 0, (byte) i });
+            }
+            return result;
+        } catch (UnknownHostException e) {
+            throw new AssertionError();
+        }
+    }
+
+    private static class FakeDns implements Dns {
+        List<String> requestedHosts = new ArrayList<String>();
+        InetAddress[] inetAddresses;
+
+        @Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
+            requestedHosts.add(host);
+            if (inetAddresses == null) throw new UnknownHostException();
+            return inetAddresses;
+        }
+
+        public void assertRequests(String... expectedHosts) {
+            assertEquals(Arrays.asList(expectedHosts), requestedHosts);
+            requestedHosts.clear();
+        }
+    }
+
+    private static class FakeProxySelector extends ProxySelector {
+        List<URI> requestedUris = new ArrayList<URI>();
+        List<Proxy> proxies = new ArrayList<Proxy>();
+        List<String> failures = new ArrayList<String>();
+
+        @Override public List<Proxy> select(URI uri) {
+            requestedUris.add(uri);
+            return proxies;
+        }
+
+        public void assertRequests(URI... expectedUris) {
+            assertEquals(Arrays.asList(expectedUris), requestedUris);
+            requestedUris.clear();
+        }
+
+        @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+            InetSocketAddress socketAddress = (InetSocketAddress) sa;
+            failures.add(String.format("%s %s:%d %s", uri, socketAddress.getHostName(),
+                    socketAddress.getPort(), ioe.getMessage()));
+        }
+    }
+}
diff --git a/src/test/java/libcore/net/http/URLConnectionTest.java b/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
similarity index 67%
rename from src/test/java/libcore/net/http/URLConnectionTest.java
rename to src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
index 53f521b..4d15189 100644
--- a/src/test/java/libcore/net/http/URLConnectionTest.java
+++ b/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
@@ -14,14 +14,19 @@
  * limitations under the License.
  */
 
-package libcore.net.http;
+package com.squareup.okhttp.internal.http;
 
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 import com.google.mockwebserver.RecordedRequest;
 import com.google.mockwebserver.SocketPolicy;
-import com.squareup.okhttp.OkHttpConnection;
-import com.squareup.okhttp.OkHttpsConnection;
+import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
+import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
+import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
+import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.internal.http.HttpResponseCache;
+import com.squareup.okhttp.internal.SslContextBuilder;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -37,10 +42,11 @@
 import java.net.PasswordAuthentication;
 import java.net.ProtocolException;
 import java.net.Proxy;
+import java.net.ProxySelector;
 import java.net.ResponseCache;
+import java.net.SocketAddress;
 import java.net.SocketTimeoutException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.UnknownHostException;
@@ -57,40 +63,44 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
-import junit.framework.TestCase;
-import libcore.net.ssl.SslContextBuilder;
-
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
-import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
-import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
+import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
 
 /**
  * Android's URLConnectionTest.
  */
-public final class URLConnectionTest extends TestCase {
+public final class URLConnectionTest {
     /** base64("username:password") */
     private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ=";
 
     private MockWebServer server = new MockWebServer();
+    private MockWebServer server2 = new MockWebServer();
+
+    private final OkHttpClient client = new OkHttpClient();
     private HttpResponseCache cache;
     private String hostName;
 
     private static final SSLContext sslContext;
-
     static {
         try {
-            sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName())
-                    .build();
+            sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build();
         } catch (GeneralSecurityException e) {
             throw new RuntimeException(e);
         } catch (UnknownHostException e) {
@@ -98,14 +108,11 @@
         }
     }
 
-    @Override protected void setUp() throws Exception {
-        super.setUp();
+    @Before public void setUp() throws Exception {
         hostName = server.getHostName();
     }
 
-    @Override protected void tearDown() throws Exception {
-        ResponseCache.setDefault(null);
-        Authenticator.setDefault(null);
+    @After public void tearDown() throws Exception {
         System.clearProperty("proxyHost");
         System.clearProperty("proxyPort");
         System.clearProperty("http.proxyHost");
@@ -113,25 +120,17 @@
         System.clearProperty("https.proxyHost");
         System.clearProperty("https.proxyPort");
         server.shutdown();
+        server2.shutdown();
         if (cache != null) {
             cache.getCache().delete();
         }
-        super.tearDown();
     }
 
-    private static OkHttpConnection openConnection(URL url) {
-        return OkHttpConnection.open(url);
-    }
-
-    private static OkHttpConnection openConnection(URL url, Proxy proxy) {
-        return OkHttpConnection.open(url, proxy);
-    }
-
-    public void testRequestHeaders() throws IOException, InterruptedException {
+    @Test public void requestHeaders() throws IOException, InterruptedException {
         server.enqueue(new MockResponse());
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         urlConnection.addRequestProperty("D", "e");
         urlConnection.addRequestProperty("D", "f");
         assertEquals("f", urlConnection.getRequestProperty("D"));
@@ -190,15 +189,15 @@
         }
     }
 
-    public void testGetRequestPropertyReturnsLastValue() throws Exception {
+    @Test public void getRequestPropertyReturnsLastValue() throws Exception {
         server.play();
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         urlConnection.addRequestProperty("A", "value1");
         urlConnection.addRequestProperty("A", "value2");
         assertEquals("value2", urlConnection.getRequestProperty("A"));
     }
 
-    public void testResponseHeaders() throws IOException, InterruptedException {
+    @Test public void responseHeaders() throws IOException, InterruptedException {
         server.enqueue(new MockResponse()
                 .setStatus("HTTP/1.0 200 Fantastic")
                 .addHeader("A: c")
@@ -207,7 +206,7 @@
                 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         assertEquals(200, urlConnection.getResponseCode());
         assertEquals("Fantastic", urlConnection.getResponseMessage());
         assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null));
@@ -233,11 +232,11 @@
         assertEquals("e", urlConnection.getHeaderField(2));
     }
 
-    public void testServerSendsInvalidResponseHeaders() throws Exception {
+    @Test public void serverSendsInvalidResponseHeaders() throws Exception {
         server.enqueue(new MockResponse().setStatus("HTP/1.1 200 OK"));
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         try {
             urlConnection.getResponseCode();
             fail();
@@ -245,11 +244,11 @@
         }
     }
 
-    public void testServerSendsInvalidCodeTooLarge() throws Exception {
+    @Test public void serverSendsInvalidCodeTooLarge() throws Exception {
         server.enqueue(new MockResponse().setStatus("HTTP/1.1 2147483648 OK"));
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         try {
             urlConnection.getResponseCode();
             fail();
@@ -257,11 +256,11 @@
         }
     }
 
-    public void testServerSendsInvalidCodeNotANumber() throws Exception {
+    @Test public void serverSendsInvalidCodeNotANumber() throws Exception {
         server.enqueue(new MockResponse().setStatus("HTTP/1.1 00a OK"));
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         try {
             urlConnection.getResponseCode();
             fail();
@@ -269,11 +268,11 @@
         }
     }
 
-    public void testServerSendsUnnecessaryWhitespace() throws Exception {
+    @Test public void serverSendsUnnecessaryWhitespace() throws Exception {
         server.enqueue(new MockResponse().setStatus(" HTTP/1.1 2147483648 OK"));
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         try {
             urlConnection.getResponseCode();
             fail();
@@ -281,42 +280,87 @@
         }
     }
 
-    public void testGetErrorStreamOnSuccessfulRequest() throws Exception {
+    @Test public void connectRetriesUntilConnectedOrFailed() throws Exception {
+        server.play();
+        URL url = server.getUrl("/foo");
+        server.shutdown();
+
+        HttpURLConnection connection = client.open(url);
+        try {
+            connection.connect();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    @Test public void requestBodySurvivesRetriesWithFixedLength() throws Exception {
+        testRequestBodySurvivesRetries(TransferKind.FIXED_LENGTH);
+    }
+
+    @Test public void requestBodySurvivesRetriesWithChunkedStreaming() throws Exception {
+        testRequestBodySurvivesRetries(TransferKind.CHUNKED);
+    }
+
+    @Test public void requestBodySurvivesRetriesWithBufferedBody() throws Exception {
+        testRequestBodySurvivesRetries(TransferKind.END_OF_STREAM);
+    }
+
+    private void testRequestBodySurvivesRetries(TransferKind transferKind) throws Exception {
+        server.enqueue(new MockResponse().setBody("abc"));
+        server.play();
+
+        // Use a misconfigured proxy to guarantee that the request is retried.
+        server2.play();
+        FakeProxySelector proxySelector = new FakeProxySelector();
+        proxySelector.proxies.add(server2.toProxyAddress());
+        client.setProxySelector(proxySelector);
+        server2.shutdown();
+
+        HttpURLConnection connection = client.open(server.getUrl("/def"));
+        connection.setDoOutput(true);
+        transferKind.setForRequest(connection, 4);
+        connection.getOutputStream().write("body".getBytes("UTF-8"));
+        assertContent("abc", connection);
+
+        assertEquals("body", server.takeRequest().getUtf8Body());
+    }
+
+    @Test public void getErrorStreamOnSuccessfulRequest() throws Exception {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertNull(connection.getErrorStream());
     }
 
-    public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception {
+    @Test public void getErrorStreamOnUnsuccessfulRequest() throws Exception {
         server.enqueue(new MockResponse().setResponseCode(404).setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE));
     }
 
     // Check that if we don't read to the end of a response, the next request on the
     // recycled connection doesn't get the unread tail of the first request's response.
     // http://code.google.com/p/android/issues/detail?id=2939
-    public void test_2939() throws Exception {
+    @Test public void bug2939() throws Exception {
         MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8);
 
         server.enqueue(response);
         server.enqueue(response);
         server.play();
 
-        assertContent("ABCDE", openConnection(server.getUrl("/")), 5);
-        assertContent("ABCDE", openConnection(server.getUrl("/")), 5);
+        assertContent("ABCDE", client.open(server.getUrl("/")), 5);
+        assertContent("ABCDE", client.open(server.getUrl("/")), 5);
     }
 
     // Check that we recognize a few basic mime types by extension.
     // http://code.google.com/p/android/issues/detail?id=10100
-    public void test_10100() throws Exception {
+    @Test public void bug10100() throws Exception {
         assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg"));
         assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf"));
     }
 
-    public void testConnectionsArePooled() throws Exception {
+    @Test public void connectionsArePooled() throws Exception {
         MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR");
 
         server.enqueue(response);
@@ -324,15 +368,15 @@
         server.enqueue(response);
         server.play();
 
-        assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/foo")));
+        assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo")));
         assertEquals(0, server.takeRequest().getSequenceNumber());
-        assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/bar?baz=quux")));
+        assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux")));
         assertEquals(1, server.takeRequest().getSequenceNumber());
-        assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/z")));
+        assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z")));
         assertEquals(2, server.takeRequest().getSequenceNumber());
     }
 
-    public void testChunkedConnectionsArePooled() throws Exception {
+    @Test public void chunkedConnectionsArePooled() throws Exception {
         MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5);
 
         server.enqueue(response);
@@ -340,23 +384,23 @@
         server.enqueue(response);
         server.play();
 
-        assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/foo")));
+        assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo")));
         assertEquals(0, server.takeRequest().getSequenceNumber());
-        assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/bar?baz=quux")));
+        assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux")));
         assertEquals(1, server.takeRequest().getSequenceNumber());
-        assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/z")));
+        assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z")));
         assertEquals(2, server.takeRequest().getSequenceNumber());
     }
 
-    public void testServerClosesSocket() throws Exception {
+    @Test public void serverClosesSocket() throws Exception {
         testServerClosesOutput(DISCONNECT_AT_END);
     }
 
-    public void testServerShutdownInput() throws Exception {
+    @Test public void serverShutdownInput() throws Exception {
         testServerClosesOutput(SHUTDOWN_INPUT_AT_END);
     }
 
-    public void SUPPRESSED_testServerShutdownOutput() throws Exception {
+    @Test public void serverShutdownOutput() throws Exception {
         testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END);
     }
 
@@ -364,40 +408,52 @@
         server.enqueue(new MockResponse()
                 .setBody("This connection won't pool properly")
                 .setSocketPolicy(socketPolicy));
-        server.enqueue(new MockResponse()
-                .setBody("This comes after a busted connection"));
+        MockResponse responseAfter = new MockResponse()
+                .setBody("This comes after a busted connection");
+        server.enqueue(responseAfter);
+        server.enqueue(responseAfter); // Enqueue 2x because the broken connection may be reused.
         server.play();
 
-        assertContent("This connection won't pool properly", openConnection(server.getUrl("/a")));
+        HttpURLConnection connection1 = client.open(server.getUrl("/a"));
+        connection1.setReadTimeout(100);
+        assertContent("This connection won't pool properly", connection1);
         assertEquals(0, server.takeRequest().getSequenceNumber());
-        assertContent("This comes after a busted connection", openConnection(server.getUrl("/b")));
+        HttpURLConnection connection2 = client.open(server.getUrl("/b"));
+        connection2.setReadTimeout(100);
+        assertContent("This comes after a busted connection", connection2);
+
+        // Check that a fresh connection was created, either immediately or after attempting reuse.
+        RecordedRequest requestAfter = server.takeRequest();
+        if (server.getRequestCount() == 3) {
+            requestAfter = server.takeRequest(); // The failure consumed a response.
+        }
         // sequence number 0 means the HTTP socket connection was not reused
-        assertEquals(0, server.takeRequest().getSequenceNumber());
+        assertEquals(0, requestAfter.getSequenceNumber());
     }
 
     enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
 
-    public void test_chunkedUpload_byteByByte() throws Exception {
+    @Test public void chunkedUpload_byteByByte() throws Exception {
         doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
     }
 
-    public void test_chunkedUpload_smallBuffers() throws Exception {
+    @Test public void chunkedUpload_smallBuffers() throws Exception {
         doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
     }
 
-    public void test_chunkedUpload_largeBuffers() throws Exception {
+    @Test public void chunkedUpload_largeBuffers() throws Exception {
         doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
     }
 
-    public void SUPPRESSED_test_fixedLengthUpload_byteByByte() throws Exception {
+    @Test public void fixedLengthUpload_byteByByte() throws Exception {
         doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
     }
 
-    public void test_fixedLengthUpload_smallBuffers() throws Exception {
+    @Test public void fixedLengthUpload_smallBuffers() throws Exception {
         doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
     }
 
-    public void test_fixedLengthUpload_largeBuffers() throws Exception {
+    @Test public void fixedLengthUpload_largeBuffers() throws Exception {
         doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
     }
 
@@ -407,7 +463,7 @@
         server.enqueue(new MockResponse());
         server.play();
 
-        OkHttpConnection conn = openConnection(server.getUrl("/"));
+        HttpURLConnection conn = client.open(server.getUrl("/"));
         conn.setDoOutput(true);
         conn.setRequestMethod("POST");
         if (uploadKind == TransferKind.CHUNKED) {
@@ -438,13 +494,13 @@
         }
     }
 
-    public void testGetResponseCodeNoResponseBody() throws Exception {
+    @Test public void getResponseCodeNoResponseBody() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("abc: def"));
         server.play();
 
         URL url = server.getUrl("/");
-        OkHttpConnection conn = openConnection(url);
+        HttpURLConnection conn = client.open(url);
         conn.setDoInput(false);
         assertEquals("def", conn.getHeaderField("abc"));
         assertEquals(200, conn.getResponseCode());
@@ -455,14 +511,14 @@
         }
     }
 
-    public void testConnectViaHttps() throws Exception {
+    @Test public void connectViaHttps() throws Exception {
         server.useHttps(sslContext.getSocketFactory(), false);
         server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
         server.play();
 
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/foo"));
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(server.getUrl("/foo"));
 
         assertContent("this response comes via HTTPS", connection);
 
@@ -470,7 +526,7 @@
         assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
     }
 
-    public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
+    @Test public void connectViaHttpsReusingConnections() throws IOException, InterruptedException {
         server.useHttps(sslContext.getSocketFactory(), false);
         server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
         server.enqueue(new MockResponse().setBody("another response via HTTPS"));
@@ -480,21 +536,19 @@
         SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory();
         RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
 
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
-        connection.setSSLSocketFactory(clientSocketFactory);
-        connection.setHostnameVerifier(hostnameVerifier);
+        client.setSSLSocketFactory(clientSocketFactory);
+        client.setHostnameVerifier(hostnameVerifier);
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertContent("this response comes via HTTPS", connection);
 
-        connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
-        connection.setSSLSocketFactory(clientSocketFactory);
-        connection.setHostnameVerifier(hostnameVerifier);
+        connection = client.open(server.getUrl("/"));
         assertContent("another response via HTTPS", connection);
 
         assertEquals(0, server.takeRequest().getSequenceNumber());
         assertEquals(1, server.takeRequest().getSequenceNumber());
     }
 
-    public void testConnectViaHttpsReusingConnectionsDifferentFactories()
+    @Test public void connectViaHttpsReusingConnectionsDifferentFactories()
             throws IOException, InterruptedException {
         server.useHttps(sslContext.getSocketFactory(), false);
         server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
@@ -502,28 +556,29 @@
         server.play();
 
         // install a custom SSL socket factory so the server can be authorized
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
-        assertContent("this response comes via HTTPS", connection);
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection1 = client.open(server.getUrl("/"));
+        assertContent("this response comes via HTTPS", connection1);
 
-        connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
+        client.setSSLSocketFactory(null);
+        HttpURLConnection connection2 = client.open(server.getUrl("/"));
         try {
-            readAscii(connection.getInputStream(), Integer.MAX_VALUE);
+            readAscii(connection2.getInputStream(), Integer.MAX_VALUE);
             fail("without an SSL socket factory, the connection should fail");
         } catch (SSLException expected) {
         }
     }
 
-    public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException {
+    @Test public void connectViaHttpsWithSSLFallback() throws IOException, InterruptedException {
         server.useHttps(sslContext.getSocketFactory(), false);
         server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
         server.enqueue(new MockResponse().setBody("this response comes via SSL"));
         server.play();
 
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/foo"));
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(server.getUrl("/foo"));
 
         assertContent("this response comes via SSL", connection);
 
@@ -536,34 +591,30 @@
      *
      * http://code.google.com/p/android/issues/detail?id=13178
      */
-//    public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
-//        TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(),
-//                                                              TestKeyStore.getServer());
-//
-//        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-//        server.enqueue(new MockResponse()); // unused
-//        server.play();
-//
-//        HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
-//        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-//        try {
-//            connection.getInputStream();
-//            fail();
-//        } catch (SSLHandshakeException expected) {
-//            assertTrue(expected.getCause() instanceof CertificateException);
-//        }
-//        assertEquals(0, server.getRequestCount());
-//    }
+    @Test public void connectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
+        server.useHttps(sslContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse()); // unused
+        server.play();
 
-    public void testConnectViaProxyUsingProxyArg() throws Exception {
+        HttpURLConnection connection = client.open(server.getUrl("/foo"));
+        try {
+            connection.getInputStream();
+            fail();
+        } catch (SSLHandshakeException expected) {
+            assertTrue(expected.getCause() instanceof CertificateException);
+        }
+        assertEquals(0, server.getRequestCount());
+    }
+
+    @Test public void connectViaProxyUsingProxyArg() throws Exception {
         testConnectViaProxy(ProxyConfig.CREATE_ARG);
     }
 
-    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
+    @Test public void connectViaProxyUsingProxySystemProperty() throws Exception {
         testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
     }
 
-    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
+    @Test public void connectViaProxyUsingHttpProxySystemProperty() throws Exception {
         testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
     }
 
@@ -573,7 +624,7 @@
         server.play();
 
         URL url = new URL("http://android.com/foo");
-        OkHttpConnection connection = proxyConfig.connect(server, url);
+        HttpURLConnection connection = proxyConfig.connect(server, client, url);
         assertContent("this response comes via a proxy", connection);
 
         RecordedRequest request = server.takeRequest();
@@ -581,17 +632,17 @@
         assertContains(request.getHeaders(), "Host: android.com");
     }
 
-    public void testContentDisagreesWithContentLengthHeader() throws IOException {
+    @Test public void contentDisagreesWithContentLengthHeader() throws IOException {
         server.enqueue(new MockResponse()
                 .setBody("abc\r\nYOU SHOULD NOT SEE THIS")
                 .clearHeaders()
                 .addHeader("Content-Length: 3"));
         server.play();
 
-        assertContent("abc", openConnection(server.getUrl("/")));
+        assertContent("abc", client.open(server.getUrl("/")));
     }
 
-    public void testContentDisagreesWithChunkedHeader() throws IOException {
+    @Test public void contentDisagreesWithChunkedHeader() throws IOException {
         MockResponse mockResponse = new MockResponse();
         mockResponse.setChunkedBody("abc", 3);
         ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
@@ -604,14 +655,14 @@
         server.enqueue(mockResponse);
         server.play();
 
-        assertContent("abc", openConnection(server.getUrl("/")));
+        assertContent("abc", client.open(server.getUrl("/")));
     }
 
-    public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
+    @Test public void connectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
         testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
     }
 
-    public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
+    @Test public void connectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
         // https should not use http proxy
         testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
     }
@@ -622,9 +673,9 @@
         server.play();
 
         URL url = server.getUrl("/foo");
-        OkHttpsConnection connection = (OkHttpsConnection) proxyConfig.connect(server, url);
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = proxyConfig.connect(server, client, url);
 
         assertContent("this response comes via HTTPS", connection);
 
@@ -632,7 +683,7 @@
         assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
     }
 
-    public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
+    @Test public void connectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
         testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
     }
 
@@ -640,11 +691,11 @@
      * We weren't honoring all of the appropriate proxy system properties when
      * connecting via HTTPS. http://b/3097518
      */
-    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
+    @Test public void connectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
         testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
     }
 
-    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
+    @Test public void connectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
         testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
     }
 
@@ -663,9 +714,9 @@
         server.play();
 
         URL url = new URL("https://android.com/foo");
-        OkHttpsConnection connection = (OkHttpsConnection) proxyConfig.connect(server, url);
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(hostnameVerifier);
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(hostnameVerifier);
+        HttpURLConnection connection = proxyConfig.connect(server, client, url);
 
         assertContent("this response comes via a secure proxy", connection);
 
@@ -683,25 +734,31 @@
     /**
      * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912
      */
-    public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception {
-        ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY;
-
+    @Test public void connectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache()
+            throws Exception {
         initResponseCache();
 
         server.useHttps(sslContext.getSocketFactory(), true);
         MockResponse response = new MockResponse() // Key to reproducing b/6754912
                 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
                 .setBody("bogus proxy connect response content");
-        server.enqueue(response); // For the first TLS tolerant connection
-        server.enqueue(response); // For the backwards-compatible SSLv3 retry
+
+        // Enqueue a pair of responses for every IP address held by localhost, because the
+        // route selector will try each in sequence.
+        // TODO: use the fake Dns implementation instead of a loop
+        for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) {
+            server.enqueue(response); // For the first TLS tolerant connection
+            server.enqueue(response); // For the backwards-compatible SSLv3 retry
+        }
         server.play();
+        client.setProxy(server.toProxyAddress());
 
         URL url = new URL("https://android.com/foo");
-        OkHttpsConnection connection = (OkHttpsConnection) proxyConfig.connect(server, url);
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        HttpURLConnection connection = client.open(url);
 
         try {
-            connection.connect();
+            connection.getResponseCode();
             fail();
         } catch (IOException expected) {
             // Thrown when the connect causes SSLSocket.startHandshake() to throw
@@ -719,13 +776,13 @@
         String tmp = System.getProperty("java.io.tmpdir");
         File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
         cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
-        ResponseCache.setDefault(cache);
+        client.setResponseCache(cache);
     }
 
     /**
      * Test which headers are sent unencrypted to the HTTP proxy.
      */
-    public void testProxyConnectIncludesProxyHeadersOnly()
+    @Test public void proxyConnectIncludesProxyHeadersOnly()
             throws IOException, InterruptedException {
         RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
 
@@ -735,15 +792,15 @@
                 .clearHeaders());
         server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
         server.play();
+        client.setProxy(server.toProxyAddress());
 
         URL url = new URL("https://android.com/foo");
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(
-                url, server.toProxyAddress());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(hostnameVerifier);
+        HttpURLConnection connection = client.open(url);
         connection.addRequestProperty("Private", "Secret");
         connection.addRequestProperty("Proxy-Authorization", "bar");
         connection.addRequestProperty("User-Agent", "baz");
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(hostnameVerifier);
         assertContent("encrypted response from the origin server", connection);
 
         RecordedRequest connect = server.takeRequest();
@@ -758,7 +815,7 @@
         assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
     }
 
-    public void testProxyAuthenticateOnConnect() throws Exception {
+    @Test public void proxyAuthenticateOnConnect() throws Exception {
         Authenticator.setDefault(new RecordingAuthenticator());
         server.useHttps(sslContext.getSocketFactory(), true);
         server.enqueue(new MockResponse()
@@ -769,12 +826,12 @@
                 .clearHeaders());
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
+        client.setProxy(server.toProxyAddress());
 
         URL url = new URL("https://android.com/foo");
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(
-                url, server.toProxyAddress());
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(url);
         assertContent("A", connection);
 
         RecordedRequest connect1 = server.takeRequest();
@@ -792,25 +849,25 @@
 
     // Don't disconnect after building a tunnel with CONNECT
     // http://code.google.com/p/android/issues/detail?id=37221
-    public void testProxyWithConnectionClose() throws IOException {
+    @Test public void proxyWithConnectionClose() throws IOException {
         server.useHttps(sslContext.getSocketFactory(), true);
         server.enqueue(new MockResponse()
                 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
                 .clearHeaders());
         server.enqueue(new MockResponse().setBody("this response comes via a proxy"));
         server.play();
+        client.setProxy(server.toProxyAddress());
 
         URL url = new URL("https://android.com/foo");
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(
-                url, server.toProxyAddress());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(url);
         connection.setRequestProperty("Connection", "close");
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
 
         assertContent("this response comes via a proxy", connection);
     }
 
-    public void testProxyWithConnectionReuse() throws IOException {
+    @Test public void proxyWithConnectionReuse() throws IOException {
         SSLSocketFactory socketFactory = sslContext.getSocketFactory();
         RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
 
@@ -821,26 +878,20 @@
         server.enqueue(new MockResponse().setBody("response 1"));
         server.enqueue(new MockResponse().setBody("response 2"));
         server.play();
+        client.setProxy(server.toProxyAddress());
 
         URL url = new URL("https://android.com/foo");
-        OkHttpsConnection connection1 = (OkHttpsConnection) openConnection(
-                url, server.toProxyAddress());
-        connection1.setSSLSocketFactory(socketFactory);
-        connection1.setHostnameVerifier(hostnameVerifier);
-        assertContent("response 1", connection1);
-
-        OkHttpsConnection connection2 = (OkHttpsConnection) openConnection(
-                url, server.toProxyAddress());
-        connection2.setSSLSocketFactory(socketFactory);
-        connection2.setHostnameVerifier(hostnameVerifier);
-        assertContent("response 2", connection2);
+        client.setSSLSocketFactory(socketFactory);
+        client.setHostnameVerifier(hostnameVerifier);
+        assertContent("response 1", client.open(url));
+        assertContent("response 2", client.open(url));
     }
 
-    public void testDisconnectedConnection() throws IOException {
+    @Test public void disconnectedConnection() throws IOException {
         server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         InputStream in = connection.getInputStream();
         assertEquals('A', (char) in.read());
         connection.disconnect();
@@ -851,62 +902,19 @@
         }
     }
 
-    public void testDisconnectBeforeConnect() throws IOException {
+    @Test public void disconnectBeforeConnect() throws IOException {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.disconnect();
 
         assertContent("A", connection);
         assertEquals(200, connection.getResponseCode());
     }
 
-//    public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException {
-//        CloseGuardGuard guard = new CloseGuardGuard();
-//        try {
-//            server.enqueue(new MockResponse()
-//                    .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
-//                    .addHeader("Content-Encoding: gzip"));
-//            server.play();
-//
-//            HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
-//            assertEquals(200, connection.getResponseCode());
-//            connection.disconnect();
-//            connection = null;
-//            assertFalse(guard.wasCloseGuardCalled());
-//        } finally {
-//            guard.close();
-//        }
-//    }
-//
-//    public static class CloseGuardGuard implements Closeable, CloseGuard.Reporter  {
-//        private final CloseGuard.Reporter oldReporter = CloseGuard.getReporter();
-//
-//        private AtomicBoolean closeGuardCalled = new AtomicBoolean();
-//
-//        public CloseGuardGuard() {
-//            CloseGuard.setReporter(this);
-//        }
-//
-//        @Override public void report(String message, Throwable allocationSite) {
-//            oldReporter.report(message, allocationSite);
-//            closeGuardCalled.set(true);
-//        }
-//
-//        public boolean wasCloseGuardCalled() {
-//            // FinalizationTester.induceFinalization();
-//            close();
-//            return closeGuardCalled.get();
-//        }
-//
-//        @Override public void close() {
-//            CloseGuard.setReporter(oldReporter);
-//        }
-//
-//    }
-
-    public void testDefaultRequestProperty() throws Exception {
+    @SuppressWarnings("deprecation")
+    @Test public void defaultRequestProperty() throws Exception {
         URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
         assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
     }
@@ -929,15 +937,15 @@
         return result.toString();
     }
 
-    public void testMarkAndResetWithContentLengthHeader() throws IOException {
+    @Test public void markAndResetWithContentLengthHeader() throws IOException {
         testMarkAndReset(TransferKind.FIXED_LENGTH);
     }
 
-    public void testMarkAndResetWithChunkedEncoding() throws IOException {
+    @Test public void markAndResetWithChunkedEncoding() throws IOException {
         testMarkAndReset(TransferKind.CHUNKED);
     }
 
-    public void testMarkAndResetWithNoLengthHeaders() throws IOException {
+    @Test public void markAndResetWithNoLengthHeaders() throws IOException {
         testMarkAndReset(TransferKind.END_OF_STREAM);
     }
 
@@ -948,7 +956,7 @@
         server.enqueue(response);
         server.play();
 
-        InputStream in = openConnection(server.getUrl("/")).getInputStream();
+        InputStream in = client.open(server.getUrl("/")).getInputStream();
         assertFalse("This implementation claims to support mark().", in.markSupported());
         in.mark(5);
         assertEquals("ABCDE", readAscii(in, 5));
@@ -958,7 +966,7 @@
         } catch (IOException expected) {
         }
         assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
-        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", openConnection(server.getUrl("/")));
+        assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", client.open(server.getUrl("/")));
     }
 
     /**
@@ -966,7 +974,7 @@
      * code 401. This causes a new HTTP request to be issued for every call into
      * the URLConnection.
      */
-    public void SUPPRESSED_testUnauthorizedResponseHandling() throws IOException {
+    @Test public void unauthorizedResponseHandling() throws IOException {
         MockResponse response = new MockResponse()
                 .addHeader("WWW-Authenticate: challenge")
                 .setResponseCode(401) // UNAUTHORIZED
@@ -977,7 +985,7 @@
         server.play();
 
         URL url = server.getUrl("/");
-        OkHttpConnection conn = openConnection(url);
+        HttpURLConnection conn = client.open(url);
 
         assertEquals(401, conn.getResponseCode());
         assertEquals(401, conn.getResponseCode());
@@ -985,14 +993,14 @@
         assertEquals(1, server.getRequestCount());
     }
 
-    public void testNonHexChunkSize() throws IOException {
+    @Test public void nonHexChunkSize() throws IOException {
         server.enqueue(new MockResponse()
                 .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
                 .clearHeaders()
                 .addHeader("Transfer-encoding: chunked"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         try {
             readAscii(connection.getInputStream(), Integer.MAX_VALUE);
             fail();
@@ -1000,7 +1008,7 @@
         }
     }
 
-    public void testMissingChunkBody() throws IOException {
+    @Test public void missingChunkBody() throws IOException {
         server.enqueue(new MockResponse()
                 .setBody("5")
                 .clearHeaders()
@@ -1008,7 +1016,7 @@
                 .setSocketPolicy(DISCONNECT_AT_END));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         try {
             readAscii(connection.getInputStream(), Integer.MAX_VALUE);
             fail();
@@ -1021,13 +1029,13 @@
      * behavior in not required by the API, so a failure of this test does not
      * imply a bug in the implementation.
      */
-    public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException {
+    @Test public void gzipEncodingEnabledByDefault() throws IOException, InterruptedException {
         server.enqueue(new MockResponse()
                 .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
                 .addHeader("Content-Encoding: gzip"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
         assertNull(connection.getContentEncoding());
 
@@ -1035,13 +1043,13 @@
         assertContains(request.getHeaders(), "Accept-Encoding: gzip");
     }
 
-    public void testClientConfiguredGzipContentEncoding() throws Exception {
+    @Test public void clientConfiguredGzipContentEncoding() throws Exception {
         server.enqueue(new MockResponse()
                 .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")))
                 .addHeader("Content-Encoding: gzip"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         connection.addRequestProperty("Accept-Encoding", "gzip");
         InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
         assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
@@ -1050,29 +1058,29 @@
         assertContains(request.getHeaders(), "Accept-Encoding: gzip");
     }
 
-    public void testGzipAndConnectionReuseWithFixedLength() throws Exception {
+    @Test public void gzipAndConnectionReuseWithFixedLength() throws Exception {
         testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false);
     }
 
-    public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception {
+    @Test public void gzipAndConnectionReuseWithChunkedEncoding() throws Exception {
         testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false);
     }
 
-    public void testGzipAndConnectionReuseWithFixedLengthAndTls() throws Exception {
+    @Test public void gzipAndConnectionReuseWithFixedLengthAndTls() throws Exception {
         testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true);
     }
 
-    public void testGzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception {
+    @Test public void gzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception {
         testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true);
     }
 
-    public void testClientConfiguredCustomContentEncoding() throws Exception {
+    @Test public void clientConfiguredCustomContentEncoding() throws Exception {
         server.enqueue(new MockResponse()
                 .setBody("ABCDE")
                 .addHeader("Content-Encoding: custom"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         connection.addRequestProperty("Accept-Encoding", "custom");
         assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
 
@@ -1088,12 +1096,12 @@
      */
     private void testClientConfiguredGzipContentEncodingAndConnectionReuse(
             TransferKind transferKind, boolean tls) throws Exception {
-        SSLSocketFactory socketFactory = null;
-        RecordingHostnameVerifier hostnameVerifier = null;
         if (tls) {
-            socketFactory = sslContext.getSocketFactory();
-            hostnameVerifier = new RecordingHostnameVerifier();
+            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+            RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
             server.useHttps(socketFactory, false);
+            client.setSSLSocketFactory(socketFactory);
+            client.setHostnameVerifier(hostnameVerifier);
         }
 
         MockResponse responseOne = new MockResponse();
@@ -1105,36 +1113,61 @@
         server.enqueue(responseTwo);
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
-        if (tls) {
-            ((OkHttpsConnection) connection).setSSLSocketFactory(socketFactory);
-            ((OkHttpsConnection) connection).setHostnameVerifier(hostnameVerifier);
-        }
-        connection.addRequestProperty("Accept-Encoding", "gzip");
-        InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
+        HttpURLConnection connection1 = client.open(server.getUrl("/"));
+        connection1.addRequestProperty("Accept-Encoding", "gzip");
+        InputStream gunzippedIn = new GZIPInputStream(connection1.getInputStream());
         assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
         assertEquals(0, server.takeRequest().getSequenceNumber());
 
-        connection = openConnection(server.getUrl("/"));
-        if (tls) {
-            ((OkHttpsConnection) connection).setSSLSocketFactory(socketFactory);
-            ((OkHttpsConnection) connection).setHostnameVerifier(hostnameVerifier);
-        }
-        assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+        HttpURLConnection connection2 = client.open(server.getUrl("/"));
+        assertEquals("two (identity)", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
         assertEquals(1, server.takeRequest().getSequenceNumber());
     }
 
+    @Test public void earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() throws Exception {
+        testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED);
+    }
+
+    @Test public void earlyDisconnectDoesntHarmPoolingWithFixedLengthEncoding() throws Exception {
+        testEarlyDisconnectDoesntHarmPooling(TransferKind.FIXED_LENGTH);
+    }
+
+    private void testEarlyDisconnectDoesntHarmPooling(TransferKind transferKind) throws Exception {
+        MockResponse response1 = new MockResponse();
+        transferKind.setBody(response1, "ABCDEFGHIJK", 1024);
+        server.enqueue(response1);
+
+        MockResponse response2 = new MockResponse();
+        transferKind.setBody(response2, "LMNOPQRSTUV", 1024);
+        server.enqueue(response2);
+
+        server.play();
+
+        URLConnection connection1 = client.open(server.getUrl("/"));
+        InputStream in1 = connection1.getInputStream();
+        assertEquals("ABCDE", readAscii(in1, 5));
+        in1.close();
+
+        HttpURLConnection connection2 = client.open(server.getUrl("/"));
+        InputStream in2 = connection2.getInputStream();
+        assertEquals("LMNOP", readAscii(in2, 5));
+        in2.close();
+
+        assertEquals(0, server.takeRequest().getSequenceNumber());
+        assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection is pooled!
+    }
+
     /**
      * Obnoxiously test that the chunk sizes transmitted exactly equal the
      * requested data+chunk header size. Although setChunkedStreamingMode()
      * isn't specific about whether the size applies to the data or the
      * complete chunk, the RI interprets it as a complete chunk.
      */
-    public void testSetChunkedStreamingMode() throws IOException, InterruptedException {
+    @Test public void setChunkedStreamingMode() throws IOException, InterruptedException {
         server.enqueue(new MockResponse());
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         urlConnection.setChunkedStreamingMode(8);
         urlConnection.setDoOutput(true);
         OutputStream outputStream = urlConnection.getOutputStream();
@@ -1146,11 +1179,11 @@
         assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes());
     }
 
-    public void testAuthenticateWithFixedLengthStreaming() throws Exception {
+    @Test public void authenticateWithFixedLengthStreaming() throws Exception {
         testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
     }
 
-    public void testAuthenticateWithChunkedStreaming() throws Exception {
+    @Test public void authenticateWithChunkedStreaming() throws Exception {
         testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
     }
 
@@ -1163,7 +1196,7 @@
         server.play();
 
         Authenticator.setDefault(new RecordingAuthenticator());
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setDoOutput(true);
         byte[] requestBody = { 'A', 'B', 'C', 'D' };
         if (streamingMode == StreamingMode.FIXED_LENGTH) {
@@ -1186,12 +1219,12 @@
         assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
     }
 
-    public void testNonStandardAuthenticationScheme() throws Exception {
+    @Test public void nonStandardAuthenticationScheme() throws Exception {
         List<String> calls = authCallsForHeader("WWW-Authenticate: Foo");
         assertEquals(Collections.<String>emptyList(), calls);
     }
 
-    public void testNonStandardAuthenticationSchemeWithRealm() throws Exception {
+    @Test public void nonStandardAuthenticationSchemeWithRealm() throws Exception {
         List<String> calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\"");
         assertEquals(1, calls.size());
         String call = calls.get(0);
@@ -1201,7 +1234,7 @@
 
     // Digest auth is currently unsupported. Test that digest requests should fail reasonably.
     // http://code.google.com/p/android/issues/detail?id=11140
-    public void testDigestAuthentication() throws Exception {
+    @Test public void digestAuthentication() throws Exception {
         List<String> calls = authCallsForHeader("WWW-Authenticate: Digest "
                 + "realm=\"testrealm@host.com\", qop=\"auth,auth-int\", "
                 + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
@@ -1212,7 +1245,7 @@
         assertTrue(call, call.contains("prompt=testrealm@host.com"));
     }
 
-    public void testAllAttributesSetInServerAuthenticationCallbacks() throws Exception {
+    @Test public void allAttributesSetInServerAuthenticationCallbacks() throws Exception {
         List<String> calls = authCallsForHeader("WWW-Authenticate: Basic realm=\"Bar\"");
         assertEquals(1, calls.size());
         URL url = server.getUrl("/");
@@ -1227,7 +1260,7 @@
         assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI.
     }
 
-    public void testAllAttributesSetInProxyAuthenticationCallbacks() throws Exception {
+    @Test public void allAttributesSetInProxyAuthenticationCallbacks() throws Exception {
         List<String> calls = authCallsForHeader("Proxy-Authenticate: Basic realm=\"Bar\"");
         assertEquals(1, calls.size());
         URL url = server.getUrl("/");
@@ -1254,14 +1287,18 @@
         server.enqueue(pleaseAuthenticate);
         server.play();
 
-        OkHttpConnection connection = proxy
-                ? openConnection(new URL("http://android.com"), server.toProxyAddress())
-                : openConnection(server.getUrl("/"));
+        HttpURLConnection connection;
+        if (proxy) {
+            client.setProxy(server.toProxyAddress());
+            connection = client.open(new URL("http://android.com"));
+        } else {
+            connection = client.open(server.getUrl("/"));
+        }
         assertEquals(responseCode, connection.getResponseCode());
         return authenticator.calls;
     }
 
-    public void testSetValidRequestMethod() throws Exception {
+    @Test public void setValidRequestMethod() throws Exception {
         server.play();
         assertValidRequestMethod("GET");
         assertValidRequestMethod("DELETE");
@@ -1273,23 +1310,23 @@
     }
 
     private void assertValidRequestMethod(String requestMethod) throws Exception {
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setRequestMethod(requestMethod);
         assertEquals(requestMethod, connection.getRequestMethod());
     }
 
-    public void testSetInvalidRequestMethodLowercase() throws Exception {
+    @Test public void setInvalidRequestMethodLowercase() throws Exception {
         server.play();
         assertInvalidRequestMethod("get");
     }
 
-    public void testSetInvalidRequestMethodConnect() throws Exception {
+    @Test public void setInvalidRequestMethodConnect() throws Exception {
         server.play();
         assertInvalidRequestMethod("CONNECT");
     }
 
     private void assertInvalidRequestMethod(String requestMethod) throws Exception {
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         try {
             connection.setRequestMethod(requestMethod);
             fail();
@@ -1297,9 +1334,9 @@
         }
     }
 
-    public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception {
+    @Test public void cannotSetNegativeFixedLengthStreamingMode() throws Exception {
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         try {
             connection.setFixedLengthStreamingMode(-2);
             fail();
@@ -1307,16 +1344,16 @@
         }
     }
 
-    public void testCanSetNegativeChunkedStreamingMode() throws Exception {
+    @Test public void canSetNegativeChunkedStreamingMode() throws Exception {
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setChunkedStreamingMode(-2);
     }
 
-    public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
+    @Test public void cannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
         try {
             connection.setFixedLengthStreamingMode(1);
@@ -1325,10 +1362,10 @@
         }
     }
 
-    public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception {
+    @Test public void cannotSetChunkedStreamingModeAfterConnect() throws Exception {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
         try {
             connection.setChunkedStreamingMode(1);
@@ -1337,9 +1374,9 @@
         }
     }
 
-    public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
+    @Test public void cannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setChunkedStreamingMode(1);
         try {
             connection.setFixedLengthStreamingMode(1);
@@ -1348,9 +1385,9 @@
         }
     }
 
-    public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
+    @Test public void cannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setFixedLengthStreamingMode(1);
         try {
             connection.setChunkedStreamingMode(1);
@@ -1359,11 +1396,11 @@
         }
     }
 
-    public void testSecureFixedLengthStreaming() throws Exception {
+    @Test public void secureFixedLengthStreaming() throws Exception {
         testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
     }
 
-    public void testSecureChunkedStreaming() throws Exception {
+    @Test public void secureChunkedStreaming() throws Exception {
         testSecureStreamingPost(StreamingMode.CHUNKED);
     }
 
@@ -1376,9 +1413,9 @@
         server.enqueue(new MockResponse().setBody("Success!"));
         server.play();
 
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setDoOutput(true);
         byte[] requestBody = { 'A', 'B', 'C', 'D' };
         if (streamingMode == StreamingMode.FIXED_LENGTH) {
@@ -1405,7 +1442,7 @@
         FIXED_LENGTH, CHUNKED
     }
 
-    public void testAuthenticateWithPost() throws Exception {
+    @Test public void authenticateWithPost() throws Exception {
         MockResponse pleaseAuthenticate = new MockResponse()
                 .setResponseCode(401)
                 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
@@ -1419,7 +1456,7 @@
         server.play();
 
         Authenticator.setDefault(new RecordingAuthenticator());
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setDoOutput(true);
         byte[] requestBody = { 'A', 'B', 'C', 'D' };
         OutputStream outputStream = connection.getOutputStream();
@@ -1440,7 +1477,7 @@
         }
     }
 
-    public void testAuthenticateWithGet() throws Exception {
+    @Test public void authenticateWithGet() throws Exception {
         MockResponse pleaseAuthenticate = new MockResponse()
                 .setResponseCode(401)
                 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
@@ -1454,7 +1491,7 @@
         server.play();
 
         Authenticator.setDefault(new RecordingAuthenticator());
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
 
         // no authorization header for the first request...
@@ -1469,28 +1506,28 @@
         }
     }
 
-    public void testRedirectedWithChunkedEncoding() throws Exception {
+    @Test public void redirectedWithChunkedEncoding() throws Exception {
         testRedirected(TransferKind.CHUNKED, true);
     }
 
-    public void testRedirectedWithContentLengthHeader() throws Exception {
+    @Test public void redirectedWithContentLengthHeader() throws Exception {
         testRedirected(TransferKind.FIXED_LENGTH, true);
     }
 
-    public void testRedirectedWithNoLengthHeaders() throws Exception {
+    @Test public void redirectedWithNoLengthHeaders() throws Exception {
         testRedirected(TransferKind.END_OF_STREAM, false);
     }
 
     private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
         MockResponse response = new MockResponse()
-                .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP)
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
                 .addHeader("Location: /foo");
         transferKind.setBody(response, "This page has moved!", 10);
         server.enqueue(response);
         server.enqueue(new MockResponse().setBody("This is the new location!"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This is the new location!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
 
@@ -1503,7 +1540,7 @@
         }
     }
 
-    public void testRedirectedOnHttps() throws IOException, InterruptedException {
+    @Test public void redirectedOnHttps() throws IOException, InterruptedException {
         server.useHttps(sslContext.getSocketFactory(), false);
         server.enqueue(new MockResponse()
                 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
@@ -1512,9 +1549,9 @@
         server.enqueue(new MockResponse().setBody("This is the new location!"));
         server.play();
 
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This is the new location!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
 
@@ -1525,7 +1562,7 @@
         assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
     }
 
-    public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
+    @Test public void notRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
         server.useHttps(sslContext.getSocketFactory(), false);
         server.enqueue(new MockResponse()
                 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
@@ -1533,45 +1570,45 @@
                 .setBody("This page has moved!"));
         server.play();
 
-        OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/"));
-        connection.setSSLSocketFactory(sslContext.getSocketFactory());
-        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        client.setSSLSocketFactory(sslContext.getSocketFactory());
+        client.setHostnameVerifier(new RecordingHostnameVerifier());
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This page has moved!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
     }
 
-    public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException {
+    @Test public void notRedirectedFromHttpToHttps() throws IOException, InterruptedException {
         server.enqueue(new MockResponse()
-                .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP)
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
                 .addHeader("Location: https://anyhost/foo")
                 .setBody("This page has moved!"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This page has moved!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
     }
 
-    public void SUPPRESSED_testRedirectToAnotherOriginServer() throws Exception {
+    @Test public void redirectToAnotherOriginServer() throws Exception {
         MockWebServer server2 = new MockWebServer();
         server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
         server2.play();
 
         server.enqueue(new MockResponse()
-                .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP)
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
                 .addHeader("Location: " + server2.getUrl("/").toString())
                 .setBody("This page has moved!"));
         server.enqueue(new MockResponse().setBody("This is the first server again!"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This is the 2nd server!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
         assertEquals(server2.getUrl("/"), connection.getURL());
 
         // make sure the first server was careful to recycle the connection
         assertEquals("This is the first server again!",
-                readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE));
+                readAscii(client.open(server.getUrl("/")).getInputStream(), Integer.MAX_VALUE));
 
         RecordedRequest first = server.takeRequest();
         assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort());
@@ -1583,21 +1620,21 @@
         server2.shutdown();
     }
 
-    public void testResponse300MultipleChoiceWithPost() throws Exception {
+    @Test public void response300MultipleChoiceWithPost() throws Exception {
         // Chrome doesn't follow the redirect, but Firefox and the RI both do
-        testResponseRedirectedWithPost(OkHttpConnection.HTTP_MULT_CHOICE);
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
     }
 
-    public void testResponse301MovedPermanentlyWithPost() throws Exception {
-        testResponseRedirectedWithPost(OkHttpConnection.HTTP_MOVED_PERM);
+    @Test public void response301MovedPermanentlyWithPost() throws Exception {
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
     }
 
-    public void testResponse302MovedTemporarilyWithPost() throws Exception {
-        testResponseRedirectedWithPost(OkHttpConnection.HTTP_MOVED_TEMP);
+    @Test public void response302MovedTemporarilyWithPost() throws Exception {
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
     }
 
-    public void testResponse303SeeOtherWithPost() throws Exception {
-        testResponseRedirectedWithPost(OkHttpConnection.HTTP_SEE_OTHER);
+    @Test public void response303SeeOtherWithPost() throws Exception {
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
     }
 
     private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
@@ -1608,7 +1645,7 @@
         server.enqueue(new MockResponse().setBody("Page 2"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/page1"));
+        HttpURLConnection connection = client.open(server.getUrl("/page1"));
         connection.setDoOutput(true);
         byte[] requestBody = { 'A', 'B', 'C', 'D' };
         OutputStream outputStream = connection.getOutputStream();
@@ -1625,15 +1662,15 @@
         assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
     }
 
-    public void testResponse305UseProxy() throws Exception {
+    @Test public void response305UseProxy() throws Exception {
         server.play();
         server.enqueue(new MockResponse()
-                .setResponseCode(OkHttpConnection.HTTP_USE_PROXY)
+                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
                 .addHeader("Location: " + server.getUrl("/"))
                 .setBody("This page has moved!"));
         server.enqueue(new MockResponse().setBody("Proxy Response"));
 
-        OkHttpConnection connection = openConnection(server.getUrl("/foo"));
+        HttpURLConnection connection = client.open(server.getUrl("/foo"));
         // Fails on the RI, which gets "Proxy Response"
         assertEquals("This page has moved!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
@@ -1643,61 +1680,31 @@
         assertEquals(1, server.getRequestCount());
     }
 
-//    public void testHttpsWithCustomTrustManager() throws Exception {
-//        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
-//        RecordingTrustManager trustManager = new RecordingTrustManager();
-//        SSLContext sc = SSLContext.getInstance("TLS");
-//        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
-//
-//        HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
-//        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
-//        SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
-//        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
-//        try {
-//            TestSSLContext testSSLContext = TestSSLContext.create();
-//            server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-//            server.enqueue(new MockResponse().setBody("ABC"));
-//            server.enqueue(new MockResponse().setBody("DEF"));
-//            server.enqueue(new MockResponse().setBody("GHI"));
-//            server.play();
-//
-//            URL url = server.getUrl("/");
-//            assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE));
-//            assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE));
-//            assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE));
-//
-//            assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
-//            assertEquals(Arrays.asList("checkServerTrusted ["
-//                    + "CN=" + hostName + " 1, "
-//                    + "CN=Test Intermediate Certificate Authority 1, "
-//                    + "CN=Test Root Certificate Authority 1"
-//                    + "] RSA"),
-//                    trustManager.calls);
-//        } finally {
-//            HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
-//            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
-//        }
-//    }
-//
-//    public void testConnectTimeouts() throws IOException {
-//        StuckServer ss = new StuckServer();
-//        int serverPort = ss.getLocalPort();
-//        URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection();
-//        int timeout = 1000;
-//        urlConnection.setConnectTimeout(timeout);
-//        long start = System.currentTimeMillis();
-//        try {
-//            urlConnection.getInputStream();
-//            fail();
-//        } catch (SocketTimeoutException expected) {
-//            long actual = System.currentTimeMillis() - start;
-//            assertTrue(Math.abs(timeout - actual) < 500);
-//        } finally {
-//            ss.close();
-//        }
-//    }
+    @Test public void httpsWithCustomTrustManager() throws Exception {
+        RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
+        RecordingTrustManager trustManager = new RecordingTrustManager();
+        SSLContext sc = SSLContext.getInstance("TLS");
+        sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom());
 
-    public void testReadTimeouts() throws IOException {
+        client.setHostnameVerifier(hostnameVerifier);
+        client.setSSLSocketFactory(sc.getSocketFactory());
+        server.useHttps(sslContext.getSocketFactory(), false);
+        server.enqueue(new MockResponse().setBody("ABC"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.enqueue(new MockResponse().setBody("GHI"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertContent("ABC", client.open(url));
+        assertContent("DEF", client.open(url));
+        assertContent("GHI", client.open(url));
+
+        assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls);
+        assertEquals(Arrays.asList("checkServerTrusted [CN=" + hostName + " 1]"),
+                trustManager.calls);
+    }
+
+    @Test public void readTimeouts() throws IOException {
         /*
          * This relies on the fact that MockWebServer doesn't close the
          * connection after a response has been sent. This causes the client to
@@ -1711,7 +1718,7 @@
         server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
         server.play();
 
-        URLConnection urlConnection = openConnection(server.getUrl("/"));
+        URLConnection urlConnection = client.open(server.getUrl("/"));
         urlConnection.setReadTimeout(1000);
         InputStream in = urlConnection.getInputStream();
         assertEquals('A', in.read());
@@ -1724,11 +1731,11 @@
         }
     }
 
-    public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
+    @Test public void setChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
         server.enqueue(new MockResponse());
         server.play();
 
-        OkHttpConnection urlConnection = openConnection(server.getUrl("/"));
+        HttpURLConnection urlConnection = client.open(server.getUrl("/"));
         urlConnection.setRequestProperty("Transfer-encoding", "chunked");
         urlConnection.setDoOutput(true);
         urlConnection.getOutputStream().write("ABC".getBytes("UTF-8"));
@@ -1738,16 +1745,16 @@
         assertEquals("ABC", new String(request.getBody(), "UTF-8"));
     }
 
-    public void testConnectionCloseInRequest() throws IOException, InterruptedException {
+    @Test public void connectionCloseInRequest() throws IOException, InterruptedException {
         server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
         server.enqueue(new MockResponse());
         server.play();
 
-        OkHttpConnection a = openConnection(server.getUrl("/"));
+        HttpURLConnection a = client.open(server.getUrl("/"));
         a.setRequestProperty("Connection", "close");
         assertEquals(200, a.getResponseCode());
 
-        OkHttpConnection b = openConnection(server.getUrl("/"));
+        HttpURLConnection b = client.open(server.getUrl("/"));
         assertEquals(200, b.getResponseCode());
 
         assertEquals(0, server.takeRequest().getSequenceNumber());
@@ -1755,15 +1762,15 @@
                 0, server.takeRequest().getSequenceNumber());
     }
 
-    public void testConnectionCloseInResponse() throws IOException, InterruptedException {
+    @Test public void connectionCloseInResponse() throws IOException, InterruptedException {
         server.enqueue(new MockResponse().addHeader("Connection: close"));
         server.enqueue(new MockResponse());
         server.play();
 
-        OkHttpConnection a = openConnection(server.getUrl("/"));
+        HttpURLConnection a = client.open(server.getUrl("/"));
         assertEquals(200, a.getResponseCode());
 
-        OkHttpConnection b = openConnection(server.getUrl("/"));
+        HttpURLConnection b = client.open(server.getUrl("/"));
         assertEquals(200, b.getResponseCode());
 
         assertEquals(0, server.takeRequest().getSequenceNumber());
@@ -1771,16 +1778,16 @@
                 0, server.takeRequest().getSequenceNumber());
     }
 
-    public void testConnectionCloseWithRedirect() throws IOException, InterruptedException {
+    @Test public void connectionCloseWithRedirect() throws IOException, InterruptedException {
         MockResponse response = new MockResponse()
-                .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP)
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
                 .addHeader("Location: /foo")
                 .addHeader("Connection: close");
         server.enqueue(response);
         server.enqueue(new MockResponse().setBody("This is the new location!"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This is the new location!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
 
@@ -1789,37 +1796,37 @@
                 0, server.takeRequest().getSequenceNumber());
     }
 
-    public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
+    @Test public void responseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
         server.enqueue(new MockResponse()
-                .setResponseCode(OkHttpConnection.HTTP_NO_CONTENT)
+                .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
                 .setBody("This body is not allowed!"));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         assertEquals("This body is not allowed!",
                 readAscii(connection.getInputStream(), Integer.MAX_VALUE));
     }
 
-    public void testSingleByteReadIsSigned() throws IOException {
+    @Test public void singleByteReadIsSigned() throws IOException {
         server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 }));
         server.play();
 
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         InputStream in = connection.getInputStream();
         assertEquals(254, in.read());
         assertEquals(255, in.read());
         assertEquals(-1, in.read());
     }
 
-    public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
+    @Test public void flushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
         testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
     }
 
-    public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException {
+    @Test public void flushAfterStreamTransmittedWithFixedLength() throws IOException {
         testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
     }
 
-    public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
+    @Test public void flushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
         testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
     }
 
@@ -1832,7 +1839,7 @@
         server.enqueue(new MockResponse().setBody("abc"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setDoOutput(true);
         byte[] upload = "def".getBytes("UTF-8");
 
@@ -1854,11 +1861,16 @@
         }
     }
 
-    public void testGetHeadersThrows() throws IOException {
-        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
+    @Test public void getHeadersThrows() throws IOException {
+        // Enqueue a response for every IP address held by localhost, because the route selector
+        // will try each in sequence.
+        // TODO: use the fake Dns implementation instead of a loop
+        for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) {
+            server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
+        }
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         try {
             connection.getInputStream();
             fail();
@@ -1872,150 +1884,67 @@
         }
     }
 
-    public void SUPPRESSED_testGetKeepAlive() throws Exception {
+    @Test public void dnsFailureThrowsIOException() throws IOException {
+        HttpURLConnection connection = client.open(new URL("http://host.unlikelytld"));
+        try {
+            connection.connect();
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    @Test public void malformedUrlThrowsUnknownHostException() throws IOException {
+        HttpURLConnection connection = client.open(new URL("http:///foo.html"));
+        try {
+            connection.connect();
+            fail();
+        } catch (UnknownHostException expected) {
+        }
+    }
+
+    @Test public void getKeepAlive() throws Exception {
         MockWebServer server = new MockWebServer();
         server.enqueue(new MockResponse().setBody("ABC"));
         server.play();
 
         // The request should work once and then fail
-        URLConnection connection = openConnection(server.getUrl(""));
-        InputStream input = connection.getInputStream();
+        URLConnection connection1 = client.open(server.getUrl(""));
+        connection1.setReadTimeout(100);
+        InputStream input = connection1.getInputStream();
         assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
         input.close();
+        server.shutdown();
         try {
-            openConnection(server.getUrl("")).getInputStream();
+            HttpURLConnection connection2 = client.open(server.getUrl(""));
+            connection2.setReadTimeout(100);
+            connection2.getInputStream();
             fail();
         } catch (ConnectException expected) {
         }
     }
 
     /**
-     * This test goes through the exhaustive set of interesting ASCII characters
-     * because most of those characters are interesting in some way according to
-     * RFC 2396 and RFC 2732. http://b/1158780
-     */
-    public void SUPPRESSED_testLenientUrlToUri() throws Exception {
-        // alphanum
-        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
-
-        // control characters
-        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
-        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
-
-        // ascii characters
-        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
-        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
-        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
-        testUrlToUriMapping("!", "!", "!", "!", "!");
-        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
-        testUrlToUriMapping("#", null, null, null, "%23");
-        testUrlToUriMapping("$", "$", "$", "$", "$");
-        testUrlToUriMapping("&", "&", "&", "&", "&");
-        testUrlToUriMapping("'", "'", "'", "'", "'");
-        testUrlToUriMapping("(", "(", "(", "(", "(");
-        testUrlToUriMapping(")", ")", ")", ")", ")");
-        testUrlToUriMapping("*", "*", "*", "*", "*");
-        testUrlToUriMapping("+", "+", "+", "+", "+");
-        testUrlToUriMapping(",", ",", ",", ",", ",");
-        testUrlToUriMapping("-", "-", "-", "-", "-");
-        testUrlToUriMapping(".", ".", ".", ".", ".");
-        testUrlToUriMapping("/", null, "/", "/", "/");
-        testUrlToUriMapping(":", null, ":", ":", ":");
-        testUrlToUriMapping(";", ";", ";", ";", ";");
-        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
-        testUrlToUriMapping("=", "=", "=", "=", "=");
-        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
-        testUrlToUriMapping("?", null, null, "?", "?");
-        testUrlToUriMapping("@", "@", "@", "@", "@");
-        testUrlToUriMapping("[", null, "%5B", null, "%5B");
-        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
-        testUrlToUriMapping("]", null, "%5D", null, "%5D");
-        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
-        testUrlToUriMapping("_", "_", "_", "_", "_");
-        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
-        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
-        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
-        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
-        testUrlToUriMapping("~", "~", "~", "~", "~");
-        testUrlToUriMapping("~", "~", "~", "~", "~");
-        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
-
-        // beyond ascii
-        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
-        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
-        testUrlToUriMapping("\ud842\udf9f",
-                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
-    }
-
-    public void SUPPRESSED_testLenientUrlToUriNul() throws Exception {
-        testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this
-    }
-
-    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
-            String asQuery, String asFragment) throws Exception {
-        if (asAuthority != null) {
-            assertEquals("http://host" + asAuthority + ".tld/",
-                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
-        }
-        if (asFile != null) {
-            assertEquals("http://host.tld/file" + asFile + "/",
-                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
-        }
-        if (asQuery != null) {
-            assertEquals("http://host.tld/file?q" + asQuery + "=x",
-                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
-        }
-        assertEquals("http://host.tld/file#" + asFragment + "-x",
-                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
-    }
-
-    /**
-     * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
-     * HttpURLConnection recovers from URLs with unescaped but unsupported URI
-     * characters like '{' and '|' by escaping these characters.
-     */
-    private URI backdoorUrlToUri(URL url) throws Exception {
-        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
-
-        ResponseCache.setDefault(new ResponseCache() {
-            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
-                return null;
-            }
-            @Override public CacheResponse get(URI uri, String requestMethod,
-                    Map<String, List<String>> requestHeaders) throws IOException {
-                uriReference.set(uri);
-                throw new UnsupportedOperationException();
-            }
-        });
-
-        try {
-            OkHttpConnection connection = openConnection(url);
-            connection.getResponseCode();
-        } catch (Exception expected) {
-            if (expected.getCause() instanceof URISyntaxException) {
-                expected.printStackTrace();
-            }
-        }
-
-        return uriReference.get();
-    }
-
-    /**
      * Don't explode if the cache returns a null body. http://b/3373699
      */
-    public void testResponseCacheReturnsNullOutputStream() throws Exception {
+    @Test public void responseCacheReturnsNullOutputStream() throws Exception {
         final AtomicBoolean aborted = new AtomicBoolean();
-        ResponseCache.setDefault(new ResponseCache() {
-            @Override public CacheResponse get(URI uri, String requestMethod,
+        client.setResponseCache(new ResponseCache() {
+            @Override
+            public CacheResponse get(URI uri, String requestMethod,
                     Map<String, List<String>> requestHeaders) throws IOException {
                 return null;
             }
-            @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
+
+            @Override
+            public CacheRequest put(URI uri, URLConnection connection) throws IOException {
                 return new CacheRequest() {
-                    @Override public void abort() {
+                    @Override
+                    public void abort() {
                         aborted.set(true);
                     }
-                    @Override public OutputStream getBody() throws IOException {
+
+                    @Override
+                    public OutputStream getBody() throws IOException {
                         return null;
                     }
                 };
@@ -2025,18 +1954,17 @@
         server.enqueue(new MockResponse().setBody("abcdef"));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         InputStream in = connection.getInputStream();
         assertEquals("abc", readAscii(in, 3));
         in.close();
         assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
     }
 
-
     /**
      * http://code.google.com/p/android/issues/detail?id=14562
      */
-    public void testReadAfterLastByte() throws Exception {
+    @Test public void readAfterLastByte() throws Exception {
         server.enqueue(new MockResponse()
                 .setBody("ABC")
                 .clearHeaders()
@@ -2044,29 +1972,29 @@
                 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END));
         server.play();
 
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         InputStream in = connection.getInputStream();
         assertEquals("ABC", readAscii(in, 3));
         assertEquals(-1, in.read());
         assertEquals(-1, in.read()); // throws IOException in Gingerbread
     }
 
-    public void testGetContent() throws Exception {
+    @Test public void getContent() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Content-Type: text/plain")
                 .setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         InputStream in = (InputStream) connection.getContent();
         assertEquals("A", readAscii(in, Integer.MAX_VALUE));
     }
 
-    public void testGetContentOfType() throws Exception {
+    @Test public void getContentOfType() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Content-Type: text/plain")
                 .setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         try {
             connection.getContent(null);
             fail();
@@ -2081,10 +2009,10 @@
         connection.disconnect();
     }
 
-    public void testGetOutputStreamOnGetFails() throws Exception {
+    @Test public void getOutputStreamOnGetFails() throws Exception {
         server.enqueue(new MockResponse());
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         try {
             connection.getOutputStream();
             fail();
@@ -2092,10 +2020,10 @@
         }
     }
 
-    public void testGetOutputAfterGetInputStreamFails() throws Exception {
+    @Test public void getOutputAfterGetInputStreamFails() throws Exception {
         server.enqueue(new MockResponse());
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setDoOutput(true);
         try {
             connection.getInputStream();
@@ -2105,10 +2033,10 @@
         }
     }
 
-    public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception {
+    @Test public void setDoOutputOrDoInputAfterConnectFails() throws Exception {
         server.enqueue(new MockResponse());
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.connect();
         try {
             connection.setDoOutput(true);
@@ -2123,10 +2051,10 @@
         connection.disconnect();
     }
 
-    public void testClientSendsContentLength() throws Exception {
+    @Test public void clientSendsContentLength() throws Exception {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         connection.setDoOutput(true);
         OutputStream out = connection.getOutputStream();
         out.write(new byte[] { 'A', 'B', 'C' });
@@ -2136,54 +2064,54 @@
         assertContains(request.getHeaders(), "Content-Length: 3");
     }
 
-    public void testGetContentLengthConnects() throws Exception {
+    @Test public void getContentLengthConnects() throws Exception {
         server.enqueue(new MockResponse().setBody("ABC"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals(3, connection.getContentLength());
         connection.disconnect();
     }
 
-    public void testGetContentTypeConnects() throws Exception {
+    @Test public void getContentTypeConnects() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Content-Type: text/plain")
                 .setBody("ABC"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("text/plain", connection.getContentType());
         connection.disconnect();
     }
 
-    public void testGetContentEncodingConnects() throws Exception {
+    @Test public void getContentEncodingConnects() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Content-Encoding: identity")
                 .setBody("ABC"));
         server.play();
-        OkHttpConnection connection = openConnection(server.getUrl("/"));
+        HttpURLConnection connection = client.open(server.getUrl("/"));
         assertEquals("identity", connection.getContentEncoding());
         connection.disconnect();
     }
 
     // http://b/4361656
-    public void testUrlContainsQueryButNoPath() throws Exception {
+    @Test public void urlContainsQueryButNoPath() throws Exception {
         server.enqueue(new MockResponse().setBody("A"));
         server.play();
         URL url = new URL("http", server.getHostName(), server.getPort(), "?query");
-        assertEquals("A", readAscii(openConnection(url).getInputStream(), Integer.MAX_VALUE));
+        assertEquals("A", readAscii(client.open(url).getInputStream(), Integer.MAX_VALUE));
         RecordedRequest request = server.takeRequest();
         assertEquals("GET /?query HTTP/1.1", request.getRequestLine());
     }
 
     // http://code.google.com/p/android/issues/detail?id=20442
-    public void testInputStreamAvailableWithChunkedEncoding() throws Exception {
+    @Test public void inputStreamAvailableWithChunkedEncoding() throws Exception {
         testInputStreamAvailable(TransferKind.CHUNKED);
     }
 
-    public void testInputStreamAvailableWithContentLengthHeader() throws Exception {
+    @Test public void inputStreamAvailableWithContentLengthHeader() throws Exception {
         testInputStreamAvailable(TransferKind.FIXED_LENGTH);
     }
 
-    public void testInputStreamAvailableWithNoLengthHeaders() throws Exception {
+    @Test public void inputStreamAvailableWithNoLengthHeaders() throws Exception {
         testInputStreamAvailable(TransferKind.END_OF_STREAM);
     }
 
@@ -2193,7 +2121,7 @@
         transferKind.setBody(response, body, 4);
         server.enqueue(response);
         server.play();
-        URLConnection connection = openConnection(server.getUrl("/"));
+        URLConnection connection = client.open(server.getUrl("/"));
         InputStream in = connection.getInputStream();
         for (int i = 0; i < body.length(); i++) {
             assertTrue(in.available() >= 0);
@@ -2203,6 +2131,52 @@
         assertEquals(-1, in.read());
     }
 
+    @Test @Ignore public void testPooledConnectionsDetectHttp10() {
+        // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1)
+        fail("TODO");
+    }
+
+    @Test @Ignore public void postBodiesRetransmittedOnAuthProblems() {
+        fail("TODO");
+    }
+
+    @Test @Ignore public void cookiesAndTrailers() {
+        // Do cookie headers get processed too many times?
+        fail("TODO");
+    }
+
+    @Test @Ignore public void headerNamesContainingNullCharacter() {
+        // This is relevant for SPDY
+        fail("TODO");
+    }
+
+    @Test @Ignore public void headerValuesContainingNullCharacter() {
+        // This is relevant for SPDY
+        fail("TODO");
+    }
+
+    @Test @Ignore public void emptyHeaderName() {
+        // This is relevant for SPDY
+        fail("TODO");
+    }
+
+    @Test @Ignore public void emptyHeaderValue() {
+        // This is relevant for SPDY
+        fail("TODO");
+    }
+
+    @Test @Ignore public void deflateCompression() {
+        fail("TODO");
+    }
+
+    @Test @Ignore public void postBodiesRetransmittedOnIpAddressProblems() {
+        fail("TODO");
+    }
+
+    @Test @Ignore public void pooledConnectionProblemsNotReportedToProxySelector() {
+        fail("TODO");
+    }
+
     /**
      * Returns a gzipped copy of {@code bytes}.
      */
@@ -2222,7 +2196,7 @@
             throws IOException {
         connection.connect();
         assertEquals(expected, readAscii(connection.getInputStream(), limit));
-        ((OkHttpConnection) connection).disconnect();
+        ((HttpURLConnection) connection).disconnect();
     }
 
     private void assertContent(String expected, URLConnection connection) throws IOException {
@@ -2251,11 +2225,17 @@
                     throws IOException {
                 response.setChunkedBody(content, chunkSize);
             }
+            @Override void setForRequest(HttpURLConnection connection, int contentLength) {
+                connection.setChunkedStreamingMode(5);
+            }
         },
         FIXED_LENGTH() {
             @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
                 response.setBody(content);
             }
+            @Override void setForRequest(HttpURLConnection connection, int contentLength) {
+                connection.setChunkedStreamingMode(contentLength);
+            }
         },
         END_OF_STREAM() {
             @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
@@ -2268,11 +2248,15 @@
                     }
                 }
             }
+            @Override void setForRequest(HttpURLConnection connection, int contentLength) {
+            }
         };
 
         abstract void setBody(MockResponse response, byte[] content, int chunkSize)
                 throws IOException;
 
+        abstract void setForRequest(HttpURLConnection connection, int contentLength);
+
         void setBody(MockResponse response, String content, int chunkSize) throws IOException {
             setBody(response, content.getBytes("UTF-8"), chunkSize);
         }
@@ -2280,65 +2264,66 @@
 
     enum ProxyConfig {
         NO_PROXY() {
-            @Override public OkHttpConnection connect(MockWebServer server, URL url)
-                    throws IOException {
-                return openConnection(url, Proxy.NO_PROXY);
+            @Override public HttpURLConnection connect
+                    (MockWebServer server, OkHttpClient client, URL url) throws IOException {
+                client.setProxy(Proxy.NO_PROXY);
+                return client.open(url);
             }
         },
 
         CREATE_ARG() {
-            @Override public OkHttpConnection connect(MockWebServer server, URL url)
-                    throws IOException {
-                return openConnection(url, server.toProxyAddress());
+            @Override public HttpURLConnection connect(
+                    MockWebServer server, OkHttpClient client, URL url) throws IOException {
+                client.setProxy(server.toProxyAddress());
+                return client.open(url);
             }
         },
 
         PROXY_SYSTEM_PROPERTY() {
-            @Override public OkHttpConnection connect(MockWebServer server, URL url)
-                    throws IOException {
+            @Override public HttpURLConnection connect(
+                    MockWebServer server, OkHttpClient client, URL url) throws IOException {
                 System.setProperty("proxyHost", "localhost");
                 System.setProperty("proxyPort", Integer.toString(server.getPort()));
-                return openConnection(url);
+                return client.open(url);
             }
         },
 
         HTTP_PROXY_SYSTEM_PROPERTY() {
-            @Override public OkHttpConnection connect(MockWebServer server, URL url)
-                    throws IOException {
+            @Override public HttpURLConnection connect(
+                    MockWebServer server, OkHttpClient client, URL url) throws IOException {
                 System.setProperty("http.proxyHost", "localhost");
                 System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
-                return openConnection(url);
+                return client.open(url);
             }
         },
 
         HTTPS_PROXY_SYSTEM_PROPERTY() {
-            @Override public OkHttpConnection connect(MockWebServer server, URL url)
-                    throws IOException {
+            @Override public HttpURLConnection connect(
+                    MockWebServer server, OkHttpClient client, URL url) throws IOException {
                 System.setProperty("https.proxyHost", "localhost");
                 System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
-                return openConnection(url);
+                return client.open(url);
             }
         };
 
-        public abstract OkHttpConnection connect(MockWebServer server, URL url) throws IOException;
+        public abstract HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) throws IOException;
     }
 
     private static class RecordingTrustManager implements X509TrustManager {
         private final List<String> calls = new ArrayList<String>();
 
         public X509Certificate[] getAcceptedIssuers() {
-            calls.add("getAcceptedIssuers");
             return new X509Certificate[] {};
         }
 
         public void checkClientTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
-            calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType);
+            calls.add("checkClientTrusted " + certificatesToString(chain));
         }
 
         public void checkServerTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
-            calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType);
+            calls.add("checkServerTrusted " + certificatesToString(chain));
         }
 
         private String certificatesToString(X509Certificate[] certificates) {
@@ -2383,4 +2368,18 @@
             return authentication;
         }
     }
+
+    private static class FakeProxySelector extends ProxySelector {
+        List<Proxy> proxies = new ArrayList<Proxy>();
+
+        @Override public List<Proxy> select(URI uri) {
+            // Don't handle 'socket' schemes, which the RI's Socket class may request (for SOCKS).
+            return uri.getScheme().equals("http") || uri.getScheme().equals("https")
+                    ? proxies
+                    : Collections.singletonList(Proxy.NO_PROXY);
+        }
+
+        @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+        }
+    }
 }
diff --git a/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java b/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java
new file mode 100644
index 0000000..0f8edca
--- /dev/null
+++ b/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.http;
+
+import com.squareup.okhttp.OkHttpClient;
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import static org.junit.Assert.assertEquals;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI,
+ * HttpURLConnection recovers from URLs with unescaped but unsupported URI
+ * characters like '{' and '|' by escaping these characters.
+ */
+public final class URLEncodingTest {
+    /**
+     * This test goes through the exhaustive set of interesting ASCII characters
+     * because most of those characters are interesting in some way according to
+     * RFC 2396 and RFC 2732. http://b/1158780
+     */
+    @Test @Ignore public void lenientUrlToUri() throws Exception {
+        // alphanum
+        testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09");
+
+        // control characters
+        testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01");
+        testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F");
+
+        // ascii characters
+        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
+        testUrlToUriMapping("%20", "%20", "%20", "%20", "%20");
+        testUrlToUriMapping(" ", "%20", "%20", "%20", "%20");
+        testUrlToUriMapping("!", "!", "!", "!", "!");
+        testUrlToUriMapping("\"", "%22", "%22", "%22", "%22");
+        testUrlToUriMapping("#", null, null, null, "%23");
+        testUrlToUriMapping("$", "$", "$", "$", "$");
+        testUrlToUriMapping("&", "&", "&", "&", "&");
+        testUrlToUriMapping("'", "'", "'", "'", "'");
+        testUrlToUriMapping("(", "(", "(", "(", "(");
+        testUrlToUriMapping(")", ")", ")", ")", ")");
+        testUrlToUriMapping("*", "*", "*", "*", "*");
+        testUrlToUriMapping("+", "+", "+", "+", "+");
+        testUrlToUriMapping(",", ",", ",", ",", ",");
+        testUrlToUriMapping("-", "-", "-", "-", "-");
+        testUrlToUriMapping(".", ".", ".", ".", ".");
+        testUrlToUriMapping("/", null, "/", "/", "/");
+        testUrlToUriMapping(":", null, ":", ":", ":");
+        testUrlToUriMapping(";", ";", ";", ";", ";");
+        testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C");
+        testUrlToUriMapping("=", "=", "=", "=", "=");
+        testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E");
+        testUrlToUriMapping("?", null, null, "?", "?");
+        testUrlToUriMapping("@", "@", "@", "@", "@");
+        testUrlToUriMapping("[", null, "%5B", null, "%5B");
+        testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C");
+        testUrlToUriMapping("]", null, "%5D", null, "%5D");
+        testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E");
+        testUrlToUriMapping("_", "_", "_", "_", "_");
+        testUrlToUriMapping("`", "%60", "%60", "%60", "%60");
+        testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B");
+        testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C");
+        testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D");
+        testUrlToUriMapping("~", "~", "~", "~", "~");
+        testUrlToUriMapping("~", "~", "~", "~", "~");
+        testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F");
+
+        // beyond ascii
+        testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80");
+        testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac");
+        testUrlToUriMapping("\ud842\udf9f",
+                "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f");
+    }
+
+    @Test @Ignore public void lenientUrlToUriNul() throws Exception {
+        testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this
+    }
+
+    private void testUrlToUriMapping(String string, String asAuthority, String asFile,
+            String asQuery, String asFragment) throws Exception {
+        if (asAuthority != null) {
+            assertEquals("http://host" + asAuthority + ".tld/",
+                    backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString());
+        }
+        if (asFile != null) {
+            assertEquals("http://host.tld/file" + asFile + "/",
+                    backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString());
+        }
+        if (asQuery != null) {
+            assertEquals("http://host.tld/file?q" + asQuery + "=x",
+                    backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString());
+        }
+        assertEquals("http://host.tld/file#" + asFragment + "-x",
+                backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString());
+    }
+
+    private URI backdoorUrlToUri(URL url) throws Exception {
+        final AtomicReference<URI> uriReference = new AtomicReference<URI>();
+
+        OkHttpClient client = new OkHttpClient();
+        client.setResponseCache(new ResponseCache() {
+            @Override public CacheRequest put(URI uri, URLConnection connection)
+                    throws IOException {
+                return null;
+            }
+
+            @Override public CacheResponse get(URI uri, String requestMethod,
+                    Map<String, List<String>> requestHeaders) throws IOException {
+                uriReference.set(uri);
+                throw new UnsupportedOperationException();
+            }
+        });
+
+        try {
+            HttpURLConnection connection = client.open(url);
+            connection.getResponseCode();
+        } catch (Exception expected) {
+            if (expected.getCause() instanceof URISyntaxException) {
+                expected.printStackTrace();
+            }
+        }
+
+        return uriReference.get();
+    }
+}
diff --git a/src/test/java/libcore/net/spdy/MockSpdyPeer.java b/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
similarity index 78%
rename from src/test/java/libcore/net/spdy/MockSpdyPeer.java
rename to src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
index 0eb8208..652786a 100644
--- a/src/test/java/libcore/net/spdy/MockSpdyPeer.java
+++ b/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
+import com.squareup.okhttp.internal.Util;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,13 +30,14 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
-import libcore.io.Streams;
 
 /**
  * Replays prerecorded outgoing frames and records incoming frames.
  */
 public final class MockSpdyPeer {
     private int frameCount = 0;
+    private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+    private final SpdyWriter spdyWriter = new SpdyWriter(bytesOut);
     private final List<OutFrame> outFrames = new ArrayList<OutFrame>();
     private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>();
     private int port;
@@ -47,9 +49,9 @@
     }
 
     public SpdyWriter sendFrame() {
-        OutFrame frame = new OutFrame(frameCount++);
+        OutFrame frame = new OutFrame(frameCount++, bytesOut.size());
         outFrames.add(frame);
-        return new SpdyWriter(frame.out);
+        return spdyWriter;
     }
 
     public int getPort() {
@@ -69,7 +71,7 @@
                 try {
                     readAndWriteFrames(serverSocket);
                 } catch (IOException e) {
-                    e.printStackTrace(); // TODO
+                    throw new RuntimeException(e);
                 }
             }
         });
@@ -79,8 +81,10 @@
         Socket socket = serverSocket.accept();
         OutputStream out = socket.getOutputStream();
         InputStream in = socket.getInputStream();
+        SpdyReader reader = new SpdyReader(in);
 
         Iterator<OutFrame> outFramesIterator = outFrames.iterator();
+        byte[] outBytes = bytesOut.toByteArray();
         OutFrame nextOutFrame = null;
 
         for (int i = 0; i < frameCount; i++) {
@@ -89,13 +93,20 @@
             }
 
             if (nextOutFrame != null && nextOutFrame.sequence == i) {
+                int start = nextOutFrame.start;
+                int end;
+                if (outFramesIterator.hasNext()) {
+                    nextOutFrame = outFramesIterator.next();
+                    end = nextOutFrame.start;
+                } else {
+                    end = outBytes.length;
+                }
+
                 // write a frame
-                nextOutFrame.out.writeTo(out);
-                nextOutFrame = null;
+                out.write(outBytes, start, end - start);
 
             } else {
                 // read a frame
-                SpdyReader reader = new SpdyReader(in);
                 InFrame inFrame = new InFrame(i, reader);
                 reader.nextFrame(inFrame);
                 inFrames.add(inFrame);
@@ -109,10 +120,11 @@
 
     private static class OutFrame {
         private final int sequence;
-        private final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        private final int start;
 
-        private OutFrame(int sequence) {
+        private OutFrame(int sequence, int start) {
             this.sequence = sequence;
+            this.start = start;
         }
     }
 
@@ -160,6 +172,14 @@
             this.nameValueBlock = nameValueBlock;
         }
 
+        @Override public void headers(int flags, int streamId, List<String> nameValueBlock) {
+            if (this.type != -1) throw new IllegalStateException();
+            this.type = SpdyConnection.TYPE_HEADERS;
+            this.streamId = streamId;
+            this.flags = flags;
+            this.nameValueBlock = nameValueBlock;
+        }
+
         @Override public void data(int flags, int streamId, InputStream in, int length)
                 throws IOException {
             if (this.type != -1) throw new IllegalStateException();
@@ -167,7 +187,7 @@
             this.flags = flags;
             this.streamId = streamId;
             this.data = new byte[length];
-            Streams.readFully(in, this.data);
+            Util.readFully(in, this.data);
         }
 
         @Override public void rstStream(int flags, int streamId, int statusCode) {
@@ -189,5 +209,12 @@
             if (this.type != -1) throw new IllegalStateException();
             this.type = SpdyConnection.TYPE_NOOP;
         }
+
+        @Override public void goAway(int flags, int lastGoodStreamId) {
+            if (this.type != -1) throw new IllegalStateException();
+            this.type = SpdyConnection.TYPE_GOAWAY;
+            this.flags = flags;
+            this.streamId = lastGoodStreamId;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/test/java/libcore/net/spdy/SettingsTest.java b/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
similarity index 84%
rename from src/test/java/libcore/net/spdy/SettingsTest.java
rename to src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
index fe479cf..1c44493 100644
--- a/src/test/java/libcore/net/spdy/SettingsTest.java
+++ b/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
@@ -13,24 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package libcore.net.spdy;
+package com.squareup.okhttp.internal.spdy;
 
-import junit.framework.TestCase;
+import com.squareup.okhttp.internal.spdy.Settings;
+import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_BANDWIDTH;
+import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_RETRANS_RATE;
+import static com.squareup.okhttp.internal.spdy.Settings.MAX_CONCURRENT_STREAMS;
+import static com.squareup.okhttp.internal.spdy.Settings.PERSISTED;
+import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE;
+import static com.squareup.okhttp.internal.spdy.Settings.UPLOAD_BANDWIDTH;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
 
-import static libcore.net.spdy.Settings.DOWNLOAD_BANDWIDTH;
-import static libcore.net.spdy.Settings.DOWNLOAD_RETRANS_RATE;
-import static libcore.net.spdy.Settings.MAX_CONCURRENT_STREAMS;
-import static libcore.net.spdy.Settings.PERSISTED;
-import static libcore.net.spdy.Settings.PERSIST_VALUE;
-import static libcore.net.spdy.Settings.UPLOAD_BANDWIDTH;
-
-public final class SettingsTest extends TestCase {
-    public void testUnsetField() {
+public final class SettingsTest {
+    @Test public void unsetField() {
         Settings settings = new Settings();
         assertEquals(-3, settings.getUploadBandwidth(-3));
     }
 
-    public void testSetFields() {
+    @Test public void setFields() {
         Settings settings = new Settings();
 
         assertEquals(-3, settings.getUploadBandwidth(-3));
@@ -62,7 +65,7 @@
         assertEquals(108, settings.getInitialWindowSize(-3));
     }
 
-    public void testIsPersisted() {
+    @Test public void isPersisted() {
         Settings settings = new Settings();
 
         // Initially false.
@@ -93,7 +96,7 @@
         assertFalse(settings.isPersisted(Settings.ROUND_TRIP_TIME));
     }
 
-    public void testPersistValue() {
+    @Test public void persistValue() {
         Settings settings = new Settings();
 
         // Initially false.
@@ -124,7 +127,7 @@
         assertFalse(settings.persistValue(Settings.ROUND_TRIP_TIME));
     }
 
-    public void testMerge() {
+    @Test public void merge() {
         Settings a = new Settings();
         a.set(UPLOAD_BANDWIDTH, PERSIST_VALUE, 100);
         a.set(DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200);
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
new file mode 100644
index 0000000..d51fed7
--- /dev/null
+++ b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
@@ -0,0 +1,919 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.spdy;
+
+import static com.squareup.okhttp.internal.Util.UTF_8;
+import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_FIN;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_UNIDIRECTIONAL;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_DATA;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_GOAWAY;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_NOOP;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_PING;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_RST_STREAM;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_REPLY;
+import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_STREAM;
+import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_FLOW_CONTROL_ERROR;
+import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_INVALID_STREAM;
+import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_PROTOCOL_ERROR;
+import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_REFUSED_STREAM;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+public final class SpdyConnectionTest {
+    private static final IncomingStreamHandler REJECT_INCOMING_STREAMS
+            = new IncomingStreamHandler() {
+        @Override public void receive(SpdyStream stream) throws IOException {
+            throw new AssertionError();
+        }
+    };
+    private final MockSpdyPeer peer = new MockSpdyPeer();
+
+    @Test public void clientCreatesStreamAndServerReplies() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+        peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
+        assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders());
+        assertStreamData("robot", stream.getInputStream());
+        writeAndClose(stream, "c3po");
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        assertEquals(0, synStream.flags);
+        assertEquals(1, synStream.streamId);
+        assertEquals(0, synStream.associatedStreamId);
+        assertEquals(Arrays.asList("b", "banana"), synStream.nameValueBlock);
+        MockSpdyPeer.InFrame requestData = peer.takeFrame();
+        assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data));
+    }
+
+    @Test public void headersOnlyStreamIsClosedImmediately() throws Exception {
+        peer.acceptFrame(); // SYN STREAM
+        peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
+        peer.play();
+
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        connection.newStream(Arrays.asList("a", "android"), false, false);
+        assertEquals(0, connection.openStreamCount());
+    }
+
+    @Test public void clientCreatesStreamAndServerRepliesWithFin() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM
+        peer.acceptFrame(); // PING
+        peer.sendFrame().synReply(FLAG_FIN, 1, Arrays.asList("a", "android"));
+        peer.sendFrame().ping(0, 1);
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        connection.newStream(Arrays.asList("b", "banana"), false, true);
+        assertEquals(1, connection.openStreamCount());
+        connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received.
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+    }
+
+    @Test public void serverCreatesStreamAndClientReplies() throws Exception {
+        // write the mocking script
+        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        final AtomicInteger receiveCount = new AtomicInteger();
+        IncomingStreamHandler handler = new IncomingStreamHandler() {
+            @Override public void receive(SpdyStream stream) throws IOException {
+                receiveCount.incrementAndGet();
+                assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
+                assertEquals(-1, stream.getRstStatusCode());
+                stream.reply(Arrays.asList("b", "banana"), true);
+
+            }
+        };
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(handler)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame reply = peer.takeFrame();
+        assertEquals(TYPE_SYN_REPLY, reply.type);
+        assertEquals(0, reply.flags);
+        assertEquals(2, reply.streamId);
+        assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock);
+        assertEquals(1, receiveCount.get());
+    }
+
+    @Test public void replyWithNoData() throws Exception {
+        // write the mocking script
+        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        final AtomicInteger receiveCount = new AtomicInteger();
+        IncomingStreamHandler handler = new IncomingStreamHandler() {
+            @Override public void receive(SpdyStream stream) throws IOException {
+                stream.reply(Arrays.asList("b", "banana"), false);
+                receiveCount.incrementAndGet();
+            }
+        };
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(handler)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame reply = peer.takeFrame();
+        assertEquals(TYPE_SYN_REPLY, reply.type);
+        assertEquals(FLAG_FIN, reply.flags);
+        assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock);
+        assertEquals(1, receiveCount.get());
+    }
+
+    @Test public void noop() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        connection.noop();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_NOOP, ping.type);
+        assertEquals(0, ping.flags);
+    }
+
+    @Test public void serverPingsClient() throws Exception {
+        // write the mocking script
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        assertEquals(0, ping.flags);
+        assertEquals(2, ping.streamId);
+    }
+
+    @Test public void clientPingsServer() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.sendFrame().ping(0, 1);
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        Ping ping = connection.ping();
+        assertTrue(ping.roundTripTime() > 0);
+        assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1));
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
+        assertEquals(TYPE_PING, pingFrame.type);
+        assertEquals(0, pingFrame.flags);
+        assertEquals(1, pingFrame.streamId);
+    }
+
+    @Test public void unexpectedPingIsNotReturned() throws Exception {
+        // write the mocking script
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame();
+        peer.sendFrame().ping(0, 3); // This ping will not be returned.
+        peer.sendFrame().ping(0, 4);
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame ping2 = peer.takeFrame();
+        assertEquals(2, ping2.streamId);
+        MockSpdyPeer.InFrame ping4 = peer.takeFrame();
+        assertEquals(4, ping4.streamId);
+    }
+
+    @Test public void serverSendsSettingsToClient() throws Exception {
+        // write the mocking script
+        Settings settings = new Settings();
+        settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10);
+        peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings);
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+
+        peer.takeFrame(); // Guarantees that the Settings frame has been processed.
+        synchronized (connection) {
+            assertEquals(10, connection.settings.getMaxConcurrentStreams(-1));
+        }
+    }
+
+    @Test public void multipleSettingsFramesAreMerged() throws Exception {
+        // write the mocking script
+        Settings settings1 = new Settings();
+        settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100);
+        settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200);
+        settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300);
+        peer.sendFrame().settings(0, settings1);
+        Settings settings2 = new Settings();
+        settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400);
+        settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500);
+        settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600);
+        peer.sendFrame().settings(0, settings2);
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+
+        peer.takeFrame(); // Guarantees that the Settings frame has been processed.
+        synchronized (connection) {
+            assertEquals(100, connection.settings.getUploadBandwidth(-1));
+            assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.UPLOAD_BANDWIDTH));
+            assertEquals(400, connection.settings.getDownloadBandwidth(-1));
+            assertEquals(0, connection.settings.flags(Settings.DOWNLOAD_BANDWIDTH));
+            assertEquals(500, connection.settings.getDownloadRetransRate(-1));
+            assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.DOWNLOAD_RETRANS_RATE));
+            assertEquals(600, connection.settings.getMaxConcurrentStreams(-1));
+            assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.MAX_CONCURRENT_STREAMS));
+        }
+    }
+
+    @Test public void bogusDataFrameDoesNotDisruptConnection() throws Exception {
+        // write the mocking script
+        peer.sendFrame().data(SpdyConnection.FLAG_FIN, 42, "bogus".getBytes("UTF-8"));
+        peer.acceptFrame(); // RST_STREAM
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame(); // PING
+        peer.play();
+
+        // play it back
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(0, rstStream.flags);
+        assertEquals(42, rstStream.streamId);
+        assertEquals(RST_INVALID_STREAM, rstStream.statusCode);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(2, ping.streamId);
+    }
+
+    @Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception {
+        // write the mocking script
+        peer.sendFrame().synReply(0, 42, Arrays.asList("a", "android"));
+        peer.acceptFrame(); // RST_STREAM
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame(); // PING
+        peer.play();
+
+        // play it back
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(0, rstStream.flags);
+        assertEquals(42, rstStream.streamId);
+        assertEquals(RST_INVALID_STREAM, rstStream.statusCode);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(2, ping.streamId);
+    }
+
+    @Test public void clientClosesClientOutputStream() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN_STREAM
+        peer.acceptFrame(); // TYPE_DATA
+        peer.acceptFrame(); // TYPE_DATA with FLAG_FIN
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame(); // PING response
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, false);
+        OutputStream out = stream.getOutputStream();
+        out.write("square".getBytes(UTF_8));
+        out.flush();
+        assertEquals(1, connection.openStreamCount());
+        out.close();
+        try {
+            out.write("round".getBytes(UTF_8));
+            fail();
+        } catch (Exception expected) {
+            assertEquals("stream closed", expected.getMessage());
+        }
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        assertEquals(FLAG_UNIDIRECTIONAL, synStream.flags);
+        MockSpdyPeer.InFrame data = peer.takeFrame();
+        assertEquals(TYPE_DATA, data.type);
+        assertEquals(0, data.flags);
+        assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
+        MockSpdyPeer.InFrame fin = peer.takeFrame();
+        assertEquals(TYPE_DATA, fin.type);
+        assertEquals(FLAG_FIN, fin.flags);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        assertEquals(2, ping.streamId);
+    }
+
+    @Test public void serverClosesClientOutputStream() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN_STREAM
+        peer.sendFrame().synReset(1, SpdyStream.RST_CANCEL);
+        peer.acceptFrame(); // PING
+        peer.sendFrame().ping(0, 1);
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
+        OutputStream out = stream.getOutputStream();
+        connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received.
+        try {
+            out.write("square".getBytes(UTF_8));
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream was reset: CANCEL", expected.getMessage());
+        }
+        out.close();
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        assertEquals(0, synStream.flags);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        assertEquals(1, ping.streamId);
+    }
+
+    /**
+     * Test that the client sends a RST_STREAM if doing so won't disrupt the
+     * output stream.
+     */
+    @Test public void clientClosesClientInputStream() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN_STREAM
+        peer.acceptFrame(); // RST_STREAM
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true);
+        InputStream in = stream.getInputStream();
+        OutputStream out = stream.getOutputStream();
+        in.close();
+        try {
+            in.read();
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream closed", expected.getMessage());
+        }
+        try {
+            out.write('a');
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream finished", expected.getMessage());
+        }
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        assertEquals(SpdyConnection.FLAG_FIN, synStream.flags);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode);
+    }
+
+    /**
+     * Test that the client doesn't send a RST_STREAM if doing so will disrupt
+     * the output stream.
+     */
+    @Test public void clientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN_STREAM
+        peer.acceptFrame(); // DATA
+        peer.acceptFrame(); // DATA with FLAG_FIN
+        peer.acceptFrame(); // RST_STREAM
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
+        InputStream in = stream.getInputStream();
+        OutputStream out = stream.getOutputStream();
+        in.close();
+        try {
+            in.read();
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream closed", expected.getMessage());
+        }
+        out.write("square".getBytes(UTF_8));
+        out.flush();
+        out.close();
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        assertEquals(0, synStream.flags);
+        MockSpdyPeer.InFrame data = peer.takeFrame();
+        assertEquals(TYPE_DATA, data.type);
+        assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
+        MockSpdyPeer.InFrame fin = peer.takeFrame();
+        assertEquals(TYPE_DATA, fin.type);
+        assertEquals(FLAG_FIN, fin.flags);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode);
+    }
+
+    @Test public void serverClosesClientInputStream() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN_STREAM
+        peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8));
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(REJECT_INCOMING_STREAMS)
+                .build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true);
+        InputStream in = stream.getInputStream();
+        assertStreamData("square", in);
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        assertEquals(SpdyConnection.FLAG_FIN, synStream.flags);
+    }
+
+    @Test public void remoteDoubleSynReply() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+        peer.acceptFrame(); // PING
+        peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
+        peer.sendFrame().ping(0, 1);
+        peer.acceptFrame(); // RST STREAM
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("c", "cola"), true, true);
+        assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders());
+        connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received.
+        try {
+            stream.getInputStream().read();
+            fail();
+        } catch (IOException e) {
+            assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage());
+        }
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(1, rstStream.streamId);
+        assertEquals(0, rstStream.flags);
+        assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode);
+    }
+
+    @Test public void remoteDoubleSynStream() throws Exception {
+        // write the mocking script
+        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
+        peer.acceptFrame();
+        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("b", "banana"));
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        final AtomicInteger receiveCount = new AtomicInteger();
+        IncomingStreamHandler handler = new IncomingStreamHandler() {
+            @Override public void receive(SpdyStream stream) throws IOException {
+                receiveCount.incrementAndGet();
+                assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
+                assertEquals(-1, stream.getRstStatusCode());
+                stream.reply(Arrays.asList("c", "cola"), true);
+            }
+        };
+        new SpdyConnection.Builder(true, peer.openSocket())
+                .handler(handler)
+                .build();
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame reply = peer.takeFrame();
+        assertEquals(TYPE_SYN_REPLY, reply.type);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(2, rstStream.streamId);
+        assertEquals(0, rstStream.flags);
+        assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode);
+        assertEquals(1, receiveCount.intValue());
+    }
+
+    @Test public void remoteSendsDataAfterInFinished() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+        peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
+        peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "c3po".getBytes("UTF-8")); // Ignored.
+        peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded.
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
+        assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders());
+        assertStreamData("robot", stream.getInputStream());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        assertEquals(2, ping.streamId);
+        assertEquals(0, ping.flags);
+    }
+
+    @Test public void remoteSendsTooMuchData() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
+        peer.sendFrame().data(0, 1, new byte[64 * 1024 + 1]);
+        peer.acceptFrame();
+        peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded.
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
+        assertEquals(Arrays.asList("b", "banana"), stream.getResponseHeaders());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(1, rstStream.streamId);
+        assertEquals(0, rstStream.flags);
+        assertEquals(RST_FLOW_CONTROL_ERROR, rstStream.statusCode);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        assertEquals(2, ping.streamId);
+    }
+
+    @Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception {
+        // write the mocking script
+        peer.acceptFrame();
+        peer.sendFrame().synReset(1, RST_REFUSED_STREAM);
+        peer.sendFrame().ping(0, 2);
+        peer.acceptFrame();
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
+        try {
+            stream.getResponseHeaders();
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage());
+        }
+        assertEquals(0, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        assertEquals(2, ping.streamId);
+        assertEquals(0, ping.flags);
+    }
+
+    @Test public void receiveGoAway() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM 1
+        peer.acceptFrame(); // SYN STREAM 3
+        peer.sendFrame().goAway(0, 1);
+        peer.acceptFrame(); // PING
+        peer.sendFrame().ping(0, 1);
+        peer.acceptFrame(); // DATA STREAM 1
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream1 = connection.newStream(Arrays.asList("a", "android"), true, true);
+        SpdyStream stream2 = connection.newStream(Arrays.asList("b", "banana"), true, true);
+        connection.ping().roundTripTime(); // Ensure that the GO_AWAY has been received.
+        stream1.getOutputStream().write("abc".getBytes(UTF_8));
+        try {
+            stream2.getOutputStream().write("abc".getBytes(UTF_8));
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage());
+        }
+        stream1.getOutputStream().write("def".getBytes(UTF_8));
+        stream1.getOutputStream().close();
+        try {
+            connection.newStream(Arrays.asList("c", "cola"), true, true);
+            fail();
+        } catch (IOException expected) {
+            assertEquals("shutdown", expected.getMessage());
+        }
+        assertEquals(1, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream1 = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream1.type);
+        MockSpdyPeer.InFrame synStream2 = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream2.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        MockSpdyPeer.InFrame data1 = peer.takeFrame();
+        assertEquals(TYPE_DATA, data1.type);
+        assertEquals(1, data1.streamId);
+        assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data));
+    }
+
+    @Test public void sendGoAway() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM 1
+        peer.acceptFrame(); // GOAWAY
+        peer.acceptFrame(); // PING
+        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("b", "banana")); // Should be ignored!
+        peer.sendFrame().ping(0, 1);
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        connection.newStream(Arrays.asList("a", "android"), true, true);
+        Ping ping = connection.ping();
+        connection.shutdown();
+        ping.roundTripTime(); // Ensure that the SYN STREAM has been received.
+        assertEquals(1, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream1 = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream1.type);
+        MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
+        assertEquals(TYPE_PING, pingFrame.type);
+        MockSpdyPeer.InFrame goaway = peer.takeFrame();
+        assertEquals(TYPE_GOAWAY, goaway.type);
+        assertEquals(0, goaway.streamId);
+    }
+
+    @Test public void noPingsAfterShutdown() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // GOAWAY
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        connection.shutdown();
+        try {
+            connection.ping();
+            fail();
+        } catch (IOException expected) {
+            assertEquals("shutdown", expected.getMessage());
+        }
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame goaway = peer.takeFrame();
+        assertEquals(TYPE_GOAWAY, goaway.type);
+    }
+
+    @Test public void close() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM
+        peer.acceptFrame(); // GOAWAY
+        peer.acceptFrame(); // RST STREAM
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
+        assertEquals(1, connection.openStreamCount());
+        connection.close();
+        assertEquals(0, connection.openStreamCount());
+        try {
+            connection.newStream(Arrays.asList("b", "banana"), true, true);
+            fail();
+        } catch (IOException expected) {
+            assertEquals("shutdown", expected.getMessage());
+        }
+        try {
+            stream.getOutputStream().write(0);
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream was reset: CANCEL", expected.getMessage());
+        }
+        try {
+            stream.getInputStream().read();
+            fail();
+        } catch (IOException expected) {
+            assertEquals("stream was reset: CANCEL", expected.getMessage());
+        }
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame goaway = peer.takeFrame();
+        assertEquals(TYPE_GOAWAY, goaway.type);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(1, rstStream.streamId);
+    }
+
+    @Test public void closeCancelsPings() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // PING
+        peer.acceptFrame(); // GOAWAY
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        Ping ping = connection.ping();
+        connection.close();
+        assertEquals(-1, ping.roundTripTime());
+    }
+
+    @Test public void readTimeoutExpires() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM
+        peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
+        stream.setReadTimeout(1000);
+        InputStream in = stream.getInputStream();
+        long startNanos = System.nanoTime();
+        try {
+            in.read();
+            fail();
+        } catch (IOException expected) {
+        }
+        long elapsedNanos = System.nanoTime() - startNanos;
+        assertEquals(1000d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */);
+        assertEquals(1, connection.openStreamCount());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+    }
+
+    @Test public void headers() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM
+        peer.acceptFrame(); // PING
+        peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
+        peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po"));
+        peer.sendFrame().ping(0, 1);
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
+        connection.ping().roundTripTime(); // Ensure that the HEADERS has been received.
+        assertEquals(Arrays.asList("a", "android", "c", "c3po"), stream.getResponseHeaders());
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+    }
+
+    @Test public void headersBeforeReply() throws Exception {
+        // write the mocking script
+        peer.acceptFrame(); // SYN STREAM
+        peer.acceptFrame(); // PING
+        peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po"));
+        peer.acceptFrame(); // RST STREAM
+        peer.sendFrame().ping(0, 1);
+        peer.play();
+
+        // play it back
+        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
+        SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
+        connection.ping().roundTripTime(); // Ensure that the HEADERS has been received.
+        try {
+            stream.getResponseHeaders();
+            fail();
+        } catch (IOException e) {
+            assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage());
+        }
+
+        // verify the peer received what was expected
+        MockSpdyPeer.InFrame synStream = peer.takeFrame();
+        assertEquals(TYPE_SYN_STREAM, synStream.type);
+        MockSpdyPeer.InFrame ping = peer.takeFrame();
+        assertEquals(TYPE_PING, ping.type);
+        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
+        assertEquals(TYPE_RST_STREAM, rstStream.type);
+        assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode);
+    }
+
+    private void writeAndClose(SpdyStream stream, String data) throws IOException {
+        OutputStream out = stream.getOutputStream();
+        out.write(data.getBytes("UTF-8"));
+        out.close();
+    }
+
+    private void assertStreamData(String expected, InputStream inputStream) throws IOException {
+        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        for (int count; (count = inputStream.read(buffer)) != -1; ) {
+            bytesOut.write(buffer, 0, count);
+        }
+        String actual = bytesOut.toString("UTF-8");
+        assertEquals(expected, actual);
+    }
+}
diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java
new file mode 100644
index 0000000..1b5574d
--- /dev/null
+++ b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.SslContextBuilder;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import org.eclipse.jetty.npn.NextProtoNego;
+
+/**
+ * A basic SPDY server that serves the contents of a local directory.
+ */
+public final class SpdyServer implements IncomingStreamHandler {
+    private final File baseDirectory;
+    private SSLSocketFactory sslSocketFactory;
+
+    public SpdyServer(File baseDirectory) {
+        this.baseDirectory = baseDirectory;
+    }
+
+    public void useHttps(SSLSocketFactory sslSocketFactory) {
+        this.sslSocketFactory = sslSocketFactory;
+    }
+
+    private void run() throws Exception {
+        ServerSocket serverSocket = new ServerSocket(8888);
+        serverSocket.setReuseAddress(true);
+
+        while (true) {
+            Socket socket = serverSocket.accept();
+            if (sslSocketFactory != null) {
+                socket = doSsl(socket);
+            }
+            new SpdyConnection.Builder(false, socket).handler(this).build();
+        }
+    }
+
+    private Socket doSsl(Socket socket) throws IOException {
+        SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket,
+                socket.getInetAddress().getHostAddress(), socket.getPort(), true);
+        sslSocket.setUseClientMode(false);
+        NextProtoNego.put(sslSocket, new NextProtoNego.ServerProvider() {
+            @Override public void unsupported() {
+                System.out.println("UNSUPPORTED");
+            }
+            @Override public List<String> protocols() {
+                return Arrays.asList("spdy/2");
+            }
+            @Override public void protocolSelected(String protocol) {
+                System.out.println("PROTOCOL SELECTED: " + protocol);
+            }
+        });
+        return sslSocket;
+    }
+
+    @Override public void receive(final SpdyStream stream) throws IOException {
+        List<String> requestHeaders = stream.getRequestHeaders();
+        String path = null;
+        for (int i = 0; i < requestHeaders.size(); i += 2) {
+            String s = requestHeaders.get(i);
+            if ("url".equals(s)) {
+                path = requestHeaders.get(i + 1);
+                break;
+            }
+        }
+
+        if (path == null) {
+            // TODO: send bad request error
+            throw new AssertionError();
+        }
+
+        File file = new File(baseDirectory + path);
+
+        if (file.isDirectory()) {
+            serveDirectory(stream, file.list());
+        } else if (file.exists()) {
+            serveFile(stream, file);
+        } else {
+            send404(stream, path);
+        }
+    }
+
+    private void send404(SpdyStream stream, String path) throws IOException {
+        List<String> responseHeaders = Arrays.asList(
+                "status", "404",
+                "version", "HTTP/1.1",
+                "content-type", "text/plain"
+        );
+        stream.reply(responseHeaders, true);
+        OutputStream out = stream.getOutputStream();
+        String text = "Not found: " + path;
+        out.write(text.getBytes("UTF-8"));
+        out.close();
+    }
+
+    private void serveDirectory(SpdyStream stream, String[] files) throws IOException {
+        List<String> responseHeaders = Arrays.asList(
+                "status", "200",
+                "version", "HTTP/1.1",
+                "content-type", "text/html; charset=UTF-8"
+        );
+        stream.reply(responseHeaders, true);
+        OutputStream out = stream.getOutputStream();
+        Writer writer = new OutputStreamWriter(out, "UTF-8");
+        for (String file : files) {
+            writer.write("<a href='" + file + "'>" + file + "</a><br>");
+        }
+        writer.close();
+    }
+
+    private void serveFile(SpdyStream stream, File file) throws IOException {
+        InputStream in = new FileInputStream(file);
+        byte[] buffer = new byte[8192];
+        stream.reply(Arrays.asList(
+                "status", "200",
+                "version", "HTTP/1.1",
+                "content-type", contentType(file)
+        ), true);
+        OutputStream out = stream.getOutputStream();
+        int count;
+        while ((count = in.read(buffer)) != -1) {
+            out.write(buffer, 0, count);
+        }
+        out.close();
+    }
+
+    private String contentType(File file) {
+        return file.getName().endsWith(".html") ? "text/html" : "text/plain";
+    }
+
+    public static void main(String... args) throws Exception {
+        if (args.length != 1 || args[0].startsWith("-")) {
+            System.out.println("Usage: SpdyServer <base directory>");
+            return;
+        }
+
+        SpdyServer server = new SpdyServer(new File(args[0]));
+        SSLContext sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName())
+                .build();
+        server.useHttps(sslContext.getSocketFactory());
+        server.run();
+    }
+}
diff --git a/src/test/java/libcore/io/StrictLineReaderTest.java b/src/test/java/libcore/io/StrictLineReaderTest.java
deleted file mode 100644
index 2b9e95e..0000000
--- a/src/test/java/libcore/io/StrictLineReaderTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.io;
-
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import junit.framework.TestCase;
-import libcore.util.Charsets;
-
-public class StrictLineReaderTest extends TestCase {
-
-    public void testLineReaderConsistencyWithReadAsciiLine () {
-        try {
-            // Testing with LineReader buffer capacity 32 to check some corner cases.
-            StrictLineReader lineReader = new StrictLineReader(createTestInputStream(), 32,
-                    Charsets.US_ASCII);
-            InputStream refStream = createTestInputStream();
-            while (true) {
-                try {
-                    String refLine = Streams.readAsciiLine(refStream);
-                    try {
-                        String line = lineReader.readLine();
-                        if (!refLine.equals(line)) {
-                            fail("line (\""+line+"\") differs from expected (\""+refLine+"\").");
-                        }
-                    } catch (EOFException eof) {
-                        fail("line reader threw EOFException too early.");
-                    }
-                } catch (EOFException refEof) {
-                    try {
-                        lineReader.readLine();
-                        fail("line reader didn't throw the expected EOFException.");
-                    } catch (EOFException eof) {
-                        // OK
-                        break;
-                    }
-                }
-            }
-            refStream.close();
-            lineReader.close();
-        } catch (IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        }
-    }
-
-    private InputStream createTestInputStream() {
-        return new ByteArrayInputStream((
-                /* each source lines below should represent 32 bytes, until the next comment */
-                "12 byte line\n18 byte line......\n" +
-                        "pad\nline spanning two 32-byte bu" +
-                        "ffers\npad......................\n" +
-                        "pad\nline spanning three 32-byte " +
-                        "buffers and ending with LF at th" +
-                        "e end of a 32 byte buffer......\n" +
-                        "pad\nLine ending with CRLF split" +
-                        " at the end of a 32-byte buffer\r" +
-                        "\npad...........................\n" +
-                        /* end of 32-byte lines */
-                        "line ending with CRLF\r\n" +
-                        "this is a long line with embedded CR \r ending with CRLF and having more than " +
-                        "32 characters\r\n" +
-                        "unterminated line - should be dropped"
-        ).getBytes());
-    }
-}
-
diff --git a/src/test/java/libcore/net/http/NewURLConnectionTest.java b/src/test/java/libcore/net/http/NewURLConnectionTest.java
deleted file mode 100644
index 8c6121e..0000000
--- a/src/test/java/libcore/net/http/NewURLConnectionTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net.http;
-
-import junit.framework.TestCase;
-
-public final class NewURLConnectionTest extends TestCase {
-
-    public void testUrlConnection() {
-    }
-
-    // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1)
-
-    // TODO: write a test that shows POST bodies are retained on AUTH problems (or prove it unnecessary)
-
-    // TODO: cookies + trailers. Do cookie headers get processed too many times?
-
-    // TODO: crash on header names or values containing the '\0' character
-
-    // TODO: crash on empty names and empty values
-
-    // TODO: deflate compression
-
-    // TODO: read the outgoing status line and incoming status line?
-
-}
diff --git a/src/test/java/libcore/net/spdy/SpdyConnectionTest.java b/src/test/java/libcore/net/spdy/SpdyConnectionTest.java
deleted file mode 100644
index 4009599..0000000
--- a/src/test/java/libcore/net/spdy/SpdyConnectionTest.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.net.spdy;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import junit.framework.TestCase;
-
-import static libcore.net.spdy.Settings.PERSIST_VALUE;
-import static libcore.net.spdy.SpdyConnection.FLAG_FIN;
-import static libcore.net.spdy.SpdyConnection.TYPE_DATA;
-import static libcore.net.spdy.SpdyConnection.TYPE_NOOP;
-import static libcore.net.spdy.SpdyConnection.TYPE_PING;
-import static libcore.net.spdy.SpdyConnection.TYPE_RST_STREAM;
-import static libcore.net.spdy.SpdyConnection.TYPE_SYN_REPLY;
-import static libcore.net.spdy.SpdyConnection.TYPE_SYN_STREAM;
-import static libcore.net.spdy.SpdyStream.RST_INVALID_STREAM;
-import static libcore.util.Charsets.UTF_8;
-
-public final class SpdyConnectionTest extends TestCase {
-    private static final IncomingStreamHandler REJECT_INCOMING_STREAMS
-            = new IncomingStreamHandler() {
-        @Override public void receive(SpdyStream stream) throws IOException {
-            throw new AssertionError();
-        }
-    };
-    private final MockSpdyPeer peer = new MockSpdyPeer();
-
-    public void testClientCreatesStreamAndServerReplies() throws Exception {
-        // write the mocking script
-        peer.acceptFrame();
-        peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
-        peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
-        SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
-        assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders());
-        assertStreamData("robot", stream.getInputStream());
-        writeAndClose(stream, "c3po");
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame synStream = peer.takeFrame();
-        assertEquals(TYPE_SYN_STREAM, synStream.type);
-        assertEquals(0, synStream.flags);
-        assertEquals(1, synStream.streamId);
-        assertEquals(0, synStream.associatedStreamId);
-        assertEquals(Arrays.asList("b", "banana"), synStream.nameValueBlock);
-        MockSpdyPeer.InFrame requestData = peer.takeFrame();
-        assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data));
-    }
-
-    public void testServerCreatesStreamAndClientReplies() throws Exception {
-        // write the mocking script
-        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        final AtomicInteger receiveCount = new AtomicInteger();
-        IncomingStreamHandler handler = new IncomingStreamHandler() {
-            @Override public void receive(SpdyStream stream) throws IOException {
-                receiveCount.incrementAndGet();
-                assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
-                assertEquals(-1, stream.getRstStatusCode());
-                stream.reply(Arrays.asList("b", "banana"), true);
-
-            }
-        };
-        new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(handler)
-                .build();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame reply = peer.takeFrame();
-        assertEquals(TYPE_SYN_REPLY, reply.type);
-        assertEquals(0, reply.flags);
-        assertEquals(2, reply.streamId);
-        assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock);
-        assertEquals(1, receiveCount.get());
-    }
-
-    public void testReplyWithNoData() throws Exception {
-        // write the mocking script
-        peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        final AtomicInteger receiveCount = new AtomicInteger();
-        IncomingStreamHandler handler = new IncomingStreamHandler() {
-            @Override public void receive(SpdyStream stream) throws IOException {
-                stream.reply(Arrays.asList("b", "banana"), false);
-                receiveCount.incrementAndGet();
-            }
-        };
-        new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(handler)
-                .build();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame reply = peer.takeFrame();
-        assertEquals(TYPE_SYN_REPLY, reply.type);
-        assertEquals(FLAG_FIN, reply.flags);
-        assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock);
-        assertEquals(1, receiveCount.get());
-    }
-
-    public void testNoop() throws Exception {
-        // write the mocking script
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        connection.noop();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame ping = peer.takeFrame();
-        assertEquals(TYPE_NOOP, ping.type);
-        assertEquals(0, ping.flags);
-    }
-
-    public void testServerPingsClient() throws Exception {
-        // write the mocking script
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame ping = peer.takeFrame();
-        assertEquals(TYPE_PING, ping.type);
-        assertEquals(0, ping.flags);
-        assertEquals(2, ping.streamId);
-    }
-
-    public void testClientPingsServer() throws Exception {
-        // write the mocking script
-        peer.acceptFrame();
-        peer.sendFrame().ping(0, 1);
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        Ping ping = connection.ping();
-        assertTrue(ping.roundTripTime() > 0);
-        assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1));
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
-        assertEquals(TYPE_PING, pingFrame.type);
-        assertEquals(0, pingFrame.flags);
-        assertEquals(1, pingFrame.streamId);
-    }
-
-    public void testUnexpectedPingIsNotReturned() throws Exception {
-        // write the mocking script
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame();
-        peer.sendFrame().ping(0, 3); // This ping will not be returned.
-        peer.sendFrame().ping(0, 4);
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame ping2 = peer.takeFrame();
-        assertEquals(2, ping2.streamId);
-        MockSpdyPeer.InFrame ping4 = peer.takeFrame();
-        assertEquals(4, ping4.streamId);
-    }
-
-    public void testServerSendsSettingsToClient() throws Exception {
-        // write the mocking script
-        Settings settings = new Settings();
-        settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10);
-        peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings);
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-
-        peer.takeFrame(); // Guarantees that the Settings frame has been processed.
-        synchronized (connection) {
-            assertEquals(10, connection.settings.getMaxConcurrentStreams(-1));
-        }
-    }
-
-    public void testMultipleSettingsFramesAreMerged() throws Exception {
-        // write the mocking script
-        Settings settings1 = new Settings();
-        settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100);
-        settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200);
-        settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300);
-        peer.sendFrame().settings(0, settings1);
-        Settings settings2 = new Settings();
-        settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400);
-        settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500);
-        settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600);
-        peer.sendFrame().settings(0, settings2);
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame();
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-
-        peer.takeFrame(); // Guarantees that the Settings frame has been processed.
-        synchronized (connection) {
-            assertEquals(100, connection.settings.getUploadBandwidth(-1));
-            assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.UPLOAD_BANDWIDTH));
-            assertEquals(400, connection.settings.getDownloadBandwidth(-1));
-            assertEquals(0, connection.settings.flags(Settings.DOWNLOAD_BANDWIDTH));
-            assertEquals(500, connection.settings.getDownloadRetransRate(-1));
-            assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.DOWNLOAD_RETRANS_RATE));
-            assertEquals(600, connection.settings.getMaxConcurrentStreams(-1));
-            assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.MAX_CONCURRENT_STREAMS));
-        }
-    }
-
-    public void testBogusDataFrameDoesNotDisruptConnection() throws Exception {
-        // write the mocking script
-        peer.sendFrame().data(SpdyConnection.FLAG_FIN, 42, "bogus".getBytes("UTF-8"));
-        peer.acceptFrame(); // RST_STREAM
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame(); // PING
-        peer.play();
-
-        // play it back
-        new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
-        assertEquals(TYPE_RST_STREAM, rstStream.type);
-        assertEquals(0, rstStream.flags);
-        assertEquals(42, rstStream.streamId);
-        assertEquals(RST_INVALID_STREAM, rstStream.statusCode);
-        MockSpdyPeer.InFrame ping = peer.takeFrame();
-        assertEquals(2, ping.streamId);
-    }
-
-    public void testBogusReplyFrameDoesNotDisruptConnection() throws Exception {
-        // write the mocking script
-        peer.sendFrame().synReply(0, 42, Arrays.asList("a", "android"));
-        peer.acceptFrame(); // RST_STREAM
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame(); // PING
-        peer.play();
-
-        // play it back
-        new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
-        assertEquals(TYPE_RST_STREAM, rstStream.type);
-        assertEquals(0, rstStream.flags);
-        assertEquals(42, rstStream.streamId);
-        assertEquals(RST_INVALID_STREAM, rstStream.statusCode);
-        MockSpdyPeer.InFrame ping = peer.takeFrame();
-        assertEquals(2, ping.streamId);
-    }
-
-    public void testClientClosesClientOutputStream() throws Exception {
-        // write the mocking script
-        peer.acceptFrame(); // SYN_STREAM
-        peer.acceptFrame(); // TYPE_DATA
-        peer.acceptFrame(); // TYPE_DATA with FLAG_FIN
-        peer.sendFrame().ping(0, 2);
-        peer.acceptFrame(); // PING response
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
-        OutputStream out = stream.getOutputStream();
-        out.write("square".getBytes(UTF_8));
-        out.flush();
-        out.close();
-        try {
-            out.write("round".getBytes(UTF_8));
-            fail();
-        } catch (Exception expected) {
-        }
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame synStream = peer.takeFrame();
-        assertEquals(TYPE_SYN_STREAM, synStream.type);
-        assertEquals(0, synStream.flags);
-        MockSpdyPeer.InFrame data = peer.takeFrame();
-        assertEquals(TYPE_DATA, data.type);
-        assertEquals(0, data.flags);
-        assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
-        MockSpdyPeer.InFrame fin = peer.takeFrame();
-        assertEquals(TYPE_DATA, fin.type);
-        assertEquals(FLAG_FIN, fin.flags);
-        MockSpdyPeer.InFrame ping = peer.takeFrame();
-        assertEquals(TYPE_PING, ping.type);
-        assertEquals(2, ping.streamId);
-    }
-
-    public void testServerClosesClientOutputStream() throws Exception {
-        // write the mocking script
-        peer.acceptFrame(); // SYN_STREAM
-        peer.sendFrame().synReset(1, SpdyStream.RST_CANCEL);
-        peer.acceptFrame(); // PING
-        peer.sendFrame().ping(0, 1);
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
-        OutputStream out = stream.getOutputStream();
-        connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received.
-        try {
-            out.write("square".getBytes(UTF_8));
-            fail();
-        } catch (IOException expected) {
-        }
-        out.close();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame synStream = peer.takeFrame();
-        assertEquals(TYPE_SYN_STREAM, synStream.type);
-        assertEquals(0, synStream.flags);
-        MockSpdyPeer.InFrame ping = peer.takeFrame();
-        assertEquals(TYPE_PING, ping.type);
-        assertEquals(1, ping.streamId);
-    }
-
-    /**
-     * Test that the client sends a RST_STREAM if doing so won't disrupt the
-     * output stream.
-     */
-    public void testClientClosesClientInputStream() throws Exception {
-        // write the mocking script
-        peer.acceptFrame(); // SYN_STREAM
-        peer.acceptFrame(); // RST_STREAM
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true);
-        InputStream in = stream.getInputStream();
-        OutputStream out = stream.getOutputStream();
-        in.close();
-        try {
-            in.read();
-            fail();
-        } catch (IOException expected) {
-        }
-        try {
-            out.write('a');
-            fail();
-        } catch (IOException expected) {
-        }
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame synStream = peer.takeFrame();
-        assertEquals(TYPE_SYN_STREAM, synStream.type);
-        assertEquals(SpdyConnection.FLAG_FIN, synStream.flags);
-
-        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
-        assertEquals(TYPE_RST_STREAM, rstStream.type);
-        assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode);
-    }
-
-    /**
-     * Test that the client doesn't send a RST_STREAM if doing so will disrupt
-     * the output stream.
-     */
-    public void testClientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception {
-        // write the mocking script
-        peer.acceptFrame(); // SYN_STREAM
-        peer.acceptFrame(); // DATA
-        peer.acceptFrame(); // DATA with FLAG_FIN
-        peer.acceptFrame(); // RST_STREAM
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true);
-        InputStream in = stream.getInputStream();
-        OutputStream out = stream.getOutputStream();
-        in.close();
-        try {
-            in.read();
-            fail();
-        } catch (IOException expected) {
-        }
-        out.write("square".getBytes(UTF_8));
-        out.flush();
-        out.close();
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame synStream = peer.takeFrame();
-        assertEquals(TYPE_SYN_STREAM, synStream.type);
-        assertEquals(0, synStream.flags);
-
-        MockSpdyPeer.InFrame data = peer.takeFrame();
-        assertEquals(TYPE_DATA, data.type);
-        assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
-
-        MockSpdyPeer.InFrame fin = peer.takeFrame();
-        assertEquals(TYPE_DATA, fin.type);
-        assertEquals(FLAG_FIN, fin.flags);
-
-        MockSpdyPeer.InFrame rstStream = peer.takeFrame();
-        assertEquals(TYPE_RST_STREAM, rstStream.type);
-        assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode);
-    }
-
-    public void testServerClosesClientInputStream() throws Exception {
-        // write the mocking script
-        peer.acceptFrame(); // SYN_STREAM
-        peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8));
-        peer.play();
-
-        // play it back
-        SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
-                .handler(REJECT_INCOMING_STREAMS)
-                .build();
-        SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true);
-        InputStream in = stream.getInputStream();
-        assertStreamData("square", in);
-
-        // verify the peer received what was expected
-        MockSpdyPeer.InFrame synStream = peer.takeFrame();
-        assertEquals(TYPE_SYN_STREAM, synStream.type);
-        assertEquals(SpdyConnection.FLAG_FIN, synStream.flags);
-    }
-
-    public void testRemoteDoubleReply() {
-        // We should get a PROTOCOL ERROR
-        // TODO
-    }
-
-    public void testRemoteSendsDataAfterInFinished() {
-        // We have a bug where we don't fastfoward the stream
-        // TODO
-    }
-
-    public void testRemoteSendsTooMuchData() {
-        // We should send RST_FLOW_CONTROL_ERROR (and fastforward the stream)
-        // TODO
-    }
-
-    public void testRemoteSendsRefusedStreamBeforeReplyHeaders() {
-        // Calling getResponseHeaders() should throw an IOException if the stream is refused.
-        // TODO
-    }
-
-    private void writeAndClose(SpdyStream stream, String data) throws IOException {
-        OutputStream out = stream.getOutputStream();
-        out.write(data.getBytes("UTF-8"));
-        out.close();
-    }
-
-    private void assertStreamData(String expected, InputStream inputStream) throws IOException {
-        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        for (int count; (count = inputStream.read(buffer)) != -1; ) {
-            bytesOut.write(buffer, 0, count);
-        }
-        String actual = bytesOut.toString("UTF-8");
-        assertEquals(expected, actual);
-    }
-}