Bug 2231 - Secure transport for PCEP
[bgpcep.git] / pcep / impl / src / main / java / org / opendaylight / protocol / pcep / impl / AbstractPCEPSessionNegotiator.java
index 58cfdf5fe4d6b14a3ff1e25b4712b4089da67a48..bf859614097fc2971ec6bcdf06a9a5a7c41178f7 100644 (file)
  */
 package org.opendaylight.protocol.pcep.impl;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
 import io.netty.channel.Channel;
-import io.netty.util.Timeout;
-import io.netty.util.Timer;
-import io.netty.util.TimerTask;
+import io.netty.handler.ssl.SslHandler;
 import io.netty.util.concurrent.Promise;
-
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import javax.annotation.concurrent.GuardedBy;
-
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
-import org.opendaylight.protocol.pcep.PCEPErrors;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.pcerr.pcerr.message.error.type.Session;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.KeepaliveMessage;
+import org.opendaylight.protocol.pcep.impl.tls.SslContextFactory;
+import org.opendaylight.protocol.pcep.spi.PCEPErrors;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Starttls;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.StarttlsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenObject;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.PcerrMessage;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessageBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcep.error.object.ErrorObject;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.error.type.SessionCase;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.start.tls.message.StartTlsMessageBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Preconditions;
-
 /**
  * Abstract PCEP session negotiator. Takes care of basic handshake without implementing a specific policy. Policies need
  * to be provided by a specific subclass.
  */
 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
