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