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