BUG-58: refactor to take advantage of netty
[bgpcep.git] / pcep / impl / src / main / java / org / opendaylight / protocol / pcep / impl / AbstractPCEPSessionNegotiator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.pcep.impl;
9
10 import io.netty.channel.Channel;
11 import io.netty.util.Timeout;
12 import io.netty.util.Timer;
13 import io.netty.util.TimerTask;
14 import io.netty.util.concurrent.Promise;
15
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
18
19 import javax.annotation.concurrent.GuardedBy;
20
21 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
22 import org.opendaylight.protocol.pcep.PCEPErrors;
23 import org.opendaylight.protocol.pcep.PCEPMessage;
24 import org.opendaylight.protocol.pcep.message.PCEPErrorMessage;
25 import org.opendaylight.protocol.pcep.message.PCEPKeepAliveMessage;
26 import org.opendaylight.protocol.pcep.message.PCEPOpenMessage;
27 import org.opendaylight.protocol.pcep.object.PCEPErrorObject;
28 import org.opendaylight.protocol.pcep.object.PCEPOpenObject;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import com.google.common.base.Preconditions;
33 import com.google.common.collect.ImmutableList;
34
35 /**
36  * Abstract PCEP session negotiator. Takes care of basic handshake without
37  * implementing a specific policy. Policies need to be provided by a specific
38  * subclass.
39  */
40 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<PCEPMessage, PCEPSessionImpl> {
41         /**
42          * Unified KeepWait and OpenWait timer expiration, in seconds.
43          */
44         public static final int FAIL_TIMER_VALUE = 60;
45
46         /**
47          * PCEP session negotiation state transitions are described in RFC5440.
48          * Simplification the two timers (KeepWait and OpenWait) are merged into
49          * a FailTimer, as they are mutually exclusive, have the same timeout
50          * value and their action is to terminate negotiation. This timer is
51          * restarted between state transitions and runs in all states except
52          * Idle and Finished.
53          */
54         private enum State {
55                 /**
56                  * Negotiation has not begun. It will be activated once we are asked
57                  * to provide our initial proposal, at which point we move into
58                  * OpenWait state.
59                  */
60                 Idle,
61                 /**
62                  * Waiting for the peer's OPEN message.
63                  */
64                 OpenWait,
65                 /**
66                  * Waiting for the peer's KEEPALIVE message.
67                  */
68                 KeepWait,
69                 /**
70                  * Negotiation has completed.
71                  */
72                 Finished,
73         }
74
75         private static final Logger logger = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
76         private final Timer timer;
77
78         @GuardedBy("this")
79         private State state = State.Idle;
80
81         @GuardedBy("this")
82         private Timeout failTimer;
83
84         @GuardedBy("this")
85         private PCEPOpenObject localPrefs;
86
87         @GuardedBy("this")
88         private PCEPOpenObject remotePrefs;
89
90         private volatile boolean localOK, openRetry, remoteOK;
91
92         protected AbstractPCEPSessionNegotiator(final Timer timer, final Promise<PCEPSessionImpl> promise, final Channel channel) {
93                 super(promise, channel);
94                 this.timer = Preconditions.checkNotNull(timer);
95         }
96
97         /**
98          * Get the initial session parameters proposal.
99          * @return Session parameters proposal.
100          */
101         protected abstract PCEPOpenObject getInitialProposal();
102
103         /**
104          * Get the revised session parameters proposal based on the feedback
105          * the peer has provided to us.
106          * 
107          * @param suggestion Peer-provided suggested session parameters
108          * @return Session parameters proposal.
109          */
110         protected abstract PCEPOpenObject getRevisedProposal(PCEPOpenObject suggestion);
111
112         /**
113          * Check whether a peer-provided session parameters proposal is acceptable.
114          * 
115          * @param proposal peer-proposed session parameters
116          * @return true if the proposal is acceptable, false otherwise
117          */
118         protected abstract boolean isProposalAcceptable(PCEPOpenObject proposal);
119
120         /**
121          * Given a peer-provided session parameters proposal which we found
122          * unacceptable, provide a counter-proposal. The requirement is that
123          * the isProposalAcceptable() method has to return true when presented
124          * with this proposal.
125          * 
126          * @param proposal unacceptable peer proposal
127          * @return our counter-proposal, or null if there is no way to negotiate
128          *         an acceptable proposal
129          */
130         protected abstract PCEPOpenObject getCounterProposal(PCEPOpenObject proposal);
131
132         /**
133          * Create the protocol session.
134          * 
135          * @param timer Timer which the session can use for its various functions.
136          * @param channel Underlying channel.
137          * @param sessionId Assigned session ID.
138          * @param localPrefs Session preferences proposed by us and accepted by the peer.
139          * @param remotePrefs Session preferences proposed by the peer and accepted by us.
140          * @return New protocol session.
141          */
142         protected abstract PCEPSessionImpl createSession(Timer timer, Channel channel,
143                         PCEPOpenObject localPrefs, PCEPOpenObject remotePrefs);
144
145         /**
146          * Sends PCEP Error Message with one PCEPError.
147          * 
148          * @param value
149          */
150         private void sendErrorMessage(final PCEPErrors value) {
151                 channel.writeAndFlush(new PCEPErrorMessage(ImmutableList.of(new PCEPErrorObject(value))));
152         }
153
154         private void scheduleFailTimer() {
155                 final Object lock = this;
156
157                 failTimer = timer.newTimeout(new TimerTask() {
158                         @Override
159                         public void run(final Timeout timeout) throws Exception {
160                                 synchronized (lock) {
161                                         // This closes the race between timer expiring and new timer
162                                         // being armed while it waits for the lock.
163                                         if (failTimer == timeout) {
164                                                 switch (state) {
165                                                 case Finished:
166                                                 case Idle:
167                                                         break;
168                                                 case KeepWait:
169                                                         sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
170                                                         negotiationFailed(new TimeoutException("KeepWait timer expired"));
171                                                         state = State.Finished;
172                                                         break;
173                                                 case OpenWait:
174                                                         sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
175                                                         negotiationFailed(new TimeoutException("OpenWait timer expired"));
176                                                         state = State.Finished;
177                                                         break;
178                                                 }
179                                         }
180                                 }
181                         }
182                 }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
183         }
184
185         @Override
186         final synchronized protected void startNegotiation() {
187                 Preconditions.checkState(state == State.Idle);
188                 localPrefs = getInitialProposal();
189                 channel.writeAndFlush(new PCEPOpenMessage(localPrefs));
190                 state = State.OpenWait;
191                 scheduleFailTimer();
192
193                 logger.debug("Channel {} started sent proposal {}", channel, localPrefs);
194         }
195
196         @Override
197         final synchronized protected void handleMessage(final PCEPMessage msg) throws Exception {
198                 failTimer.cancel();
199
200                 logger.debug("Channel {} handling message in state {}", channel, msg);
201
202                 switch (state) {
203                 case Finished:
204                 case Idle:
205                         throw new IllegalStateException("Unexpected handleMessage in state " + state);
206                 case KeepWait:
207                         if (msg instanceof PCEPKeepAliveMessage) {
208                                 localOK = true;
209                                 if (remoteOK) {
210                                         negotiationSuccessful(createSession(timer, channel, localPrefs, remotePrefs));
211                                         state = State.Finished;
212                                 } else {
213                                         scheduleFailTimer();
214                                         state = State.OpenWait;
215                                         logger.debug("Channel {} moved to OpenWait state with localOK=1", channel);
216                                 }
217
218                                 return;
219                         } else if (msg instanceof PCEPErrorMessage) {
220                                 final PCEPErrorMessage err = (PCEPErrorMessage) msg;
221                                 localPrefs = getRevisedProposal(err.getOpenObject());
222                                 if (localPrefs == null) {
223                                         sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
224                                         negotiationFailed(new RuntimeException("Peer suggested unacceptable retry proposal"));
225                                         state = State.Finished;
226                                         return;
227                                 }
228
229                                 if (!remoteOK) {
230                                         state = State.OpenWait;
231                                 }
232                                 scheduleFailTimer();
233                                 return;
234                         }
235
236                         break;
237                 case OpenWait:
238                         if (msg instanceof PCEPOpenMessage) {
239                                 final PCEPOpenObject open = ((PCEPOpenMessage) msg).getOpenObject();
240                                 if (isProposalAcceptable(open)) {
241                                         channel.writeAndFlush(new PCEPKeepAliveMessage());
242                                         remotePrefs = open;
243                                         remoteOK = true;
244                                         if (localOK) {
245                                                 negotiationSuccessful(createSession(timer, channel, localPrefs, remotePrefs));
246                                                 state = State.Finished;
247                                         } else {
248                                                 scheduleFailTimer();
249                                                 state = State.KeepWait;
250                                                 logger.debug("Channel {} moved to KeepWait state with remoteOK=1", channel);
251                                         }
252                                         return;
253                                 }
254
255                                 if (openRetry) {
256                                         sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
257                                         negotiationFailed(new RuntimeException("OPEN renegotiation failed"));
258                                         state = State.Finished;
259                                         return;
260                                 }
261
262                                 final PCEPOpenObject newPrefs = getCounterProposal(open);
263                                 if (newPrefs == null) {
264                                         sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
265                                         negotiationFailed(new RuntimeException("Peer sent unacceptable session parameters"));
266                                         state = State.Finished;
267                                         return;
268                                 }
269
270                                 channel.writeAndFlush(
271                                                 new PCEPErrorMessage(newPrefs, ImmutableList.of(
272                                                                 new PCEPErrorObject(PCEPErrors.NON_ACC_NEG_SESSION_CHAR)), null));
273
274                                 openRetry = true;
275                                 state = localOK ? State.OpenWait : State.KeepWait;
276                                 scheduleFailTimer();
277                                 return;
278                         }
279
280                         break;
281                 }
282
283                 logger.warn("Channel {} in state {} received unexpected message {}", channel, state, msg);
284                 sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
285                 negotiationFailed(new Exception("Illegal message encountered"));
286                 state = State.Finished;
287         }
288 }