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