001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2017-2019 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.net;
020
021import java.net.InetSocketAddress;
022import java.net.SocketAddress;
023import java.nio.ByteBuffer;
024import java.security.KeyManagementException;
025import java.security.NoSuchAlgorithmException;
026import java.security.cert.X509Certificate;
027import java.util.Collections;
028import java.util.List;
029import java.util.Optional;
030import java.util.concurrent.ExecutionException;
031import java.util.concurrent.FutureTask;
032import java.util.logging.Level;
033import java.util.logging.Logger;
034
035import javax.net.ssl.ExtendedSSLSession;
036import javax.net.ssl.SNIServerName;
037import javax.net.ssl.SSLContext;
038import javax.net.ssl.SSLEngine;
039import javax.net.ssl.SSLEngineResult;
040import javax.net.ssl.SSLEngineResult.HandshakeStatus;
041import javax.net.ssl.SSLEngineResult.Status;
042import javax.net.ssl.SSLException;
043import javax.net.ssl.TrustManager;
044import javax.net.ssl.X509TrustManager;
045
046import org.jgrapes.core.Channel;
047import org.jgrapes.core.ClassChannel;
048import org.jgrapes.core.Component;
049import org.jgrapes.core.Components;
050import org.jgrapes.core.EventPipeline;
051import org.jgrapes.core.annotation.Handler;
052import org.jgrapes.core.annotation.HandlerDefinition.ChannelReplacements;
053import org.jgrapes.io.IOSubchannel;
054import org.jgrapes.io.events.Close;
055import org.jgrapes.io.events.Closed;
056import org.jgrapes.io.events.HalfClosed;
057import org.jgrapes.io.events.IOError;
058import org.jgrapes.io.events.Input;
059import org.jgrapes.io.events.OpenTcpConnection;
060import org.jgrapes.io.events.Output;
061import org.jgrapes.io.events.Purge;
062import org.jgrapes.io.util.LinkedIOSubchannel;
063import org.jgrapes.io.util.ManagedBuffer;
064import org.jgrapes.io.util.ManagedBufferPool;
065import org.jgrapes.net.events.Accepted;
066import org.jgrapes.net.events.Connected;
067
068/**
069 * A component that receives and sends byte buffers on an
070 * encrypted channel and sends and receives the corresponding
071 * decrypted data on a plain channel.
072 * 
073 * The encrypted channel is assumed to be the network side
074 * ("upstream") and therefore {@link Input} events represent
075 * encrypted data and are decoded to {@link Output} events on
076 * the plain channel ("downstream") and vice versa.
077 */
078@SuppressWarnings({ "PMD.ExcessiveImports" })
079public class SslCodec extends Component {
080
081    @SuppressWarnings("PMD.FieldNamingConventions")
082    private static final Logger logger
083        = Logger.getLogger(SslCodec.class.getName());
084
085    private final Channel encryptedChannel;
086    private final SSLContext sslContext;
087
088    /**
089     * Represents the encrypted channel in annotations.
090     */
091    private class EncryptedChannel extends ClassChannel {
092    }
093
094    /**
095     * Creates a new codec that uses the given {@link SSLContext}.
096     * 
097     * @param plainChannel the component's channel
098     * @param encryptedChannel the channel with the encrypted data
099     * @param sslContext the SSL context to use
100     */
101    public SslCodec(Channel plainChannel, Channel encryptedChannel,
102            SSLContext sslContext) {
103        super(plainChannel, ChannelReplacements.create()
104            .add(EncryptedChannel.class, encryptedChannel));
105        this.encryptedChannel = encryptedChannel;
106        this.sslContext = sslContext;
107    }
108
109    /**
110     * Creates a new codec to be used as client.
111     * 
112     * @param plainChannel the component's channel
113     * @param encryptedChannel the channel with the encrypted data
114     * @param dontValidate if `true` accept all kinds of certificates
115     */
116    @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.CommentRequired",
117        "PMD.ReturnEmptyArrayRatherThanNull", "PMD.UncommentedEmptyMethodBody",
118        "PMD.AvoidDuplicateLiterals" })
119    public SslCodec(Channel plainChannel, Channel encryptedChannel,
120            boolean dontValidate) {
121        super(plainChannel, ChannelReplacements.create()
122            .add(EncryptedChannel.class, encryptedChannel));
123        this.encryptedChannel = encryptedChannel;
124        try {
125            final SSLContext sslContext = SSLContext.getInstance("SSL");
126            if (dontValidate) {
127                // Create a trust manager that does not validate certificate
128                // chains
129                final TrustManager[] trustAllCerts = {
130                    new X509TrustManager() {
131                        public X509Certificate[] getAcceptedIssuers() {
132                            return null;
133                        }
134
135                        public void checkClientTrusted(
136                                X509Certificate[] certs, String authType) {
137                        }
138
139                        public void checkServerTrusted(
140                                X509Certificate[] certs, String authType) {
141                        }
142                    }
143                };
144                sslContext.init(null, trustAllCerts, null);
145            } else {
146                sslContext.init(null, null, null);
147            }
148            this.sslContext = sslContext;
149        } catch (NoSuchAlgorithmException | KeyManagementException e) {
150            throw new IllegalArgumentException(e);
151        }
152    }
153
154    /**
155     * Creates a new downstream connection as {@link LinkedIOSubchannel} 
156     * of the network connection together with an {@link SSLEngine}.
157     * 
158     * @param event
159     *            the accepted event
160     */
161    @Handler(channels = EncryptedChannel.class)
162    public void onAccepted(Accepted event, IOSubchannel encryptedChannel) {
163        new PlainChannel(event, encryptedChannel);
164    }
165
166    /**
167     * Forward the connection request to the encrypted network.
168     *
169     * @param event the event
170     */
171    @Handler
172    public void onOpenConnection(OpenTcpConnection event) {
173        fire(new OpenTcpConnection(event.address()), encryptedChannel);
174    }
175
176    /**
177     * Creates a new downstream connection as {@link LinkedIOSubchannel} 
178     * of the network connection together with an {@link SSLEngine}.
179     * 
180     * @param event
181     *            the accepted event
182     */
183    @Handler(channels = EncryptedChannel.class)
184    public void onConnected(Connected event, IOSubchannel encryptedChannel) {
185        new PlainChannel(event, encryptedChannel);
186    }
187
188    /**
189     * Handles encrypted data from upstream (the network). The data is 
190     * send through the {@link SSLEngine} and events are sent downstream 
191     * (and in the initial phases upstream) according to the conversion 
192     * results.
193     * 
194     * @param event the event
195     * @param encryptedChannel the channel for exchanging the encrypted data
196     * @throws InterruptedException 
197     * @throws SSLException 
198     * @throws ExecutionException 
199     */
200    @Handler(channels = EncryptedChannel.class)
201    public void onInput(
202            Input<ByteBuffer> event, IOSubchannel encryptedChannel)
203            throws InterruptedException, SSLException, ExecutionException {
204        @SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
205        final Optional<PlainChannel> plainChannel
206            = (Optional<PlainChannel>) LinkedIOSubchannel
207                .downstreamChannel(this, encryptedChannel);
208        if (plainChannel.isPresent()) {
209            plainChannel.get().sendDownstream(event);
210        }
211    }
212
213    /**
214     * Handles a half close event from the encrypted channel (client).
215     * 
216     * @param event the event
217     * @param encryptedChannel the channel for exchanging the encrypted data
218     * @throws InterruptedException 
219     * @throws SSLException 
220     */
221    @Handler(channels = EncryptedChannel.class)
222    public void onHalfClosed(HalfClosed event, IOSubchannel encryptedChannel)
223            throws SSLException, InterruptedException {
224        @SuppressWarnings("unchecked")
225        final Optional<PlainChannel> plainChannel
226            = (Optional<PlainChannel>) LinkedIOSubchannel
227                .downstreamChannel(this, encryptedChannel);
228        if (plainChannel.isPresent()) {
229            plainChannel.get().upstreamHalfClosed();
230        }
231    }
232
233    /**
234     * Handles a close event from the encrypted channel (client).
235     * 
236     * @param event the event
237     * @param encryptedChannel the channel for exchanging the encrypted data
238     * @throws InterruptedException 
239     * @throws SSLException 
240     */
241    @Handler(channels = EncryptedChannel.class)
242    public void onClosed(Closed event, IOSubchannel encryptedChannel)
243            throws SSLException, InterruptedException {
244        @SuppressWarnings("unchecked")
245        final Optional<PlainChannel> plainChannel
246            = (Optional<PlainChannel>) LinkedIOSubchannel
247                .downstreamChannel(this, encryptedChannel);
248        if (plainChannel.isPresent()) {
249            plainChannel.get().upstreamClosed();
250        }
251    }
252
253    /**
254     * Forwards a {@link Purge} event downstream.
255     *
256     * @param event the event
257     * @param encryptedChannel the encrypted channel
258     */
259    @Handler(channels = EncryptedChannel.class)
260    public void onPurge(Purge event, IOSubchannel encryptedChannel) {
261        @SuppressWarnings("unchecked")
262        final Optional<PlainChannel> plainChannel
263            = (Optional<PlainChannel>) LinkedIOSubchannel
264                .downstreamChannel(this, encryptedChannel);
265        if (plainChannel.isPresent()) {
266            plainChannel.get().purge();
267        }
268    }
269
270    /**
271     * Handles an {@link IOError} event from the encrypted channel (client)
272     * by sending it downstream.
273     * 
274     * @param event the event
275     * @param encryptedChannel the channel for exchanging the encrypted data
276     * @throws InterruptedException 
277     * @throws SSLException 
278     */
279    @Handler(channels = EncryptedChannel.class)
280    public void onIOError(IOError event, IOSubchannel encryptedChannel)
281            throws SSLException, InterruptedException {
282        @SuppressWarnings("unchecked")
283        final Optional<PlainChannel> plainChannel
284            = (Optional<PlainChannel>) LinkedIOSubchannel
285                .downstreamChannel(this, encryptedChannel);
286        plainChannel.ifPresent(channel -> fire(new IOError(event), channel));
287    }
288
289    /**
290     * Sends plain data through the engine and then upstream.
291     * 
292     * @param event
293     *            the event with the data
294     * @throws InterruptedException if the execution was interrupted
295     * @throws SSLException if some SSL related problem occurs
296     * @throws ExecutionException 
297     */
298    @Handler
299    public void onOutput(Output<ByteBuffer> event,
300            PlainChannel plainChannel)
301            throws InterruptedException, SSLException, ExecutionException {
302        if (plainChannel.hub() != this) {
303            return;
304        }
305        plainChannel.sendUpstream(event);
306    }
307
308    /**
309     * Forwards a close event upstream.
310     * 
311     * @param event
312     *            the close event
313     * @throws SSLException if an SSL related problem occurs
314     * @throws InterruptedException if the execution was interrupted
315     */
316    @Handler
317    public void onClose(Close event, PlainChannel plainChannel)
318            throws InterruptedException, SSLException {
319        if (plainChannel.hub() != this) {
320            return;
321        }
322        plainChannel.close(event);
323    }
324
325    /**
326     * Represents the plain channel.
327     */
328    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
329    private class PlainChannel extends LinkedIOSubchannel {
330        public SocketAddress localAddress;
331        public SocketAddress remoteAddress;
332        public SSLEngine sslEngine;
333        private EventPipeline downPipeline;
334        private ManagedBufferPool<ManagedBuffer<ByteBuffer>,
335                ByteBuffer> downstreamPool;
336        private ByteBuffer carryOver;
337        private boolean[] inputProcessed = { false };
338
339        /**
340         * Instantiates a new plain channel from an accepted connection.
341         *
342         * @param event the event
343         * @param upstreamChannel the upstream channel
344         */
345        public PlainChannel(Accepted event, IOSubchannel upstreamChannel) {
346            super(SslCodec.this, channel(), upstreamChannel,
347                newEventPipeline());
348            localAddress = event.localAddress();
349            remoteAddress = event.remoteAddress();
350            init();
351            sslEngine.setUseClientMode(false);
352        }
353
354        /**
355         * Instantiates a new plain channel from an initiated connection.
356         *
357         * @param event the event
358         * @param upstreamChannel the upstream channel
359         */
360        public PlainChannel(Connected event, IOSubchannel upstreamChannel) {
361            super(SslCodec.this, channel(), upstreamChannel,
362                newEventPipeline());
363            localAddress = event.localAddress();
364            remoteAddress = event.remoteAddress();
365            init();
366            sslEngine.setUseClientMode(true);
367
368            // Forward downstream
369            downPipeline.fire(
370                new Connected(event.localAddress(), event.remoteAddress()),
371                this);
372        }
373
374        private void init() {
375            if (remoteAddress instanceof InetSocketAddress) {
376                sslEngine = sslContext.createSSLEngine(
377                    ((InetSocketAddress) remoteAddress).getAddress()
378                        .getHostAddress(),
379                    ((InetSocketAddress) remoteAddress).getPort());
380            } else {
381                sslEngine = sslContext.createSSLEngine();
382            }
383            String channelName = Components.objectName(SslCodec.this)
384                + "." + Components.objectName(this);
385            // Create buffer pools, adding 50 to decoded application buffer
386            // size, see
387            // https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/samples/sslengine/SSLEngineSimpleDemo.java
388            final int appBufSize
389                = sslEngine.getSession().getApplicationBufferSize();
390            downstreamPool = new ManagedBufferPool<>(ManagedBuffer::new,
391                () -> ByteBuffer.allocate(appBufSize + 50), 2)
392                    .setName(channelName + ".downstream.buffers");
393            // Provide buffers with application buffer size
394            // for use by downstream components.
395            setByteBufferPool(new ManagedBufferPool<>(ManagedBuffer::new,
396                () -> ByteBuffer.allocate(appBufSize), 2)
397                    .setName(channelName + ".upstream.buffers"));
398            downPipeline = newEventPipeline();
399            // Buffers for sending encrypted data upstream will be
400            // obtained from upstream() and resized if required.
401        }
402
403        /**
404         * Sends input downstream.
405         *
406         * @param event the event
407         * @throws SSLException the SSL exception
408         * @throws InterruptedException the interrupted exception
409         * @throws ExecutionException the execution exception
410         */
411        public void sendDownstream(Input<ByteBuffer> event)
412                throws SSLException, InterruptedException, ExecutionException {
413            ByteBuffer input = event.buffer().duplicate();
414            if (carryOver != null) {
415                if (carryOver.remaining() < input.remaining()) {
416                    // Shouldn't happen with carryOver having packet size
417                    // bytes left, have seen it happen nevertheless.
418                    carryOver.flip();
419                    ByteBuffer extCarryOver = ByteBuffer.allocate(
420                        carryOver.remaining() + input.remaining());
421                    extCarryOver.put(carryOver);
422                    carryOver = extCarryOver;
423                }
424                carryOver.put(input);
425                carryOver.flip();
426                input = carryOver;
427                carryOver = null;
428            }
429
430            // Main processing
431            processInput(input);
432
433            // Check if data from incomplete packet remains in input buffer
434            if (input.hasRemaining()) {
435                // Actually, packet buffer size should be sufficient,
436                // but since this is hard to test and doesn't really matter...
437                carryOver = ByteBuffer.allocate(input.remaining()
438                    + sslEngine.getSession().getPacketBufferSize() + 50);
439                carryOver.put(input);
440            }
441        }
442
443        @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount",
444            "PMD.AvoidInstantiatingObjectsInLoops", "PMD.ExcessiveMethodLength",
445            "PMD.NPathComplexity" })
446        private SSLEngineResult processInput(ByteBuffer input)
447                throws SSLException, InterruptedException, ExecutionException {
448            SSLEngineResult unwrapResult;
449            ManagedBuffer<ByteBuffer> unwrapped = downstreamPool.acquire();
450            while (true) {
451                unwrapResult
452                    = sslEngine.unwrap(input, unwrapped.backingBuffer());
453                synchronized (inputProcessed) {
454                    inputProcessed[0] = true;
455                    inputProcessed.notifyAll();
456                }
457                // Handle any handshaking procedures
458                switch (unwrapResult.getHandshakeStatus()) {
459                case NEED_TASK:
460                    while (true) {
461                        Runnable runnable = sslEngine.getDelegatedTask();
462                        if (runnable == null) {
463                            break;
464                        }
465                        // Having this handled by the response thread is
466                        // probably not really necessary, but as the delegated
467                        // task usually includes sending upstream...
468                        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
469                        FutureTask<Boolean> task
470                            = new FutureTask<>(runnable, true);
471                        upstreamChannel().responsePipeline()
472                            .executorService().submit(task);
473                        task.get();
474                    }
475                    continue;
476
477                case NEED_WRAP:
478                    ManagedBuffer<ByteBuffer> feedback
479                        = acquireUpstreamBuffer();
480                    synchronized (sslEngine) {
481                        SSLEngineResult wrapResult = sslEngine.wrap(
482                            ManagedBuffer.EMPTY_BYTE_BUFFER.backingBuffer(),
483                            feedback.backingBuffer());
484                        // JDK11 sometimes returns NEED_WRAP (together
485                        // with status is CLOSED) but does not produce
486                        // anything when wrapping (all of which does
487                        // not make sense).
488                        if (feedback.position() == 0
489                            && unwrapResult.getStatus() == Status.CLOSED) {
490                            feedback.unlockBuffer();
491                            break;
492                        }
493                        upstreamChannel()
494                            .respond(Output.fromSink(feedback, false));
495                        if (wrapResult
496                            .getHandshakeStatus() == HandshakeStatus.FINISHED) {
497                            fireAccepted();
498                        }
499                    }
500                    continue;
501
502                case FINISHED:
503                    fireAccepted();
504                    break;
505
506                default:
507                    break;
508                }
509
510                // Anything to forward downstream?
511                if (unwrapped.position() > 0) {
512                    // forward unwrapped data
513                    downPipeline.fire(Input.fromSink(unwrapped,
514                        sslEngine.isInboundDone()), this);
515                    unwrapped = null;
516                }
517
518                // If we have a buffer overflow or everything was okay
519                // and there's data left, we try again, else we quit.
520                if (unwrapResult.getStatus() != Status.BUFFER_OVERFLOW
521                    && (unwrapResult.getStatus() != Status.OK
522                        || !input.hasRemaining())) {
523                    // Underflow or closed
524                    if (unwrapped != null) {
525                        unwrapped.unlockBuffer();
526                    }
527                    break;
528                }
529
530                // Make sure we have an output buffer.
531                if (unwrapped == null) {
532                    unwrapped = downstreamPool.acquire();
533                }
534            }
535            return unwrapResult;
536        }
537
538        @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
539        private void fireAccepted() {
540            List<SNIServerName> snis = Collections.emptyList();
541            if (sslEngine.getSession() instanceof ExtendedSSLSession) {
542                snis = ((ExtendedSSLSession) sslEngine.getSession())
543                    .getRequestedServerNames();
544            }
545            downPipeline.fire(new Accepted(
546                localAddress, remoteAddress, true, snis), this);
547        }
548
549        /**
550         * Send output upstream.
551         *
552         * @param event the event
553         * @throws SSLException the SSL exception
554         * @throws InterruptedException the interrupted exception
555         * @throws ExecutionException 
556         */
557        public void sendUpstream(Output<ByteBuffer> event)
558                throws SSLException, InterruptedException, ExecutionException {
559            ByteBuffer output = event.buffer().backingBuffer().duplicate();
560            processOutput(output, event.isEndOfRecord());
561        }
562
563        @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis",
564            "PMD.CyclomaticComplexity", "PMD.NcssCount",
565            "PMD.NPathComplexity" })
566        private void processOutput(ByteBuffer output, boolean eor)
567                throws InterruptedException, SSLException, ExecutionException {
568            ManagedBuffer<ByteBuffer> wrapped = acquireUpstreamBuffer();
569            while (true) {
570                SSLEngineResult wrapResult;
571                // Cheap synchronization: no (relevant) input
572                inputProcessed[0] = false;
573                synchronized (sslEngine) {
574                    wrapResult
575                        = sslEngine.wrap(output, wrapped.backingBuffer());
576                    // Anything to be sent upstream?
577                    if (wrapped.position() > 0) {
578                        upstreamChannel().respond(Output.fromSink(wrapped,
579                            sslEngine.isInboundDone()
580                                || eor && !output.hasRemaining()));
581                        wrapped = null;
582                    }
583                }
584                switch (wrapResult.getHandshakeStatus()) {
585                case NEED_TASK:
586                    while (true) {
587                        Runnable runnable = sslEngine.getDelegatedTask();
588                        if (runnable == null) {
589                            break;
590                        }
591                        runnable.run();
592                    }
593                    continue;
594
595                case NEED_UNWRAP:
596                    // Input required. Wait until
597                    // input becomes available and retry.
598                    synchronized (inputProcessed) {
599                        while (!inputProcessed[0]) {
600                            inputProcessed.wait();
601                        }
602                    }
603                    break;
604
605                default:
606                    break;
607                }
608
609                // If we have a buffer overflow or everything was okay
610                // and there's data left, we try again, else we quit.
611                if (wrapResult.getStatus() != Status.BUFFER_OVERFLOW
612                    && (wrapResult.getStatus() != Status.OK
613                        || !output.hasRemaining())) {
614                    // Underflow or closed
615                    if (wrapped != null) {
616                        wrapped.unlockBuffer();
617                    }
618                    // Warn if data is discarded
619                    if (output.hasRemaining()) {
620                        logger.warning(() -> toString()
621                            + ": Upstream data discarded, SSLEngine status: "
622                            + wrapResult.getStatus());
623                    }
624                    break;
625                }
626
627                // Was handshake (or partial content), get new buffer and try
628                // again
629                if (wrapped == null) {
630                    wrapped = acquireUpstreamBuffer();
631                }
632            }
633        }
634
635        /**
636         * Close the connection.
637         *
638         * @param event the event
639         * @throws InterruptedException the interrupted exception
640         * @throws SSLException the SSL exception
641         */
642        public void close(Close event)
643                throws InterruptedException, SSLException {
644            sslEngine.closeOutbound();
645            while (!sslEngine.isOutboundDone()) {
646                ManagedBuffer<ByteBuffer> feedback = acquireUpstreamBuffer();
647                sslEngine.wrap(ManagedBuffer.EMPTY_BYTE_BUFFER
648                    .backingBuffer(), feedback.backingBuffer());
649                upstreamChannel().respond(Output.fromSink(feedback, false));
650            }
651            upstreamChannel().respond(new Close());
652        }
653
654        /**
655         * Handles the {@link HalfClosed} event.
656         *
657         * @throws SSLException the SSL exception
658         * @throws InterruptedException the interrupted exception
659         */
660        public void upstreamHalfClosed()
661                throws SSLException, InterruptedException {
662            if (sslEngine.isInboundDone()) {
663                // Was properly closed on SSL level
664                return;
665            }
666            try {
667                sslEngine.closeInbound();
668                sslEngine.closeOutbound();
669                while (!sslEngine.isOutboundDone()) {
670                    ManagedBuffer<ByteBuffer> feedback
671                        = acquireUpstreamBuffer();
672                    SSLEngineResult result = sslEngine.wrap(
673                        ManagedBuffer.EMPTY_BYTE_BUFFER.backingBuffer(),
674                        feedback.backingBuffer());
675                    // This is required for/since JDK 11. It claims that
676                    // outbound is not done, but doesn't produce any
677                    // additional
678                    // data.
679                    if (result.getStatus() == Status.CLOSED
680                        || feedback.position() == 0) {
681                        feedback.unlockBuffer();
682                        break;
683                    }
684                    upstreamChannel()
685                        .respond(Output.fromSink(feedback, false));
686                }
687            } catch (SSLException e) {
688                // Several clients (notably chromium, see
689                // https://bugs.chromium.org/p/chromium/issues/detail?id=118366
690                // don't close the connection properly. So nobody is really
691                // interested in this message
692                logger.log(Level.FINEST, e.getMessage(), e);
693            } catch (InterruptedException e) {
694                logger.log(Level.WARNING, e.getMessage(), e);
695            }
696        }
697
698        /**
699         * Forwards the {@link Closed} event downstream.
700         *
701         * @throws SSLException the SSL exception
702         * @throws InterruptedException the interrupted exception
703         */
704        public void upstreamClosed()
705                throws SSLException, InterruptedException {
706            downPipeline.fire(new Closed(), this);
707        }
708
709        private ManagedBuffer<ByteBuffer> acquireUpstreamBuffer()
710                throws InterruptedException {
711            ManagedBuffer<ByteBuffer> feedback
712                = upstreamChannel().byteBufferPool().acquire();
713            int encSize
714                = sslEngine.getSession().getPacketBufferSize() + 50;
715            if (feedback.capacity() < encSize) {
716                feedback.replaceBackingBuffer(ByteBuffer.allocate(
717                    encSize));
718            }
719            return feedback;
720        }
721
722        /**
723         * Fire a {@link Purge} event downstream.
724         */
725        public void purge() {
726            downPipeline.fire(new Purge(), this);
727        }
728    }
729}