SSLSocketImpl.java revision 6812a2e8bb43d9a875633a9ba255d9882c63e327
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.io.InputStream;
22import java.io.OutputStream;
23import java.net.InetAddress;
24import java.net.SocketAddress;
25import java.net.SocketException;
26import java.net.UnknownHostException;
27import java.util.ArrayList;
28import javax.net.ssl.HandshakeCompletedEvent;
29import javax.net.ssl.HandshakeCompletedListener;
30import javax.net.ssl.SSLEngineResult;
31import javax.net.ssl.SSLException;
32import javax.net.ssl.SSLSession;
33import javax.net.ssl.SSLSocket;
34
35/**
36 * SSLSocket implementation.
37 * @see javax.net.ssl.SSLSocket class documentation for more information.
38 */
39public class SSLSocketImpl extends SSLSocket {
40
41    // indicates if handshake has been started
42    private boolean handshake_started = false;
43
44    // record protocol to be used
45    protected SSLRecordProtocol recordProtocol;
46    // handshake protocol to be used
47    private HandshakeProtocol handshakeProtocol;
48    // alert protocol to be used
49    private AlertProtocol alertProtocol;
50    // application data input stream, this stream is presented by
51    // ssl socket as an input stream. Additionaly this object is a
52    // place where application data will be stored by record protocol
53    private SSLSocketInputStream appDataIS;
54    // outcoming application data stream
55    private SSLSocketOutputStream appDataOS;
56    // active session object
57    private SSLSessionImpl session;
58
59    private boolean socket_was_closed = false;
60
61    // the sslParameters object encapsulates all the info
62    // about supported and enabled cipher suites and protocols,
63    // as well as the information about client/server mode of
64    // ssl socket, whether it require/want client authentication or not,
65    // and controls whether new SSL sessions may be established by this
66    // socket or not.
67    protected SSLParametersImpl sslParameters;
68    // super's streams to be wrapped:
69    protected InputStream input;
70    protected OutputStream output;
71    // handshake complete listeners
72    private ArrayList<HandshakeCompletedListener> listeners;
73    // logger
74    private Logger.Stream logger = Logger.getStream("socket");
75
76    // ----------------- Constructors and initializers --------------------
77
78    /**
79     * Constructor
80     * @param   sslParameters:  SSLParametersImpl
81     * @see javax.net.ssl.SSLSocket#SSLSocket() method documentation
82     * for more information.
83     */
84    protected SSLSocketImpl(SSLParametersImpl sslParameters) {
85        super();
86        this.sslParameters = sslParameters;
87        // init should be called after creation!
88    }
89
90    /**
91     * Constructor
92     * @param   host:   String
93     * @param   port:   int
94     * @param   sslParameters:  SSLParametersImpl
95     * @throws  IOException
96     * @throws  UnknownHostException
97     * @see javax.net.ssl.SSLSocket#SSLSocket(String,int)
98     * method documentation for more information.
99     */
100    protected SSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
101            throws IOException, UnknownHostException {
102        super(host, port);
103        this.sslParameters = sslParameters;
104        init();
105    }
106
107    /**
108     * Constructor
109     * @param   host:   String
110     * @param   port:   int
111     * @param   localHost:  InetAddress
112     * @param   localPort:  int
113     * @param   sslParameters:  SSLParametersImpl
114     * @throws  IOException
115     * @throws  UnknownHostException
116     * @see javax.net.ssl.SSLSocket#SSLSocket(String,int,InetAddress,int)
117     * method documentation for more information.
118     */
119    protected SSLSocketImpl(String host, int port,
120            InetAddress localHost, int localPort,
121            SSLParametersImpl sslParameters) throws IOException,
122            UnknownHostException {
123        super(host, port, localHost, localPort);
124        this.sslParameters = sslParameters;
125        init();
126    }
127
128    /**
129     * Constructor
130     * @param   host:   InetAddress
131     * @param   port:   int
132     * @param   sslParameters:  SSLParametersImpl
133     * @return
134     * @throws  IOException
135     * @see javax.net.ssl.SSLSocket#SSLSocket(InetAddress,int)
136     * method documentation for more information.
137     */
138    protected SSLSocketImpl(InetAddress host, int port,
139            SSLParametersImpl sslParameters) throws IOException {
140        super(host, port);
141        this.sslParameters = sslParameters;
142        init();
143    }
144
145    /**
146     * Constructor
147     * @param   address:    InetAddress
148     * @param   port:   int
149     * @param   localAddress:   InetAddress
150     * @param   localPort:  int
151     * @param   sslParameters:  SSLParametersImpl
152     * @return
153     * @throws  IOException
154     * @see javax.net.ssl.SSLSocket#SSLSocket(InetAddress,int,InetAddress,int)
155     * method documentation for more information.
156     */
157    protected SSLSocketImpl(InetAddress address, int port,
158            InetAddress localAddress, int localPort,
159            SSLParametersImpl sslParameters) throws IOException {
160        super(address, port, localAddress, localPort);
161        this.sslParameters = sslParameters;
162        init();
163    }
164
165    /**
166     * Initialize the SSL socket.
167     */
168    protected void init() throws IOException {
169        if (appDataIS != null) {
170            // already initialized
171            return;
172        }
173        initTransportLayer();
174        appDataIS = new SSLSocketInputStream(this);
175        appDataOS = new SSLSocketOutputStream(this);
176    }
177
178    /**
179     * Initialize the transport data streams.
180     */
181    protected void initTransportLayer() throws IOException {
182        input = super.getInputStream();
183        output = super.getOutputStream();
184    }
185
186    /**
187     * Closes the transport data streams.
188     */
189    protected void closeTransportLayer() throws IOException {
190        super.close();
191        if (input != null) {
192            input.close();
193            output.close();
194        }
195    }
196
197    // --------------- SSLParameters based methods ---------------------
198
199    /**
200     * This method works according to the specification of implemented class.
201     * @see javax.net.ssl.SSLSocket#getSupportedCipherSuites()
202     * method documentation for more information
203     */
204    @Override
205    public String[] getSupportedCipherSuites() {
206        return CipherSuite.getSupportedCipherSuiteNames();
207    }
208
209    /**
210     * This method works according to the specification of implemented class.
211     * @see javax.net.ssl.SSLSocket#getEnabledCipherSuites()
212     * method documentation for more information
213     */
214    @Override
215    public String[] getEnabledCipherSuites() {
216        return sslParameters.getEnabledCipherSuites();
217    }
218
219    /**
220     * This method works according to the specification of implemented class.
221     * @see javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])
222     * method documentation for more information
223     */
224    @Override
225    public void setEnabledCipherSuites(String[] suites) {
226        sslParameters.setEnabledCipherSuites(suites);
227    }
228
229    /**
230     * This method works according to the specification of implemented class.
231     * @see javax.net.ssl.SSLSocket#getSupportedProtocols()
232     * method documentation for more information
233     */
234    @Override
235    public String[] getSupportedProtocols() {
236        return ProtocolVersion.supportedProtocols.clone();
237    }
238
239    /**
240     * This method works according to the specification of implemented class.
241     * @see javax.net.ssl.SSLSocket#getEnabledProtocols()
242     * method documentation for more information
243     */
244    @Override
245    public String[] getEnabledProtocols() {
246        return sslParameters.getEnabledProtocols();
247    }
248
249    /**
250     * This method works according to the specification of implemented class.
251     * @see javax.net.ssl.SSLSocket#setEnabledProtocols(String[])
252     * method documentation for more information
253     */
254    @Override
255    public void setEnabledProtocols(String[] protocols) {
256        sslParameters.setEnabledProtocols(protocols);
257    }
258
259    /**
260     * This method works according to the specification of implemented class.
261     * @see javax.net.ssl.SSLSocket#setUseClientMode(boolean)
262     * method documentation for more information
263     */
264    @Override
265    public void setUseClientMode(boolean mode) {
266        if (handshake_started) {
267            throw new IllegalArgumentException(
268            "Could not change the mode after the initial handshake has begun.");
269        }
270        sslParameters.setUseClientMode(mode);
271    }
272
273    /**
274     * This method works according to the specification of implemented class.
275     * @see javax.net.ssl.SSLSocket#getUseClientMode()
276     * method documentation for more information
277     */
278    @Override
279    public boolean getUseClientMode() {
280        return sslParameters.getUseClientMode();
281    }
282
283    /**
284     * This method works according to the specification of implemented class.
285     * @see javax.net.ssl.SSLSocket#setNeedClientAuth(boolean)
286     * method documentation for more information
287     */
288    @Override
289    public void setNeedClientAuth(boolean need) {
290        sslParameters.setNeedClientAuth(need);
291    }
292
293    /**
294     * This method works according to the specification of implemented class.
295     * @see javax.net.ssl.SSLSocket#getNeedClientAuth()
296     * method documentation for more information
297     */
298    @Override
299    public boolean getNeedClientAuth() {
300        return sslParameters.getNeedClientAuth();
301    }
302
303    /**
304     * This method works according to the specification of implemented class.
305     * @see javax.net.ssl.SSLSocket#setWantClientAuth(boolean)
306     * method documentation for more information
307     */
308    @Override
309    public void setWantClientAuth(boolean want) {
310        sslParameters.setWantClientAuth(want);
311    }
312
313    /**
314     * This method works according to the specification of implemented class.
315     * @see javax.net.ssl.SSLSocket#getWantClientAuth()
316     * method documentation for more information
317     */
318    @Override
319    public boolean getWantClientAuth() {
320        return sslParameters.getWantClientAuth();
321    }
322
323    /**
324     * This method works according to the specification of implemented class.
325     * @see javax.net.ssl.SSLSocket#setEnableSessionCreation(boolean)
326     * method documentation for more information
327     */
328    @Override
329    public void setEnableSessionCreation(boolean flag) {
330        sslParameters.setEnableSessionCreation(flag);
331    }
332
333    /**
334     * This method works according to the specification of implemented class.
335     * @see javax.net.ssl.SSLSocket#getEnableSessionCreation()
336     * method documentation for more information
337     */
338    @Override
339    public boolean getEnableSessionCreation() {
340        return sslParameters.getEnableSessionCreation();
341    }
342
343    // -----------------------------------------------------------------
344
345    /**
346     * This method works according to the specification of implemented class.
347     * @see javax.net.ssl.SSLSocket#getSession()
348     * method documentation for more information
349     */
350    @Override
351    public SSLSession getSession() {
352        if (!handshake_started) {
353            try {
354                startHandshake();
355            } catch (IOException e) {
356                // return an invalid session with
357                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
358                return SSLSessionImpl.NULL_SESSION;
359            }
360        }
361        return session;
362    }
363
364    /**
365     * This method works according to the specification of implemented class.
366     * @see javax.net.ssl.SSLSocket#addHandshakeCompletedListener(HandshakeCompletedListener)
367     * method documentation for more information
368     */
369    @Override
370    public void addHandshakeCompletedListener(
371            HandshakeCompletedListener listener) {
372        if (listener == null) {
373            throw new IllegalArgumentException("Provided listener is null");
374        }
375        if (listeners == null) {
376            listeners = new ArrayList<HandshakeCompletedListener>();
377        }
378        listeners.add(listener);
379    }
380
381    /**
382     * This method works according to the specification of implemented class.
383     * @see javax.net.ssl.SSLSocket#removeHandshakeCompletedListener(HandshakeCompletedListener)
384     * method documentation for more information
385     */
386    @Override
387    public void removeHandshakeCompletedListener(
388            HandshakeCompletedListener listener) {
389        if (listener == null) {
390            throw new IllegalArgumentException("Provided listener is null");
391        }
392        if (listeners == null) {
393            throw new IllegalArgumentException(
394                    "Provided listener is not registered");
395        }
396        if (!listeners.remove(listener)) {
397            throw new IllegalArgumentException(
398                    "Provided listener is not registered");
399        }
400    }
401
402    /**
403     * Performs the handshake process over the SSL/TLS connection
404     * as described in rfc 2246, TLS v1 specification
405     * http://www.ietf.org/rfc/rfc2246.txt. If the initial handshake
406     * has been already done, this method initiates rehandshake.
407     * This method works according to the specification of implemented class.
408     * @see javax.net.ssl.SSLSocket#startHandshake()
409     * method documentation for more information
410     */
411    @Override
412    public void startHandshake() throws IOException {
413        if (appDataIS == null) {
414            throw new IOException("Socket is not connected.");
415        }
416        if (socket_was_closed) {
417            throw new IOException("Socket has already been closed.");
418        }
419
420        if (!handshake_started) {
421            handshake_started = true;
422            if (sslParameters.getUseClientMode()) {
423                if (logger != null) {
424                    logger.println("SSLSocketImpl: CLIENT");
425                }
426                handshakeProtocol = new ClientHandshakeImpl(this);
427            } else {
428                if (logger != null) {
429                    logger.println("SSLSocketImpl: SERVER");
430                }
431                handshakeProtocol = new ServerHandshakeImpl(this);
432            }
433
434            alertProtocol = new AlertProtocol();
435            recordProtocol = new SSLRecordProtocol(handshakeProtocol,
436                    alertProtocol, new SSLStreamedInput(input),
437                    appDataIS.dataPoint);
438        }
439
440        if (logger != null) {
441            logger.println("SSLSocketImpl.startHandshake");
442        }
443
444        handshakeProtocol.start();
445
446        doHandshake();
447
448        if (logger != null) {
449            logger.println("SSLSocketImpl.startHandshake: END");
450        }
451    }
452
453
454    // ---------------- Socket's methods overridings -------------------
455
456    /**
457     * This method works according to the specification of implemented class.
458     * @see javax.net.ssl.SSLSocket#getInputStream()
459     * method documentation for more information
460     */
461    @Override
462    public InputStream getInputStream() throws IOException {
463        if (socket_was_closed) {
464            throw new IOException("Socket has already been closed.");
465        }
466        return appDataIS;
467    }
468
469    /**
470     * This method works according to the specification of implemented class.
471     * @see javax.net.ssl.SSLSocket#getOutputStream()
472     * method documentation for more information
473     */
474    @Override
475    public OutputStream getOutputStream() throws IOException {
476        if (socket_was_closed) {
477            throw new IOException("Socket has already been closed.");
478        }
479        return appDataOS;
480    }
481
482    /**
483     * This method works according to the specification of implemented class.
484     * @see java.net.Socket#connect(SocketAddress)
485     * method documentation for more information
486     */
487    @Override
488    public void connect(SocketAddress endpoint) throws IOException {
489        super.connect(endpoint);
490        init();
491    }
492
493    /**
494     * This method works according to the specification of implemented class.
495     * @see java.net.Socket#connect(SocketAddress,int)
496     * method documentation for more information
497     */
498    @Override
499    public void connect(SocketAddress endpoint, int timeout)
500            throws IOException {
501        super.connect(endpoint, timeout);
502        init();
503    }
504
505    /**
506     * This method works according to the specification of implemented class.
507     * @see javax.net.ssl.SSLSocket#close()
508     * method documentation for more information
509     */
510    @Override
511    public void close() throws IOException {
512        if (logger != null) {
513            logger.println("SSLSocket.close "+socket_was_closed);
514        }
515        if (!socket_was_closed) {
516            if (handshake_started) {
517                alertProtocol.alert(AlertProtocol.WARNING,
518                        AlertProtocol.CLOSE_NOTIFY);
519                try {
520                    output.write(alertProtocol.wrap());
521                } catch (IOException ex) { }
522                alertProtocol.setProcessed();
523            }
524            shutdown();
525            closeTransportLayer();
526            socket_was_closed = true;
527        }
528    }
529
530    /**
531     * This method is not supported for SSLSocket implementation.
532     */
533    @Override
534    public void sendUrgentData(int data) throws IOException {
535        throw new SocketException(
536                "Method sendUrgentData() is not supported.");
537    }
538
539    /**
540     * This method is not supported for SSLSocket implementation.
541     */
542    @Override
543    public void setOOBInline(boolean on) throws SocketException {
544        throw new SocketException(
545                "Methods sendUrgentData, setOOBInline are not supported.");
546    }
547
548    /**
549     * This method is not supported for SSLSocket implementation.
550     */
551    @Override
552    public void shutdownOutput() {
553        throw new UnsupportedOperationException(
554                "Method shutdownOutput() is not supported.");
555    }
556
557    /**
558     * This method is not supported for SSLSocket implementation.
559     */
560    @Override
561    public void shutdownInput() {
562        throw new UnsupportedOperationException(
563                "Method shutdownInput() is not supported.");
564    }
565
566    /**
567     * Returns the string representation of the object.
568     */
569    @Override
570    public String toString() {
571        return "[SSLSocketImpl]";
572    }
573
574    // -----------------------------------------------------------------
575
576    // Shutdownes the ssl socket and makes all cleanup work.
577    private void shutdown() {
578        if (handshake_started) {
579            alertProtocol.shutdown();
580            alertProtocol = null;
581            handshakeProtocol.shutdown();
582            handshakeProtocol = null;
583            recordProtocol.shutdown();
584            recordProtocol = null;
585        }
586        socket_was_closed = true;
587    }
588
589    /**
590     * This method is called by SSLSocketInputStream class
591     * when client application tryes to read application data from
592     * the stream, but there is no data in its underlying buffer.
593     * @throws  IOException
594     */
595    protected void needAppData() throws IOException {
596        if (!handshake_started) {
597            startHandshake();
598        }
599        int type;
600        if (logger != null) {
601            logger.println("SSLSocket.needAppData..");
602        }
603        try {
604            while(appDataIS.available() == 0) {
605                // read and unwrap the record contained in the transport
606                // input stream (SSLStreamedInput), pass it
607                // to appropriate client protocol (alert, handshake, or app)
608                // and retrieve the type of unwrapped data
609                switch (type = recordProtocol.unwrap()) {
610                    case ContentType.HANDSHAKE:
611                        if (!handshakeProtocol.getStatus().equals(
612                                SSLEngineResult.HandshakeStatus
613                                .NOT_HANDSHAKING)) {
614                            // handshake protocol got addressed to it message
615                            // and did not ignore it, so it's a rehandshake
616                            doHandshake();
617                        }
618                        break;
619                    case ContentType.ALERT:
620                        processAlert();
621                        if (socket_was_closed) {
622                            return;
623                        }
624                        break;
625                    case ContentType.APPLICATION_DATA:
626                        if (logger != null) {
627                            logger.println(
628                                    "SSLSocket.needAppData: got the data");
629                        }
630                        break;
631                    default:
632                        // will throw exception
633                        reportFatalAlert(AlertProtocol.UNEXPECTED_MESSAGE,
634                                new SSLException("Unexpected message of type "
635                                    + type + " has been got"));
636                }
637                if (alertProtocol.hasAlert()) {
638                    // warning alert occured during wrap or unwrap
639                    // (note: fatal alert causes AlertException
640                    // to be thrown)
641                    output.write(alertProtocol.wrap());
642                    alertProtocol.setProcessed();
643                }
644                if (socket_was_closed) {
645                    appDataIS.setEnd();
646                    return;
647                }
648            }
649        } catch (AlertException e) {
650            // will throw exception
651            reportFatalAlert(e.getDescriptionCode(), e.getReason());
652        } catch (EndOfSourceException e) {
653            // end of socket's input stream has been reached
654            appDataIS.setEnd();
655        }
656        if (logger != null) {
657            logger.println("SSLSocket.needAppData: app data len: "
658                    + appDataIS.available());
659        }
660    }
661
662    /**
663     * This method is called by SSLSocketOutputStream when client application
664     * tryes to send the data over ssl protocol.
665     */
666    protected void writeAppData(byte[] data, int offset, int len)
667                                                    throws IOException {
668        if (!handshake_started) {
669            startHandshake();
670        }
671        if (logger != null) {
672            logger.println("SSLSocket.writeAppData: " +
673                    len + " " + SSLRecordProtocol.MAX_DATA_LENGTH);
674            //logger.println(new String(data, offset, len));
675        }
676        try {
677            if (len < SSLRecordProtocol.MAX_DATA_LENGTH) {
678                output.write(recordProtocol.wrap(ContentType.APPLICATION_DATA,
679                            data, offset, len));
680            } else {
681                while (len >= SSLRecordProtocol.MAX_DATA_LENGTH) {
682                    output.write(recordProtocol.wrap(
683                                ContentType.APPLICATION_DATA, data, offset,
684                                SSLRecordProtocol.MAX_DATA_LENGTH));
685                    offset += SSLRecordProtocol.MAX_DATA_LENGTH;
686                    len -= SSLRecordProtocol.MAX_DATA_LENGTH;
687                }
688                if (len > 0) {
689                    output.write(
690                        recordProtocol.wrap(ContentType.APPLICATION_DATA,
691                                            data, offset, len));
692                }
693            }
694        } catch (AlertException e) {
695            // will throw exception
696            reportFatalAlert(e.getDescriptionCode(), e.getReason());
697        }
698    }
699
700    /*
701     * Performs handshake proccess over this connection. The hanshake
702     * process is dirrected by the handshake status code provided by
703     * handshake protocol. If this status is NEED_WRAP, method retrieves
704     * handshake message from handshake protocol and sends it to another peer.
705     * If this status is NEED_UNWRAP, method receives and processes handshake
706     * message from another peer. Each of this stages (wrap/unwrap) change
707     * the state of handshake protocol and this process is performed
708     * until handshake status is FINISHED. After handshake process is finnished
709     * handshake completed event are sent to the registered listeners.
710     * For more information about the handshake process see
711     * TLS v1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 7.3.
712     */
713    private void doHandshake() throws IOException {
714        SSLEngineResult.HandshakeStatus status;
715        int type;
716        try {
717            while (!(status = handshakeProtocol.getStatus()).equals(
718                        SSLEngineResult.HandshakeStatus.FINISHED)) {
719                if (logger != null) {
720                    String s = (status.equals(
721                                SSLEngineResult.HandshakeStatus.NEED_WRAP))
722                        ? "NEED_WRAP"
723                        : (status.equals(
724                                SSLEngineResult.HandshakeStatus.NEED_UNWRAP))
725                            ? "NEED_UNWRAP"
726                            : "STATUS: OTHER!";
727                    logger.println("SSLSocketImpl: HS status: "+s+" "+status);
728                }
729                if (status.equals(SSLEngineResult.HandshakeStatus.NEED_WRAP)) {
730                    output.write(handshakeProtocol.wrap());
731                } else if (status.equals(
732                            SSLEngineResult.HandshakeStatus.NEED_UNWRAP)) {
733                    // read and unwrap the record contained in the transport
734                    // input stream (SSLStreamedInput), pass it
735                    // to appropriate client protocol (alert, handshake, or app)
736                    // and retrieve the type of unwrapped data
737                    switch (type = recordProtocol.unwrap()) {
738                        case ContentType.HANDSHAKE:
739                        case ContentType.CHANGE_CIPHER_SPEC:
740                            break;
741                        case ContentType.APPLICATION_DATA:
742                            // So it's rehandshake and
743                            // if app data buffer will be overloaded
744                            // it will throw alert exception.
745                            // Probably we should count the number of
746                            // not handshaking data and make additional
747                            // constraints (do not expect buffer overflow).
748                            break;
749                        case ContentType.ALERT:
750                            processAlert();
751                            if (socket_was_closed) {
752                                return;
753                            }
754                            break;
755                        default:
756                            // will throw exception
757                            reportFatalAlert(AlertProtocol.UNEXPECTED_MESSAGE,
758                                    new SSLException(
759                                        "Unexpected message of type "
760                                        + type + " has been got"));
761                    }
762                } else {
763                    // will throw exception
764                    reportFatalAlert(AlertProtocol.INTERNAL_ERROR,
765                        new SSLException(
766                            "Handshake passed unexpected status: "+status));
767                }
768                if (alertProtocol.hasAlert()) {
769                    // warning alert uccured during wrap or unwrap
770                    // (note: fatal alert causes AlertException
771                    // to be thrown)
772                    output.write(alertProtocol.wrap());
773                    alertProtocol.setProcessed();
774                }
775            }
776        } catch (EndOfSourceException e) {
777            appDataIS.setEnd();
778            throw new IOException("Connection was closed");
779        } catch (AlertException e) {
780            // will throw exception
781            reportFatalAlert(e.getDescriptionCode(), e.getReason());
782        }
783
784        session = recordProtocol.getSession();
785        if (listeners != null) {
786            // notify the listeners
787            HandshakeCompletedEvent event =
788                new HandshakeCompletedEvent(this, session);
789            int size = listeners.size();
790            for (int i=0; i<size; i++) {
791                listeners.get(i)
792                    .handshakeCompleted(event);
793            }
794        }
795    }
796
797    /*
798     * Process received alert message
799     */
800    private void processAlert() throws IOException {
801        if (!alertProtocol.hasAlert()) {
802            return;
803        }
804        if (alertProtocol.isFatalAlert()) {
805            alertProtocol.setProcessed();
806            String description = "Fatal alert received "
807                + alertProtocol.getAlertDescription();
808            shutdown();
809            throw new SSLException(description);
810        }
811
812        if (logger != null) {
813            logger.println("Warning alert received: "
814                + alertProtocol.getAlertDescription());
815        }
816        switch(alertProtocol.getDescriptionCode()) {
817            case AlertProtocol.CLOSE_NOTIFY:
818                alertProtocol.setProcessed();
819                appDataIS.setEnd();
820                close();
821                return;
822            default:
823                alertProtocol.setProcessed();
824            // TODO: process other warning messages
825        }
826    }
827
828    /*
829     * Sends fatal alert message and throws exception
830     */
831    private void reportFatalAlert(byte description_code,
832            SSLException reason) throws IOException {
833        alertProtocol.alert(AlertProtocol.FATAL, description_code);
834        try {
835            // the output stream can be closed
836            output.write(alertProtocol.wrap());
837        } catch (IOException ex) { }
838        alertProtocol.setProcessed();
839        shutdown();
840        throw reason;
841    }
842}
843
844