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