-       /**
-        * Unified KeepWait and OpenWait timer expiration, in seconds.
-        */
-       public static final int FAIL_TIMER_VALUE = 60;
-
-       /**
-        * PCEP session negotiation state transitions are described in RFC5440. Simplification the two timers (KeepWait and
-        * OpenWait) are merged into a FailTimer, as they are mutually exclusive, have the same timeout value and their
-        * action is to terminate negotiation. This timer is restarted between state transitions and runs in all states
-        * except Idle and Finished.
-        */
-       private enum State {
-               /**
-                * Negotiation has not begun. It will be activated once we are asked to provide our initial proposal, at which
-                * point we move into OpenWait state.
-                */
-               Idle,
-               /**
-                * Waiting for the peer's OPEN message.
-                */
-               OpenWait,
-               /**
-                * Waiting for the peer's KEEPALIVE message.
-                */
-               KeepWait,
-               /**
-                * Negotiation has completed.
-                */
-               Finished,
-       }
-
-       private static final Logger logger = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
-
-       private final Timer timer;
-
-       @GuardedBy("this")
-       private State state = State.Idle;
-
-       @GuardedBy("this")
-       private Timeout failTimer;
-
-       @GuardedBy("this")
-       private OpenObject localPrefs;
-
-       @GuardedBy("this")
-       private OpenObject remotePrefs;
-
-       private volatile boolean localOK, openRetry, remoteOK;
-
-       protected AbstractPCEPSessionNegotiator(final Timer timer, final Promise<PCEPSessionImpl> promise, final Channel channel) {
-               super(promise, channel);
-               this.timer = Preconditions.checkNotNull(timer);
-       }
-
-       /**
-        * Get the initial session parameters proposal.
-        * 
-        * @return Session parameters proposal.
-        */
-       protected abstract OpenObject getInitialProposal();
-
-       /**
-        * Get the revised session parameters proposal based on the feedback the peer has provided to us.
-        * 
-        * @param suggestion Peer-provided suggested session parameters
-        * @return Session parameters proposal.
-        */
-       protected abstract OpenObject getRevisedProposal(OpenObject suggestion);
-
-       /**
-        * Check whether a peer-provided session parameters proposal is acceptable.
-        * 
-        * @param proposal peer-proposed session parameters
-        * @return true if the proposal is acceptable, false otherwise
-        */
-       protected abstract boolean isProposalAcceptable(OpenObject proposal);
-
-       /**
-        * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
-        * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
-        * 
-        * @param proposal unacceptable peer proposal
-        * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
-        */
-       protected abstract OpenObject getCounterProposal(OpenObject proposal);
-
-       /**
-        * Create the protocol session.
-        * 
-        * @param timer Timer which the session can use for its various functions.
-        * @param channel Underlying channel.
-        * @param sessionId Assigned session ID.
-        * @param localPrefs Session preferences proposed by us and accepted by the peer.
-        * @param remotePrefs Session preferences proposed by the peer and accepted by us.
-        * @return New protocol session.
-        */
-       protected abstract PCEPSessionImpl createSession(Timer timer, Channel channel, OpenObject localPrefs, OpenObject remotePrefs);
-
-       /**
-        * Sends PCEP Error Message with one PCEPError.
-        * 
-        * @param value
-        */
-       private void sendErrorMessage(final PCEPErrors value) {
-
-               this.channel.writeAndFlush(Util.createErrorMessage(value, null));
-       }
-
-       private void scheduleFailTimer() {
-               final Object lock = this;
-
-               this.failTimer = this.timer.newTimeout(new TimerTask() {
-                       @Override
-                       public void run(final Timeout timeout) throws Exception {
-                               synchronized (lock) {
-                                       // This closes the race between timer expiring and new timer
-                                       // being armed while it waits for the lock.
-                                       if (AbstractPCEPSessionNegotiator.this.failTimer == timeout) {
-                                               switch (AbstractPCEPSessionNegotiator.this.state) {
-                                               case Finished:
-                                               case Idle:
-                                                       break;
-                                               case KeepWait:
-                                                       sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
-                                                       negotiationFailed(new TimeoutException("KeepWait timer expired"));
-                                                       AbstractPCEPSessionNegotiator.this.state = State.Finished;
-                                                       break;
-                                               case OpenWait:
-                                                       sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
-                                                       negotiationFailed(new TimeoutException("OpenWait timer expired"));
-                                                       AbstractPCEPSessionNegotiator.this.state = State.Finished;
-                                                       break;
-                                               }
-                                       }
-                               }
-                       }
-               }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
-       }
-
-       @Override
-       final synchronized protected void startNegotiation() {
-               Preconditions.checkState(this.state == State.Idle);
-               this.localPrefs = getInitialProposal();
-               final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
-                               new OpenMessageBuilder().setOpen(
-                                               (org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.open.message.Open) this.localPrefs).build()).build();
-               this.channel.writeAndFlush(m);
-               this.state = State.OpenWait;
-               scheduleFailTimer();
-
-               logger.debug("Channel {} started sent proposal {}", this.channel, this.localPrefs);
-       }
-
-       @Override
-       final synchronized protected void handleMessage(final Message msg) throws Exception {
-               this.failTimer.cancel();
-
-               logger.debug("Channel {} handling message in state {}", this.channel, this.state);
-
-               switch (this.state) {
-               case Finished:
-               case Idle:
-                       throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
-               case KeepWait:
-                       if (msg instanceof KeepaliveMessage) {
-                               this.localOK = true;
-                               if (this.remoteOK) {
-                                       negotiationSuccessful(createSession(this.timer, this.channel, this.localPrefs, this.remotePrefs));
-                                       this.state = State.Finished;
-                               } else {
-                                       scheduleFailTimer();
-                                       this.state = State.OpenWait;
-                                       logger.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
-                               }
-
-                               return;
-                       } else if (msg instanceof PcerrMessage) {
-                               final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((PcerrMessage) msg).getPcerrMessage();
-                               this.localPrefs = getRevisedProposal((OpenObject) ((Session) err.getErrorType()).getOpen());
-                               if (this.localPrefs == null) {
-                                       sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
-                                       negotiationFailed(new RuntimeException("Peer suggested unacceptable retry proposal"));
-                                       this.state = State.Finished;
-                                       return;
-                               }
-
-                               if (!this.remoteOK) {
-                                       this.state = State.OpenWait;
-                               }
-                               scheduleFailTimer();
-                               return;
-                       }
-
-                       break;
-               case OpenWait:
-                       if (msg instanceof OpenMessage) {
-                               final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessage o = ((OpenMessage) msg).getOpenMessage();
-                               final OpenObject open = o.getOpen();
-                               if (isProposalAcceptable(open)) {
-                                       this.channel.writeAndFlush(new KeepaliveMessageBuilder().build());
-                                       this.remotePrefs = open;
-                                       this.remoteOK = true;
-                                       if (this.localOK) {
-                                               negotiationSuccessful(createSession(this.timer, this.channel, this.localPrefs, this.remotePrefs));
-                                               this.state = State.Finished;
-                                       } else {
-                                               scheduleFailTimer();
-                                               this.state = State.KeepWait;
-                                               logger.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
-                                       }
-                                       return;
-                               }
-
-                               if (this.openRetry) {
-                                       sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
-                                       negotiationFailed(new RuntimeException("OPEN renegotiation failed"));
-                                       this.state = State.Finished;
-                                       return;
-                               }
-
-                               final OpenObject newPrefs = getCounterProposal(open);
-                               if (newPrefs == null) {
-                                       sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
-                                       negotiationFailed(new RuntimeException("Peer sent unacceptable session parameters"));
-                                       this.state = State.Finished;
-                                       return;
-                               }
-
-                               this.channel.writeAndFlush(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
-
-                               this.openRetry = true;
-                               this.state = this.localOK ? State.OpenWait : State.KeepWait;
-                               scheduleFailTimer();
-                               return;
-                       }
-
-                       break;
-               }
-
-               logger.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
-               sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
-               negotiationFailed(new Exception("Illegal message encountered"));
-               this.state = State.Finished;
-       }
+    /**
+     * Unified KeepWait and OpenWait timer expiration, in seconds.
+     */
+    public static final int FAIL_TIMER_VALUE = 60;
+
+    /**
+     * PCEP session negotiation state transitions are described in RFC5440. Simplification the two timers (KeepWait and
+     * OpenWait) are merged into a FailTimer, as they are mutually exclusive, have the same timeout value and their
+     * action is to terminate negotiation. This timer is restarted between state transitions and runs in all states
+     * except Idle and Finished.
+     */
+    @VisibleForTesting
+    public enum State {
+        /**
+         * Negotiation has not begun. It will be activated once we are asked to provide our initial proposal, at which
+         * point we move into OpenWait state.
+         */
+        IDLE,
+        /**
+         * Waiting for the peer's StartTLS message
+         */
+        START_TLS_WAIT,
+        /**
+         * Waiting for the peer's OPEN message.
+         */
+        OPEN_WAIT,
+        /**
+         * Waiting for the peer's KEEPALIVE message.
+         */
+        KEEP_WAIT,
+        /**
+         * Negotiation has completed.
+         */
+        FINISHED,
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
+    private static final Keepalive KEEPALIVE = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
+
+    private volatile boolean localOK, openRetry, remoteOK;
+    private volatile State state = State.IDLE;
+    private Future<?> failTimer;
+    private Open localPrefs;
+    private Open remotePrefs;
+    private Tls tlsConfiguration;
+
+    protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
+        super(promise, channel);
+    }
+
+    /**
+     * Get the initial session parameters proposal.
+     *
+     * @return Session parameters proposal.
+     */
+    protected abstract Open getInitialProposal();
+
+    /**
+     * Get the revised session parameters proposal based on the feedback the peer has provided to us.
+     *
+     * @param suggestion Peer-provided suggested session parameters
+     * @return Session parameters proposal, or null if peers session parameters preclude us from suggesting anything
+     */
+    protected abstract Open getRevisedProposal(Open suggestion);
+
+    /**
+     * Check whether a peer-provided session parameters proposal is acceptable.
+     *
+     * @param proposal peer-proposed session parameters
+     * @return true if the proposal is acceptable, false otherwise
+     */
+    protected abstract boolean isProposalAcceptable(Open proposal);
+
+    /**
+     * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
+     * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
+     *
+     * @param proposal unacceptable peer proposal
+     * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
+     */
+    protected abstract Open getCounterProposal(Open proposal);
+
+    /**
+     * Create the protocol session.
+     *
+     * @param channel Underlying channel.
+     * @param localPrefs Session preferences proposed by us and accepted by the peer.
+     * @param remotePrefs Session preferences proposed by the peer and accepted by us.
+     * @return New protocol session.
+     */
+    protected abstract PCEPSessionImpl createSession(Channel channel, Open localPrefs, Open remotePrefs);
+
+    /**
+     * Sends PCEP Error Message with one PCEPError.
+     *
+     * @param value
+     */
+    private void sendErrorMessage(final PCEPErrors value) {
+
+        this.sendMessage(Util.createErrorMessage(value, null));
+    }
+
+    private void scheduleFailTimer() {
+        this.failTimer = this.channel.eventLoop().schedule(new Runnable() {
+            @Override
+            public void run() {
+                switch (AbstractPCEPSessionNegotiator.this.state) {
+                case FINISHED:
+                case IDLE:
+                    break;
+                case START_TLS_WAIT:
+                    sendErrorMessage(PCEPErrors.STARTTLS_TIMER_EXP);
+                    negotiationFailed(new TimeoutException("StartTLSWait timer expired"));
+                    AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
+                case KEEP_WAIT:
+                    sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
+                    negotiationFailed(new TimeoutException("KeepWait timer expired"));
+                    AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
+                    break;
+                case OPEN_WAIT:
+                    sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
+                    negotiationFailed(new TimeoutException("OpenWait timer expired"));
+                    AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
+                    break;
+                default:
+                    break;
+                }
+            }
+        }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
+    }
+
+    @Override
+    protected final void startNegotiation() {
+        Preconditions.checkState(this.state == State.IDLE);
+        if (this.tlsConfiguration != null) {
+            this.sendMessage(new StarttlsBuilder().setStartTlsMessage(new StartTlsMessageBuilder().build()).build());
+            this.state = State.START_TLS_WAIT;
+            scheduleFailTimer();
+            LOG.info("Started TLS connection negotiation with peer {}", this.channel);
+        } else {
+            startNegotiationWithOpen();
+        }
+    }
+
+    private void startNegotiationWithOpen() {
+        this.localPrefs = getInitialProposal();
+        final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
+                new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
+        this.sendMessage(m);
+        this.state = State.OPEN_WAIT;
+        scheduleFailTimer();
+
+        LOG.info("PCEP session with {} started, sent proposal {}", this.channel, this.localPrefs);
+    }
+
+    private boolean handleMessageKeepWait(final Message msg) {
+        if (msg instanceof Keepalive) {
+            this.localOK = true;
+            if (this.remoteOK) {
+                LOG.info("PCEP peer {} completed negotiation", this.channel);
+                negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
+                this.state = State.FINISHED;
+            } else {
+                scheduleFailTimer();
+                this.state = State.OPEN_WAIT;
+                LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
+            }
+            return true;
+        } else if (msg instanceof Pcerr) {
+            final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
+            if (err.getErrorType() == null) {
+                final ErrorObject obj = err.getErrors().get(0).getErrorObject();
+                LOG.warn("Unexpected error received from PCC: type {} value {}", obj.getType(), obj.getValue());
+                negotiationFailed(new IllegalStateException("Unexpected error received from PCC."));
+                this.state = State.IDLE;
+                return true;
+            }
+            this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
+            if (this.localPrefs == null) {
+                sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
+                negotiationFailed(new IllegalStateException("Peer suggested unacceptable retry proposal"));
+                this.state = State.FINISHED;
+                return true;
+            }
+            this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
+            if (!this.remoteOK) {
+                this.state = State.OPEN_WAIT;
+            }
+            scheduleFailTimer();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean handleMessageOpenWait(final Message msg) {
+        if (!(msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open)) {
+            return false;
+        }
+        final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessage o = ((org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) msg).getOpenMessage();
+        final Open open = o.getOpen();
+        if (isProposalAcceptable(open)) {
+            this.sendMessage(KEEPALIVE);
+            this.remotePrefs = open;
+            this.remoteOK = true;
+            if (this.localOK) {
+                negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
+                LOG.info("PCEP peer {} completed negotiation", this.channel);
+                this.state = State.FINISHED;
+            } else {
+                scheduleFailTimer();
+                this.state = State.KEEP_WAIT;
+                LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
+            }
+            return true;
+        }
+        if (this.openRetry) {
+            sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
+            negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
+            this.state = State.FINISHED;
+            return true;
+        }
+        final Open newPrefs = getCounterProposal(open);
+        if (newPrefs == null) {
+            sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
+            negotiationFailed(new IllegalStateException("Peer sent unacceptable session parameters"));
+            this.state = State.FINISHED;
+            return true;
+        }
+        this.sendMessage(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
+        this.openRetry = true;
+        this.state = this.localOK ? State.OPEN_WAIT : State.KEEP_WAIT;
+        scheduleFailTimer();
+        return true;
+    }
+
+    private boolean handleMessageStartTlsWait(final Message msg) {
+        if (msg instanceof Starttls) {
+            final SslContextFactory sslFactory = new SslContextFactory(this.tlsConfiguration);
+            final SSLContext sslContext = sslFactory.getServerContext();
+            if (sslContext == null) {
+                this.sendErrorMessage(PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS);
+                negotiationFailed(new IllegalStateException("Failed to establish a TLS connection."));
+                this.state = State.FINISHED;
+                return true;
+            }
+            final SSLEngine engine = sslContext.createSSLEngine();
+            engine.setNeedClientAuth(true);
+            engine.setUseClientMode(false);
+            this.channel.pipeline().addFirst(new SslHandler(engine));
+            LOG.info("PCEPS TLS connection with peer: {} established succesfully.", this.channel);
+            startNegotiationWithOpen();
+            return true;
+        } else if (! (msg instanceof Pcerr)) {
+            this.sendErrorMessage(PCEPErrors.NON_STARTTLS_MSG_RCVD);
+            negotiationFailed(new IllegalStateException("Unexpected message recieved."));
+            this.state = State.FINISHED;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected final void handleMessage(final Message msg) {
+        this.failTimer.cancel(false);
+
+        LOG.debug("Channel {} handling message {} in state {}", this.channel, msg, this.state);
+
+        switch (this.state) {
+        case FINISHED:
+        case IDLE:
+            throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
+        case START_TLS_WAIT:
+            if (handleMessageStartTlsWait(msg)) {
+                return;
+            }
+            break;
+        case KEEP_WAIT:
+            if (handleMessageKeepWait(msg)) {
+                return;
+            }
+            break;
+        case OPEN_WAIT:
+            if (handleMessageOpenWait(msg)) {
+                return;
+            }
+            break;
+        default:
+            break;
+        }
+        LOG.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
+        sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
+        negotiationFailed(new Exception("Illegal message encountered"));
+        this.state = State.FINISHED;
+    }
+
+    @VisibleForTesting
+    State getState() {
+        return this.state;
+    }
+
+    public void setTlsConfiguration(final Tls tlsConfiguration) {
+        this.tlsConfiguration = tlsConfiguration;
+    }
 }