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