1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  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
18package org.apache.harmony.xnet.provider.jsse;
19
20import java.io.IOException;
21import java.security.SecureRandom;
22import java.util.Arrays;
23import libcore.io.Streams;
24import libcore.util.EmptyArray;
25
26/**
27 * Represents Client Hello message
28 * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.2.
29 * Client hello</a>
30 *
31 */
32public class ClientHello extends Message {
33
34    /**
35     * Client version
36     */
37    final byte[] client_version;
38
39    /**
40     * Random bytes
41     */
42    final byte[] random = new byte[32];
43
44    /**
45     * Session id
46     */
47    final byte[] session_id;
48
49    /**
50     * Cipher suites supported by the client
51     */
52    final CipherSuite[] cipher_suites;
53
54    /**
55     * Compression methods supported by the client
56     */
57    final byte[] compression_methods;
58
59    /**
60     * Creates outbound message
61     * @param sr
62     * @param version
63     * @param ses_id
64     * @param cipher_suite
65     */
66    public ClientHello(SecureRandom sr, byte[] version, byte[] ses_id,
67            CipherSuite[] cipher_suite) {
68        client_version = version;
69        long gmt_unix_time = System.currentTimeMillis()/1000;
70        sr.nextBytes(random);
71        random[0] = (byte) (gmt_unix_time & 0xFF000000 >>> 24);
72        random[1] = (byte) (gmt_unix_time & 0xFF0000 >>> 16);
73        random[2] = (byte) (gmt_unix_time & 0xFF00 >>> 8);
74        random[3] = (byte) (gmt_unix_time & 0xFF);
75        session_id = ses_id;
76        this.cipher_suites = cipher_suite;
77        compression_methods = new byte[] { 0 }; // CompressionMethod.null
78        length = 38 + session_id.length + (this.cipher_suites.length << 1)
79                + compression_methods.length;
80    }
81
82    /**
83     * Creates inbound message
84     * @param in
85     * @param length
86     * @throws IOException
87     */
88    public ClientHello(HandshakeIODataStream in, int length) throws IOException {
89        client_version = new byte[2];
90        client_version[0] = (byte) in.readUint8();
91        client_version[1] = (byte) in.readUint8();
92        Streams.readFully(in, random);
93        int size = in.read();
94        session_id = new byte[size];
95        in.read(session_id, 0, size);
96        int l = in.readUint16();
97        if ((l & 0x01) == 0x01) { // cipher suites length must be an even number
98            fatalAlert(AlertProtocol.DECODE_ERROR,
99                    "DECODE ERROR: incorrect ClientHello");
100        }
101        size = l >> 1;
102        cipher_suites = new CipherSuite[size];
103        for (int i = 0; i < size; i++) {
104            byte b0 = (byte) in.read();
105            byte b1 = (byte) in.read();
106            cipher_suites[i] = CipherSuite.getByCode(b0, b1);
107        }
108        size = in.read();
109        compression_methods = new byte[size];
110        in.read(compression_methods, 0, size);
111        this.length = 38 + session_id.length + (cipher_suites.length << 1)
112                + compression_methods.length;
113        if (this.length > length) {
114            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientHello");
115        }
116        // for forward compatibility, extra data is permitted;
117        // must be ignored
118        if (this.length < length) {
119            in.skip(length - this.length);
120            this.length = length;
121        }
122    }
123    /**
124     * Parse V2ClientHello
125     * @param in
126     * @throws IOException
127     */
128    public ClientHello(HandshakeIODataStream in) throws IOException {
129        if (in.readUint8() != 1) {
130            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello");
131        }
132        client_version = new byte[2];
133        client_version[0] = (byte) in.readUint8();
134        client_version[1] = (byte) in.readUint8();
135        int cipher_spec_length = in.readUint16();
136        if (in.readUint16() != 0) { // session_id_length
137            // as client already knows the protocol known to a server it should
138            // initiate the connection in that native protocol
139            fatalAlert(AlertProtocol.DECODE_ERROR,
140                    "DECODE ERROR: incorrect V2ClientHello, cannot be used for resuming");
141        }
142        int challenge_length = in.readUint16();
143        if (challenge_length < 16) {
144            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, short challenge data");
145        }
146        session_id = EmptyArray.BYTE;
147        cipher_suites = new CipherSuite[cipher_spec_length/3];
148        for (int i = 0; i < cipher_suites.length; i++) {
149            byte b0 = (byte) in.read();
150            byte b1 = (byte) in.read();
151            byte b2 = (byte) in.read();
152            cipher_suites[i] = CipherSuite.getByCode(b0, b1, b2);
153        }
154        compression_methods = new byte[] { 0 }; // CompressionMethod.null
155
156        if (challenge_length < 32) {
157            Arrays.fill(random, 0, 32 - challenge_length, (byte)0);
158            System.arraycopy(in.read(challenge_length), 0, random, 32 - challenge_length, challenge_length);
159        } else if (challenge_length == 32) {
160            System.arraycopy(in.read(32), 0, random, 0, 32);
161        } else {
162            System.arraycopy(in.read(challenge_length), challenge_length - 32, random, 0, 32);
163        }
164        if (in.available() > 0) {
165            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, extra data");
166        }
167        this.length = 38 + session_id.length + (cipher_suites.length << 1)
168                + compression_methods.length;
169    }
170
171    /**
172     * Sends message
173     * @param out
174     */
175    @Override
176    public void send(HandshakeIODataStream out) {
177        out.write(client_version);
178        out.write(random);
179        out.writeUint8(session_id.length);
180        out.write(session_id);
181        int size = cipher_suites.length << 1;
182        out.writeUint16(size);
183        for (int i = 0; i < cipher_suites.length; i++) {
184            out.write(cipher_suites[i].toBytes());
185        }
186        out.writeUint8(compression_methods.length);
187        for (int i = 0; i < compression_methods.length; i++) {
188            out.write(compression_methods[i]);
189        }
190    }
191
192    /**
193     * Returns client random
194     * @return client random
195     */
196    public byte[] getRandom() {
197        return random;
198    }
199
200    /**
201     * Returns message type
202     * @return
203     */
204    @Override
205    public int getType() {
206        return Handshake.CLIENT_HELLO;
207    }
208}
209