• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 Square, Inc.
3  * Copyright (C) 2012 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 /*
18  * Forked from OkHttp 2.5.0
19  */
20 
21 package io.grpc.okhttp.internal;
22 
23 import java.io.IOException;
24 import java.lang.reflect.InvocationHandler;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Proxy;
28 import java.net.InetSocketAddress;
29 import java.net.Socket;
30 import java.net.SocketException;
31 import java.security.AccessController;
32 import java.security.KeyManagementException;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.PrivilegedActionException;
35 import java.security.PrivilegedExceptionAction;
36 import java.security.Provider;
37 import java.security.Security;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42 import javax.net.ssl.SSLContext;
43 import javax.net.ssl.SSLEngine;
44 import javax.net.ssl.SSLParameters;
45 import javax.net.ssl.SSLSocket;
46 import okio.Buffer;
47 
48 /**
49  * Access to platform-specific features.
50  *
51  * <h3>Server name indication (SNI)</h3>
52  *
53  * Supported on Android 2.3+.
54  *
55  * <h3>Session Tickets</h3>
56  *
57  * Supported on Android 2.3+.
58  *
59  * <h3>Android Traffic Stats (Socket Tagging)</h3>
60  *
61  * Supported on Android 4.0+.
62  *
63  * <h3>ALPN (Application Layer Protocol Negotiation)</h3>
64  *
65  * Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
66  * unstable.
67  *
68  * <p>Supported on OpenJDK 9+.
69  *
70  * <p>Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
71  */
72 public class Platform {
73   public static final Logger logger = Logger.getLogger(Platform.class.getName());
74 
75   public enum TlsExtensionType {
76     ALPN_AND_NPN,
77     NPN,
78     NONE,
79   }
80 
81   /**
82    * List of recognized security providers. The first recognized security provider according to the
83    * preference order returned by {@link Security#getProviders} will be selected.
84    */
85   private static final String[] ANDROID_SECURITY_PROVIDERS =
86       new String[] {
87         // See https://developer.android.com/training/articles/security-gms-provider.html
88         "com.google.android.gms.org.conscrypt.OpenSSLProvider",
89         "org.conscrypt.OpenSSLProvider",
90         "com.android.org.conscrypt.OpenSSLProvider",
91         "org.apache.harmony.xnet.provider.jsse.OpenSSLProvider",
92         "com.google.android.libraries.stitch.sslguard.SslGuardProvider"
93       };
94 
95   private static final Platform PLATFORM = findPlatform();
96 
get()97   public static Platform get() {
98     return PLATFORM;
99   }
100 
101   private final Provider sslProvider;
102 
Platform(Provider sslProvider)103   public Platform(Provider sslProvider) {
104     this.sslProvider = sslProvider;
105   }
106 
107   /** Prefix used on custom headers. */
getPrefix()108   public String getPrefix() {
109     return "OkHttp";
110   }
111 
logW(String warning)112   public void logW(String warning) {
113     System.out.println(warning);
114   }
115 
tagSocket(Socket socket)116   public void tagSocket(Socket socket) throws SocketException {
117   }
118 
untagSocket(Socket socket)119   public void untagSocket(Socket socket) throws SocketException {
120   }
121 
getProvider()122   public Provider getProvider() {
123     return sslProvider;
124   }
125 
126   /** Returns the TLS extension type available (ALPN and NPN, NPN, or None). */
getTlsExtensionType()127   public TlsExtensionType getTlsExtensionType() {
128     return TlsExtensionType.NONE;
129   }
130 
131   /**
132    * Configure TLS extensions on {@code sslSocket} for {@code route}.
133    *
134    * @param hostname non-null for client-side handshakes; null for
135    *     server-side handshakes.
136    */
configureTlsExtensions(SSLSocket sslSocket, String hostname, List<Protocol> protocols)137   public void configureTlsExtensions(SSLSocket sslSocket, String hostname,
138       List<Protocol> protocols) {
139   }
140 
141   /**
142    * Called after the TLS handshake to release resources allocated by {@link
143    * #configureTlsExtensions}.
144    */
afterHandshake(SSLSocket sslSocket)145   public void afterHandshake(SSLSocket sslSocket) {
146   }
147 
148   /** Returns the negotiated protocol, or null if no protocol was negotiated. */
getSelectedProtocol(SSLSocket socket)149   public String getSelectedProtocol(SSLSocket socket) {
150     return null;
151   }
152 
connectSocket(Socket socket, InetSocketAddress address, int connectTimeout)153   public void connectSocket(Socket socket, InetSocketAddress address,
154       int connectTimeout) throws IOException {
155     socket.connect(address, connectTimeout);
156   }
157 
158   /** Attempt to match the host runtime to a capable Platform implementation. */
findPlatform()159   private static Platform findPlatform() {
160     Provider androidOrAppEngineProvider = getAndroidSecurityProvider();
161     if (androidOrAppEngineProvider != null) {
162       // Attempt to find Android 2.3+ APIs.
163       OptionalMethod<Socket> setUseSessionTickets
164           = new OptionalMethod<>(null, "setUseSessionTickets", boolean.class);
165       OptionalMethod<Socket> setHostname
166           = new OptionalMethod<>(null, "setHostname", String.class);
167       Method trafficStatsTagSocket = null;
168       Method trafficStatsUntagSocket = null;
169       OptionalMethod<Socket> getAlpnSelectedProtocol =
170           new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
171       OptionalMethod<Socket> setAlpnProtocols =
172           new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
173 
174       // Attempt to find Android 4.0+ APIs.
175       try {
176         Class<?> trafficStats = Class.forName("android.net.TrafficStats");
177         trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);
178         trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);
179       } catch (ClassNotFoundException ignored) {
180         // On older Android
181       } catch (NoSuchMethodException ignored) {
182         // On older Android
183       }
184 
185       TlsExtensionType tlsExtensionType;
186       if (androidOrAppEngineProvider.getName().equals("GmsCore_OpenSSL")
187           || androidOrAppEngineProvider.getName().equals("Conscrypt")
188           || androidOrAppEngineProvider.getName().equals("Ssl_Guard")) {
189         tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
190       } else if (isAtLeastAndroid5()) {
191         tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
192       } else if (isAtLeastAndroid41()) {
193         tlsExtensionType = TlsExtensionType.NPN;
194       } else {
195         tlsExtensionType = TlsExtensionType.NONE;
196       }
197       return new Android(
198           setUseSessionTickets,
199           setHostname,
200           trafficStatsTagSocket,
201           trafficStatsUntagSocket,
202           getAlpnSelectedProtocol,
203           setAlpnProtocols,
204           androidOrAppEngineProvider,
205           tlsExtensionType);
206     }
207     Provider sslProvider;
208     try {
209       sslProvider = SSLContext.getDefault().getProvider();
210     } catch (NoSuchAlgorithmException nsae) {
211       throw new RuntimeException(nsae);
212     }
213 
214     // Find JDK9+ ALPN support
215     try {
216       // getApplicationProtocol() may throw UnsupportedOperationException, so first construct a
217       // dummy SSLEngine and verify the method does not throw.
218       SSLContext context = SSLContext.getInstance("TLS", sslProvider);
219       context.init(null, null, null);
220       SSLEngine engine = context.createSSLEngine();
221       Method getEngineApplicationProtocol =
222           AccessController.doPrivileged(
223               new PrivilegedExceptionAction<Method>() {
224                 @Override
225                 public Method run() throws Exception {
226                   return SSLEngine.class.getMethod("getApplicationProtocol");
227                 }
228               });
229       getEngineApplicationProtocol.invoke(engine);
230 
231       Method setApplicationProtocols =
232           AccessController.doPrivileged(
233               new PrivilegedExceptionAction<Method>() {
234                 @Override
235                 public Method run() throws Exception {
236                   return SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
237                 }
238               });
239       Method getApplicationProtocol =
240           AccessController.doPrivileged(
241               new PrivilegedExceptionAction<Method>() {
242                 @Override
243                 public Method run() throws Exception {
244                   return SSLSocket.class.getMethod("getApplicationProtocol");
245                 }
246               });
247       return new JdkAlpnPlatform(sslProvider, setApplicationProtocols, getApplicationProtocol);
248     } catch (NoSuchAlgorithmException ignored) {
249       // On older Java
250     } catch (KeyManagementException ignored) {
251       // On older Java
252     } catch (PrivilegedActionException ignored) {
253       // On older Java
254     } catch (IllegalAccessException ignored) {
255       // On older Java
256     } catch (InvocationTargetException ignored) {
257       // On older Java
258     }
259 
260     // Find Jetty's ALPN extension for OpenJDK.
261     try {
262       String negoClassName = "org.eclipse.jetty.alpn.ALPN";
263       Class<?> negoClass = Class.forName(negoClassName);
264       Class<?> providerClass = Class.forName(negoClassName + "$Provider");
265       Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
266       Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
267       Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
268       Method getMethod = negoClass.getMethod("get", SSLSocket.class);
269       Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
270       return new JdkWithJettyBootPlatform(
271           putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass,
272           sslProvider);
273     } catch (ClassNotFoundException ignored) {
274       // No Jetty ALPN
275     } catch (NoSuchMethodException ignored) {
276       // Weird Jetty ALPN
277     }
278 
279     // TODO(ericgribkoff) Return null here
280     return new Platform(sslProvider);
281   }
282 
isAtLeastAndroid5()283   private static boolean isAtLeastAndroid5() {
284     try {
285       Platform.class
286           .getClassLoader()
287           .loadClass("android.net.Network"); // Arbitrary class added in Android 5.0.
288       return true;
289     } catch (ClassNotFoundException e) {
290       logger.log(Level.FINE, "Can't find class", e);
291     }
292     return false;
293   }
294 
isAtLeastAndroid41()295   private static boolean isAtLeastAndroid41() {
296     try {
297       Platform.class
298           .getClassLoader()
299           .loadClass("android.app.ActivityOptions"); // Arbitrary class added in Android 4.1.
300       return true;
301     } catch (ClassNotFoundException e) {
302       logger.log(Level.FINE, "Can't find class", e);
303     }
304     return false;
305   }
306 
307   /**
308    * Select the first recognized security provider according to the preference order returned by
309    * {@link Security#getProviders}. If a recognized provider is not found then warn but continue.
310    */
getAndroidSecurityProvider()311   private static Provider getAndroidSecurityProvider() {
312     Provider[] providers = Security.getProviders();
313     for (Provider availableProvider : providers) {
314       for (String providerClassName : ANDROID_SECURITY_PROVIDERS) {
315         if (providerClassName.equals(availableProvider.getClass().getName())) {
316           logger.log(Level.FINE, "Found registered provider {0}", providerClassName);
317           return availableProvider;
318         }
319       }
320     }
321     logger.log(Level.WARNING, "Unable to find Conscrypt");
322     return null;
323   }
324 
325   /** Android 2.3 or better, or AppEngine with Conscrypt. */
326   private static class Android extends Platform {
327 
328     private final OptionalMethod<Socket> setUseSessionTickets;
329     private final OptionalMethod<Socket> setHostname;
330 
331     // Non-null on Android 4.0+.
332     private final Method trafficStatsTagSocket;
333     private final Method trafficStatsUntagSocket;
334 
335     // Non-null on Android 5.0+.
336     private final OptionalMethod<Socket> getAlpnSelectedProtocol;
337     private final OptionalMethod<Socket> setAlpnProtocols;
338 
339     private final TlsExtensionType tlsExtensionType;
340 
Android( OptionalMethod<Socket> setUseSessionTickets, OptionalMethod<Socket> setHostname, Method trafficStatsTagSocket, Method trafficStatsUntagSocket, OptionalMethod<Socket> getAlpnSelectedProtocol, OptionalMethod<Socket> setAlpnProtocols, Provider provider, TlsExtensionType tlsExtensionType)341     public Android(
342         OptionalMethod<Socket> setUseSessionTickets,
343         OptionalMethod<Socket> setHostname,
344         Method trafficStatsTagSocket,
345         Method trafficStatsUntagSocket,
346         OptionalMethod<Socket> getAlpnSelectedProtocol,
347         OptionalMethod<Socket> setAlpnProtocols,
348         Provider provider,
349         TlsExtensionType tlsExtensionType) {
350       super(provider);
351       this.setUseSessionTickets = setUseSessionTickets;
352       this.setHostname = setHostname;
353       this.trafficStatsTagSocket = trafficStatsTagSocket;
354       this.trafficStatsUntagSocket = trafficStatsUntagSocket;
355       this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
356       this.setAlpnProtocols = setAlpnProtocols;
357       this.tlsExtensionType = tlsExtensionType;
358     }
359 
360     @Override
getTlsExtensionType()361     public TlsExtensionType getTlsExtensionType() {
362       return tlsExtensionType;
363     }
364 
connectSocket(Socket socket, InetSocketAddress address, int connectTimeout)365     @Override public void connectSocket(Socket socket, InetSocketAddress address,
366         int connectTimeout) throws IOException {
367       try {
368         socket.connect(address, connectTimeout);
369       } catch (SecurityException se) {
370         // Before android 4.3, socket.connect could throw a SecurityException
371         // if opening a socket resulted in an EACCES error.
372         IOException ioException = new IOException("Exception in connect");
373         ioException.initCause(se);
374         throw ioException;
375       }
376     }
377 
configureTlsExtensions( SSLSocket sslSocket, String hostname, List<Protocol> protocols)378     @Override public void configureTlsExtensions(
379         SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
380       // Enable SNI and session tickets.
381       if (hostname != null) {
382         setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
383         setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
384       }
385 
386       // Enable ALPN.
387       if (setAlpnProtocols.isSupported(sslSocket)) {
388         Object[] parameters = { concatLengthPrefixed(protocols) };
389         setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
390       }
391     }
392 
getSelectedProtocol(SSLSocket socket)393     @Override public String getSelectedProtocol(SSLSocket socket) {
394       if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
395 
396       byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
397       return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
398     }
399 
tagSocket(Socket socket)400     @Override public void tagSocket(Socket socket) throws SocketException {
401       if (trafficStatsTagSocket == null) return;
402 
403       try {
404         trafficStatsTagSocket.invoke(null, socket);
405       } catch (IllegalAccessException e) {
406         throw new RuntimeException(e);
407       } catch (InvocationTargetException e) {
408         throw new RuntimeException(e.getCause());
409       }
410     }
411 
untagSocket(Socket socket)412     @Override public void untagSocket(Socket socket) throws SocketException {
413       if (trafficStatsUntagSocket == null) return;
414 
415       try {
416         trafficStatsUntagSocket.invoke(null, socket);
417       } catch (IllegalAccessException e) {
418         throw new RuntimeException(e);
419       } catch (InvocationTargetException e) {
420         throw new RuntimeException(e.getCause());
421       }
422     }
423   }
424 
425   /** OpenJDK 9+. */
426   private static class JdkAlpnPlatform extends Platform {
427     private final Method setApplicationProtocols;
428     private final Method getApplicationProtocol;
429 
JdkAlpnPlatform( Provider provider, Method setApplicationProtocols, Method getApplicationProtocol)430     private JdkAlpnPlatform(
431         Provider provider, Method setApplicationProtocols, Method getApplicationProtocol) {
432       super(provider);
433       this.setApplicationProtocols = setApplicationProtocols;
434       this.getApplicationProtocol = getApplicationProtocol;
435     }
436 
437     @Override
getTlsExtensionType()438     public TlsExtensionType getTlsExtensionType() {
439       return TlsExtensionType.ALPN_AND_NPN;
440     }
441 
442     @Override
configureTlsExtensions( SSLSocket sslSocket, String hostname, List<Protocol> protocols)443     public void configureTlsExtensions(
444         SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
445       SSLParameters parameters = sslSocket.getSSLParameters();
446       List<String> names = new ArrayList<>(protocols.size());
447       for (Protocol protocol : protocols) {
448         if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
449         names.add(protocol.toString());
450       }
451       try {
452         setApplicationProtocols.invoke(
453             parameters, new Object[] {names.toArray(new String[names.size()])});
454       } catch (IllegalAccessException e) {
455         throw new RuntimeException(e);
456       } catch (InvocationTargetException e) {
457         throw new RuntimeException(e);
458       }
459       sslSocket.setSSLParameters(parameters);
460     }
461 
462     /** Returns the negotiated protocol, or null if no protocol was negotiated. */
463     @Override
getSelectedProtocol(SSLSocket socket)464     public String getSelectedProtocol(SSLSocket socket) {
465       try {
466         return (String) getApplicationProtocol.invoke(socket);
467       } catch (IllegalAccessException e) {
468         throw new RuntimeException(e);
469       } catch (InvocationTargetException e) {
470         throw new RuntimeException(e);
471       }
472     }
473   }
474 
475   /**
476    * OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
477    */
478   private static class JdkWithJettyBootPlatform extends Platform {
479     private final Method putMethod;
480     private final Method getMethod;
481     private final Method removeMethod;
482     private final Class<?> clientProviderClass;
483     private final Class<?> serverProviderClass;
484 
JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod, Class<?> clientProviderClass, Class<?> serverProviderClass, Provider provider)485     public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
486         Class<?> clientProviderClass, Class<?> serverProviderClass, Provider provider) {
487       super(provider);
488       this.putMethod = putMethod;
489       this.getMethod = getMethod;
490       this.removeMethod = removeMethod;
491       this.clientProviderClass = clientProviderClass;
492       this.serverProviderClass = serverProviderClass;
493     }
494 
495     @Override
getTlsExtensionType()496     public TlsExtensionType getTlsExtensionType() {
497       return TlsExtensionType.ALPN_AND_NPN;
498     }
499 
configureTlsExtensions( SSLSocket sslSocket, String hostname, List<Protocol> protocols)500     @Override public void configureTlsExtensions(
501         SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
502       List<String> names = new ArrayList<>(protocols.size());
503       for (int i = 0, size = protocols.size(); i < size; i++) {
504         Protocol protocol = protocols.get(i);
505         if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
506         names.add(protocol.toString());
507       }
508       try {
509         Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
510             new Class<?>[] { clientProviderClass, serverProviderClass }, new JettyNegoProvider(names));
511         putMethod.invoke(null, sslSocket, provider);
512       } catch (InvocationTargetException e) {
513         throw new AssertionError(e);
514       } catch (IllegalAccessException e) {
515         throw new AssertionError(e);
516       }
517     }
518 
afterHandshake(SSLSocket sslSocket)519     @Override public void afterHandshake(SSLSocket sslSocket) {
520       try {
521         removeMethod.invoke(null, sslSocket);
522       } catch (IllegalAccessException ignored) {
523         throw new AssertionError();
524       } catch (InvocationTargetException ex) {
525         // This would be very surprising and there's not much to do about it
526         logger.log(Level.FINE, "Failed to remove SSLSocket from Jetty ALPN", ex);
527       }
528     }
529 
getSelectedProtocol(SSLSocket socket)530     @Override public String getSelectedProtocol(SSLSocket socket) {
531       try {
532         JettyNegoProvider provider =
533             (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
534         if (!provider.unsupported && provider.selected == null) {
535           logger.log(Level.INFO, "ALPN callback dropped: SPDY and HTTP/2 are disabled. "
536               + "Is alpn-boot on the boot class path?");
537           return null;
538         }
539         return provider.unsupported ? null : provider.selected;
540       } catch (InvocationTargetException e) {
541         throw new AssertionError();
542       } catch (IllegalAccessException e) {
543         throw new AssertionError();
544       }
545     }
546   }
547 
548   /**
549    * Handle the methods of ALPN's ClientProvider and ServerProvider
550    * without a compile-time dependency on those interfaces.
551    */
552   private static class JettyNegoProvider implements InvocationHandler {
553     /** This peer's supported protocols. */
554     private final List<String> protocols;
555     /** Set when remote peer notifies ALPN is unsupported. */
556     private boolean unsupported;
557     /** The protocol the server selected. */
558     private String selected;
559 
JettyNegoProvider(List<String> protocols)560     public JettyNegoProvider(List<String> protocols) {
561       this.protocols = protocols;
562     }
563 
invoke(Object proxy, Method method, Object[] args)564     @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
565       String methodName = method.getName();
566       Class<?> returnType = method.getReturnType();
567       if (args == null) {
568         args = Util.EMPTY_STRING_ARRAY;
569       }
570       if (methodName.equals("supports") && boolean.class == returnType) {
571         return true; // ALPN is supported.
572       } else if (methodName.equals("unsupported") && void.class == returnType) {
573         this.unsupported = true; // Peer doesn't support ALPN.
574         return null;
575       } else if (methodName.equals("protocols") && args.length == 0) {
576         return protocols; // Client advertises these protocols.
577       } else if ((methodName.equals("selectProtocol") || methodName.equals("select"))
578           && String.class == returnType && args.length == 1 && args[0] instanceof List) {
579         @SuppressWarnings("unchecked")
580         List<String> peerProtocols = (List) args[0];
581         // Pick the first known protocol the peer advertises.
582         for (int i = 0, size = peerProtocols.size(); i < size; i++) {
583           if (protocols.contains(peerProtocols.get(i))) {
584             return selected = peerProtocols.get(i);
585           }
586         }
587         return selected = protocols.get(0); // On no intersection, try peer's first protocol.
588       } else if ((methodName.equals("protocolSelected") || methodName.equals("selected"))
589           && args.length == 1) {
590         this.selected = (String) args[0]; // Server selected this protocol.
591         return null;
592       } else {
593         return method.invoke(this, args);
594       }
595     }
596   }
597 
598   /**
599    * Returns the concatenation of 8-bit, length prefixed protocol names.
600    * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
601    */
concatLengthPrefixed(List<Protocol> protocols)602   public static byte[] concatLengthPrefixed(List<Protocol> protocols) {
603     Buffer result = new Buffer();
604     for (int i = 0, size = protocols.size(); i < size; i++) {
605       Protocol protocol = protocols.get(i);
606       if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
607       result.writeByte(protocol.toString().length());
608       result.writeUtf8(protocol.toString());
609     }
610     return result.readByteArray();
611   }
612 }
613