Merge "Add an API to expose Next Protocol Negotiation (NPN)."
diff --git a/api/current.txt b/api/current.txt
index bebbf36..2a64ee1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11924,8 +11924,10 @@
     method public java.lang.String[] getDefaultCipherSuites();
     method public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache);
     method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
+    method public byte[] getNpnSelectedProtocol(java.net.Socket);
     method public java.lang.String[] getSupportedCipherSuites();
     method public void setKeyManagers(javax.net.ssl.KeyManager[]);
+    method public void setNpnProtocols(byte[][]);
     method public void setTrustManagers(javax.net.ssl.TrustManager[]);
   }
 
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 5c4b258..6a4f1f2 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -18,13 +18,11 @@
 
 import android.os.SystemProperties;
 import android.util.Log;
-
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
 import java.security.KeyManagementException;
 import java.security.cert.X509Certificate;
-
 import javax.net.SocketFactory;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
@@ -36,7 +34,6 @@
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
-
 import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
 import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
@@ -89,6 +86,7 @@
     private SSLSocketFactory mSecureFactory = null;
     private TrustManager[] mTrustManagers = null;
     private KeyManager[] mKeyManagers = null;
+    private byte[] mNpnProtocols = null;
 
     private final int mHandshakeTimeoutMillis;
     private final SSLClientSessionCache mSessionCache;
@@ -251,6 +249,60 @@
     }
 
     /**
+     * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
+     * Protocol Negotiation (NPN)</a> protocols that this peer is interested in.
+     *
+     * <p>For servers this is the sequence of protocols to advertise as
+     * supported, in order of preference. This list is sent unencrypted to
+     * all clients that support NPN.
+     *
+     * <p>For clients this is a list of supported protocols to match against the
+     * server's list. If there is no protocol supported by both client and
+     * server then the first protocol in the client's list will be selected.
+     * The order of the client's protocols is otherwise insignificant.
+     *
+     * @param npnProtocols a possibly-empty list of protocol byte arrays. All
+     *     arrays must be non-empty and of length less than 256.
+     */
+    public void setNpnProtocols(byte[][] npnProtocols) {
+        this.mNpnProtocols = toNpnProtocolsList(npnProtocols);
+    }
+
+    /**
+     * Returns an array containing the concatenation of length-prefixed byte
+     * strings.
+     */
+    static byte[] toNpnProtocolsList(byte[]... npnProtocols) {
+        int totalLength = 0;
+        for (byte[] s : npnProtocols) {
+            if (s.length == 0 || s.length > 255) {
+                throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length);
+            }
+            totalLength += 1 + s.length;
+        }
+        byte[] result = new byte[totalLength];
+        int pos = 0;
+        for (byte[] s : npnProtocols) {
+            result[pos++] = (byte) s.length;
+            for (byte b : s) {
+                result[pos++] = b;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
+     * Protocol Negotiation (NPN)</a> protocol selected by client and server, or
+     * null if no protocol was negotiated.
+     *
+     * @param socket a socket created by this factory.
+     */
+    public byte[] getNpnSelectedProtocol(Socket socket) {
+        return ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol();
+    }
+
+    /**
      * Sets the {@link KeyManager}s to be used for connections made by this factory.
      */
     public void setKeyManagers(KeyManager[] keyManagers) {
@@ -271,6 +323,7 @@
     @Override
     public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         if (mSecure) {
             verifyHostname(s, host);
@@ -289,6 +342,7 @@
     @Override
     public Socket createSocket() throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         return s;
     }
@@ -305,6 +359,7 @@
             throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
                 addr, port, localAddr, localPort);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         return s;
     }
@@ -319,6 +374,7 @@
     @Override
     public Socket createSocket(InetAddress addr, int port) throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         return s;
     }
@@ -334,6 +390,7 @@
             throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
                 host, port, localAddr, localPort);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         if (mSecure) {
             verifyHostname(s, host);
@@ -350,6 +407,7 @@
     @Override
     public Socket createSocket(String host, int port) throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         if (mSecure) {
             verifyHostname(s, host);
diff --git a/core/tests/coretests/src/android/net/SSLTest.java b/core/tests/coretests/src/android/net/SSLTest.java
index 810ed0d..c573498 100644
--- a/core/tests/coretests/src/android/net/SSLTest.java
+++ b/core/tests/coretests/src/android/net/SSLTest.java
@@ -16,17 +16,16 @@
 
 package android.net;
 
-import android.net.SSLCertificateSocketFactory;
 import android.test.suitebuilder.annotation.Suppress;
-import junit.framework.TestCase;
-
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.util.Arrays;
+import junit.framework.TestCase;
 
-//This test relies on network resources.
-@Suppress
 public class SSLTest extends TestCase {
+    //This test relies on network resources.
+    @Suppress
     public void testCertificate() throws Exception {
         // test www.fortify.net/sslcheck.html
         Socket ssl = SSLCertificateSocketFactory.getDefault().createSocket("www.fortify.net",443);
@@ -49,4 +48,35 @@
 
         // System.out.println(new String(b));
     }
+
+    public void testStringsToNpnBytes() {
+        byte[] expected = {
+                6, 's', 'p', 'd', 'y', '/', '2',
+                8, 'h', 't', 't', 'p', '/', '1', '.', '1',
+        };
+        assertTrue(Arrays.equals(expected, SSLCertificateSocketFactory.toNpnProtocolsList(
+                new byte[] { 's', 'p', 'd', 'y', '/', '2' },
+                new byte[] { 'h', 't', 't', 'p', '/', '1', '.', '1' })));
+    }
+
+    public void testStringsToNpnBytesEmptyByteArray() {
+        try {
+            SSLCertificateSocketFactory.toNpnProtocolsList(new byte[0]);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testStringsToNpnBytesEmptyArray() {
+        byte[] expected = {};
+        assertTrue(Arrays.equals(expected, SSLCertificateSocketFactory.toNpnProtocolsList()));
+    }
+
+    public void testStringsToNpnBytesOversizedInput() {
+        try {
+            SSLCertificateSocketFactory.toNpnProtocolsList(new byte[256]);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
 }