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 */
17package com.squareup.okhttp.internal;
18
19import dalvik.system.SocketTagger;
20import java.io.IOException;
21import java.io.OutputStream;
22import java.net.InetSocketAddress;
23import java.net.Socket;
24import java.net.SocketException;
25import java.net.URI;
26import java.net.URISyntaxException;
27import java.net.URL;
28import java.util.List;
29import javax.net.ssl.SSLSocket;
30
31import com.squareup.okhttp.Protocol;
32
33import okio.Buffer;
34
35/**
36 * Access to proprietary Android APIs. Doesn't use reflection.
37 */
38public final class Platform {
39    private static final Platform PLATFORM = new Platform();
40
41    public static Platform get() {
42        return PLATFORM;
43    }
44
45    /** setUseSessionTickets(boolean) */
46    private static final OptionalMethod<Socket> SET_USE_SESSION_TICKETS =
47            new OptionalMethod<Socket>(null, "setUseSessionTickets", Boolean.TYPE);
48    /** setHostname(String) */
49    private static final OptionalMethod<Socket> SET_HOSTNAME =
50            new OptionalMethod<Socket>(null, "setHostname", String.class);
51    /** byte[] getAlpnSelectedProtocol() */
52    private static final OptionalMethod<Socket> GET_ALPN_SELECTED_PROTOCOL =
53            new OptionalMethod<Socket>(byte[].class, "getAlpnSelectedProtocol");
54    /** setAlpnSelectedProtocol(byte[]) */
55    private static final OptionalMethod<Socket> SET_ALPN_PROTOCOLS =
56            new OptionalMethod<Socket>(null, "setAlpnProtocols", byte[].class );
57
58    public void logW(String warning) {
59        System.logW(warning);
60    }
61
62    public void tagSocket(Socket socket) throws SocketException {
63        SocketTagger.get().tag(socket);
64    }
65
66    public void untagSocket(Socket socket) throws SocketException {
67        SocketTagger.get().untag(socket);
68    }
69
70    public URI toUriLenient(URL url) throws URISyntaxException {
71        return url.toURILenient();
72    }
73
74    public void configureTlsExtensions(
75            SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
76        // Enable SNI and session tickets.
77        if (hostname != null) {
78            SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true);
79            SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname);
80        }
81
82        // Enable ALPN.
83        boolean alpnSupported = SET_ALPN_PROTOCOLS.isSupported(sslSocket);
84        if (!alpnSupported) {
85            return;
86        }
87
88        Object[] parameters = { concatLengthPrefixed(protocols) };
89        if (alpnSupported) {
90            SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
91        }
92    }
93
94    /**
95     * Called after the TLS handshake to release resources allocated by {@link
96     * #configureTlsExtensions}.
97     */
98    public void afterHandshake(SSLSocket sslSocket) {
99    }
100
101    public String getSelectedProtocol(SSLSocket socket) {
102        boolean alpnSupported = GET_ALPN_SELECTED_PROTOCOL.isSupported(socket);
103        if (!alpnSupported) {
104            return null;
105        }
106
107        byte[] alpnResult =
108                (byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
109        if (alpnResult != null) {
110            return new String(alpnResult, Util.UTF_8);
111        }
112        return null;
113    }
114
115    public void connectSocket(Socket socket, InetSocketAddress address,
116              int connectTimeout) throws IOException {
117        socket.connect(address, connectTimeout);
118    }
119
120    /** Prefix used on custom headers. */
121    public String getPrefix() {
122        return "X-Android";
123    }
124
125    /**
126     * Returns the concatenation of 8-bit, length prefixed protocol names.
127     * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
128     */
129    static byte[] concatLengthPrefixed(List<Protocol> protocols) {
130        Buffer result = new Buffer();
131        for (int i = 0, size = protocols.size(); i < size; i++) {
132            Protocol protocol = protocols.get(i);
133            if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
134            result.writeByte(protocol.toString().length());
135            result.writeUtf8(protocol.toString());
136        }
137        return result.readByteArray();
138    }
139}
140