2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.pcep.impl;
10 import io.netty.channel.ChannelHandlerContext;
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Date;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.Queue;
18 import java.util.Timer;
19 import java.util.TimerTask;
21 import org.opendaylight.protocol.framework.DeserializerException;
22 import org.opendaylight.protocol.framework.DocumentedException;
23 import org.opendaylight.protocol.framework.ProtocolMessage;
24 import org.opendaylight.protocol.framework.ProtocolMessageFactory;
25 import org.opendaylight.protocol.framework.ProtocolSession;
26 import org.opendaylight.protocol.framework.SessionParent;
27 import org.opendaylight.protocol.pcep.PCEPCloseTermination;
28 import org.opendaylight.protocol.pcep.PCEPConnection;
29 import org.opendaylight.protocol.pcep.PCEPDeserializerException;
30 import org.opendaylight.protocol.pcep.PCEPErrorTermination;
31 import org.opendaylight.protocol.pcep.PCEPErrors;
32 import org.opendaylight.protocol.pcep.PCEPMessage;
33 import org.opendaylight.protocol.pcep.PCEPSession;
34 import org.opendaylight.protocol.pcep.PCEPSessionListener;
35 import org.opendaylight.protocol.pcep.PCEPSessionPreferences;
36 import org.opendaylight.protocol.pcep.PCEPSessionProposalChecker;
37 import org.opendaylight.protocol.pcep.PCEPTlv;
38 import org.opendaylight.protocol.pcep.impl.message.PCEPRawMessage;
39 import org.opendaylight.protocol.pcep.message.PCEPCloseMessage;
40 import org.opendaylight.protocol.pcep.message.PCEPErrorMessage;
41 import org.opendaylight.protocol.pcep.message.PCEPKeepAliveMessage;
42 import org.opendaylight.protocol.pcep.message.PCEPOpenMessage;
43 import org.opendaylight.protocol.pcep.object.PCEPCloseObject;
44 import org.opendaylight.protocol.pcep.object.PCEPCloseObject.Reason;
45 import org.opendaylight.protocol.pcep.object.PCEPErrorObject;
46 import org.opendaylight.protocol.pcep.object.PCEPOpenObject;
47 import org.opendaylight.protocol.pcep.tlv.NodeIdentifierTlv;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * Implementation of PCEPSession. (Not final for testing.)
54 class PCEPSessionImpl implements PCEPSession, ProtocolSession, PCEPSessionRuntimeMXBean {
57 * KeepAlive Timer is to be scheduled periodically, each time it starts, it sends KeepAlive Message.
59 private class KeepAliveTimer extends TimerTask {
60 private final PCEPSessionImpl parent;
62 public KeepAliveTimer(final PCEPSessionImpl parent) {
68 this.parent.handleKeepaliveTimer();
73 * DeadTimer is to be scheduled periodically, when it expires, it closes PCEP session.
75 private class DeadTimer extends TimerTask {
76 private final PCEPSessionImpl parent;
78 public DeadTimer(final PCEPSessionImpl parent) {
84 this.parent.handleDeadtimer();
89 * OpenWaitTimer runs just once, but can be rescheduled or canceled before expiration. When it expires, it sends an
90 * error message (1, 2)
92 private class OpenWaitTimer extends TimerTask {
94 private final PCEPSessionImpl parent;
96 public OpenWaitTimer(final PCEPSessionImpl parent) {
102 this.parent.handleOpenWait();
107 * KeepWaitTimer runs just once, but can be rescheduled or canceled before expiration. When it expires, it sends an
108 * error message (1, 7)
110 private class KeepWaitTimer extends TimerTask {
112 private final PCEPSessionImpl parent;
114 public KeepWaitTimer(final PCEPSessionImpl parent) {
115 this.parent = parent;
120 this.parent.handleKeepWait();
125 * Possible states for Finite State Machine
128 IDLE, OPEN_WAIT, KEEP_WAIT, UP
134 public static final int OPEN_WAIT_TIMER_VALUE = 60;
136 public static final int KEEP_WAIT_TIMER_VALUE = 60;
138 public int KEEP_ALIVE_TIMER_VALUE = 3;
140 public int DEAD_TIMER_VALUE = 4 * this.KEEP_ALIVE_TIMER_VALUE;
143 * Actual state of the FSM.
147 private OpenWaitTimer openWaitTimer;
149 private KeepWaitTimer keepWaitTimer;
152 * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
154 protected long lastMessageSentAt;
157 * System.nanoTime value about when was received the last message
159 private long lastMessageReceivedAt;
161 private boolean localOK = false;
163 private boolean remoteOK = false;
165 private boolean openRetry = false;
167 private final int sessionId;
170 * Protected for testing.
172 protected int maxUnknownMessages = 5;
174 protected final Queue<Long> unknownMessagesTimes = new LinkedList<Long>();
176 private final PCEPSessionListener listener;
178 private PCEPSessionProposalChecker checker = null;
181 * Open Object with session characteristics that were accepted by another PCE (sent from this session).
183 private PCEPOpenObject localOpen = null;
186 * Open Object with session characteristics for this session (sent from another PCE).
188 private PCEPOpenObject remoteOpen = null;
190 private static final Logger logger = LoggerFactory.getLogger(PCEPSessionImpl.class);
193 * Timer object grouping FSM Timers
195 private final Timer stateTimer;
197 private final SessionParent parent;
199 private final PCEPMessageFactory factory;
201 private int sentMsgCount = 0;
203 private int receivedMsgCount = 0;
205 private final String peerAddress;
207 private final ChannelHandlerContext ctx;
209 PCEPSessionImpl(final SessionParent parent, final Timer timer, final PCEPConnection connection, final PCEPMessageFactory factory,
210 final int maxUnknownMessages, final int sessionId, final ChannelHandlerContext ctx) {
211 this.state = State.IDLE;
212 this.listener = connection.getListener();
213 this.checker = connection.getProposalChecker();
214 this.sessionId = sessionId;
215 this.localOpen = connection.getProposal().getOpenObject();
216 this.peerAddress = connection.getPeerAddress().getHostString();
217 this.stateTimer = timer;
218 this.parent = parent;
219 this.factory = factory;
221 if (this.maxUnknownMessages != 0)
222 this.maxUnknownMessages = maxUnknownMessages;
226 public void startSession() {
227 logger.debug("Session started.");
228 this.sendMessage(new PCEPOpenMessage(this.localOpen));
229 this.restartOpenWait();
230 this.changeState(State.OPEN_WAIT);
234 * OpenWait timer can be canceled or rescheduled before its expiration. When it expires, it sends particular
235 * PCEPErrorMessage and closes PCEP session.
237 private synchronized void handleOpenWait() {
238 if (this.state != State.IDLE) {
239 this.terminate(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT); // 1, 1
244 * KeepWait timer can be canceled or rescheduled before its expiration. When it expires, it sends particular
245 * PCEPErrorMessage and closes PCEP session.
247 private synchronized void handleKeepWait() {
248 if (this.state != State.IDLE) {
249 this.terminate(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT); // 1, 7
254 * If DeadTimer expires, the session ends. If a message (whichever) was received during this period, the DeadTimer
255 * will be rescheduled by DEAD_TIMER_VALUE + the time that has passed from the start of the DeadTimer to the time at
256 * which the message was received. If the session was closed by the time this method starts to execute (the session
257 * state will become IDLE), that rescheduling won't occur.
259 private synchronized void handleDeadtimer() {
260 final long ct = System.nanoTime();
262 final long nextDead = (long) (this.lastMessageReceivedAt + (this.DEAD_TIMER_VALUE * 1E9));
264 if (this.state != State.IDLE) {
265 if (ct >= nextDead) {
266 logger.debug("DeadTimer expired. " + new Date());
267 this.terminate(Reason.EXP_DEADTIMER);
271 this.stateTimer.schedule(new DeadTimer(this), (long) ((nextDead - ct) / 1E6));
276 * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
277 * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
278 * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
279 * starts to execute (the session state will become IDLE), that rescheduling won't occur.
281 private synchronized void handleKeepaliveTimer() {
282 final long ct = System.nanoTime();
284 long nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
286 if (this.state != State.IDLE) {
287 if (ct >= nextKeepalive) {
288 this.sendMessage(new PCEPKeepAliveMessage());
289 nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
292 this.stateTimer.schedule(new KeepAliveTimer(this), (long) ((nextKeepalive - ct) / 1E6));
296 private void changeState(final State finalState) {
297 switch (finalState) {
299 logger.debug("Changed to state: " + State.IDLE);
300 this.state = State.IDLE;
303 logger.debug("Changed to state: " + State.OPEN_WAIT);
304 if (this.state == State.UP) {
305 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.OPEN_WAIT);
307 this.state = State.OPEN_WAIT;
310 logger.debug("Changed to state: " + State.KEEP_WAIT);
311 if (this.state == State.UP || this.state == State.IDLE) {
312 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.KEEP_WAIT);
314 this.state = State.KEEP_WAIT;
317 logger.debug("Changed to state: " + State.UP);
318 if (this.state == State.IDLE || this.state == State.UP) {
319 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.UP);
321 this.state = State.UP;
326 private void restartOpenWait() {
327 if (this.state == State.OPEN_WAIT && this.openWaitTimer != null) {
328 this.openWaitTimer.cancel();
330 this.openWaitTimer = new OpenWaitTimer(this);
331 this.stateTimer.schedule(this.openWaitTimer, OPEN_WAIT_TIMER_VALUE * 1000);
334 private void restartKeepWaitTimer() {
335 if (this.state == State.KEEP_WAIT && this.keepWaitTimer != null) {
336 this.keepWaitTimer.cancel();
338 this.keepWaitTimer = new KeepWaitTimer(this);
339 this.stateTimer.schedule(this.keepWaitTimer, KEEP_WAIT_TIMER_VALUE * 1000);
343 * Makes a callback to check if the session characteristics that FSM received, are acceptable.
345 * @param keepAliveTimerValue
346 * @param deadTimerValue
350 private boolean checkSessionCharacteristics(final PCEPOpenObject openObj) {
351 return this.checker.checkSessionCharacteristics(new PCEPSessionPreferences(openObj));
354 private PCEPOpenObject getNewProposal() {
355 return this.checker.getNewProposal(new PCEPSessionPreferences(this.localOpen)).getOpenObject();
359 * Sends message to serialization.
361 * @param msg to be sent
364 public void sendMessage(final PCEPMessage msg) {
366 this.ctx.writeAndFlush(msg);
367 this.lastMessageSentAt = System.nanoTime();
368 if (!(msg instanceof PCEPKeepAliveMessage))
369 logger.debug("Sent message: " + msg);
371 } catch (final Exception e) {
372 logger.warn("Message {} was not sent.", msg, e);
377 * Closes PCEP session without sending a Close message, as the channel is no longer active. Notify parent about
380 * @param reason reason, why it was terminated
383 public void close() {
384 logger.trace("Closing session: {}", this);
385 this.changeState(State.IDLE);
386 this.parent.onSessionClosed(this);
390 * Closes PCEP session, cancels all timers, returns to state Idle, sends the Close Message. KeepAlive and DeadTimer
391 * are cancelled if the state of the session changes to IDLE. This method is used to close the PCEP session from
392 * inside the session or from the listener, therefore the parent of this session should be informed.
395 public synchronized void close(final PCEPCloseObject.Reason reason) {
396 logger.debug("Closing session: {}", this);
397 this.sendMessage(new PCEPCloseMessage(new PCEPCloseObject(reason)));
398 this.changeState(State.IDLE);
399 this.parent.onSessionClosed(this);
402 private void terminate(final PCEPCloseObject.Reason reason) {
403 this.listener.onSessionTerminated(this, new PCEPCloseTermination(reason));
404 this.sendMessage(new PCEPCloseMessage(new PCEPCloseObject(reason)));
408 private void terminate(final PCEPErrors error) {
409 this.listener.onSessionTerminated(this, new PCEPErrorTermination(error));
410 this.sendErrorMessage(error);
415 public void endOfInput() {
416 if (this.state != State.IDLE) {
417 this.listener.onSessionDown(this, null, new IOException("End of input detected. Close the session."));
422 public int maximumMessageSize() {
426 private void sendErrorMessage(final PCEPErrors value) {
427 this.sendErrorMessage(value, null);
431 * Sends PCEP Error Message with one PCEPError and Open Object.
436 private void sendErrorMessage(final PCEPErrors value, final PCEPOpenObject open) {
437 final PCEPErrorObject error = new PCEPErrorObject(value);
438 final List<PCEPErrorObject> errors = new ArrayList<PCEPErrorObject>();
440 this.sendMessage(new PCEPErrorMessage(open, errors, null));
444 public void handleMalformedMessage(final DeserializerException e) {
450 public void handleMalformedMessage(final DocumentedException e) {
456 * The fact, that a message is malformed, comes from parser. In case of unrecognized message a particular error is
457 * sent (CAPABILITY_NOT_SUPPORTED) and the method checks if the MAX_UNKNOWN_MSG per minute wasn't overstepped.
458 * Second, any other error occurred that is specified by rfc. In this case, the an error message is generated and
461 * @param error documented error in RFC5440 or draft
463 public void handleMalformedMessage(final PCEPErrors error) {
464 final long ct = System.nanoTime();
465 this.sendErrorMessage(error);
466 if (error == PCEPErrors.CAPABILITY_NOT_SUPPORTED) {
467 this.unknownMessagesTimes.add(ct);
468 while (ct - this.unknownMessagesTimes.peek() > 60 * 1E9) {
469 this.unknownMessagesTimes.poll();
471 if (this.unknownMessagesTimes.size() > this.maxUnknownMessages) {
472 this.terminate(Reason.TOO_MANY_UNKNOWN_MSG);
478 * In case of syntactic error or some parsing error, the session needs to be closed with the Reason: malformed
479 * message. The user needs to be notified about this error.
481 * @param e exception that was thrown from parser
483 public void handleMalformedMessage(final Exception e) {
484 logger.warn("PCEP byte stream corruption detected", e);
485 this.terminate(Reason.MALFORMED_MSG);
489 * Open message should be handled only if the FSM is in OPEN_WAIT state.
493 private void handleOpenMessage(final PCEPOpenMessage msg) {
494 this.remoteOpen = msg.getOpenObject();
495 logger.debug("Received message: " + msg.toString());
496 if (this.state != State.OPEN_WAIT) {
497 this.sendErrorMessage(PCEPErrors.ATTEMPT_2ND_SESSION);
500 final Boolean result = this.checkSessionCharacteristics(this.remoteOpen);
501 if (result == null) {
502 this.terminate(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR); // 1, 3
505 this.DEAD_TIMER_VALUE = this.remoteOpen.getDeadTimerValue();
506 this.KEEP_ALIVE_TIMER_VALUE = this.remoteOpen.getKeepAliveTimerValue();
507 logger.debug("Session chars are acceptable. Overwriting: deadtimer: " + this.DEAD_TIMER_VALUE + "keepalive: "
508 + this.KEEP_ALIVE_TIMER_VALUE);
509 this.remoteOK = true;
510 this.openWaitTimer.cancel();
511 this.sendMessage(new PCEPKeepAliveMessage());
512 // if the timer is not disabled
513 if (this.KEEP_ALIVE_TIMER_VALUE != 0) {
514 this.stateTimer.schedule(new KeepAliveTimer(this), this.KEEP_ALIVE_TIMER_VALUE * 1000);
517 // if the timer is not disabled
518 if (this.DEAD_TIMER_VALUE != 0) {
519 this.stateTimer.schedule(new DeadTimer(this), this.DEAD_TIMER_VALUE * 1000);
521 this.changeState(State.UP);
522 this.listener.onSessionUp(this, this.localOpen, this.remoteOpen);
524 this.restartKeepWaitTimer();
525 this.changeState(State.KEEP_WAIT);
528 } else if (!result) {
529 this.localOpen = this.getNewProposal();
530 if (this.openRetry) {
531 this.terminate(PCEPErrors.SECOND_OPEN_MSG); // 1, 5
533 this.openRetry = true;
534 this.sendErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, this.localOpen); // 1, 4
536 this.restartOpenWait();
537 this.changeState(State.OPEN_WAIT);
539 this.restartKeepWaitTimer();
540 this.changeState(State.KEEP_WAIT);
547 * Error message should be handled in FSM if its state is KEEP_WAIT, otherwise it is just passed to session listener
552 private void handleErrorMessage(final PCEPErrorMessage msg) {
553 this.remoteOpen = msg.getOpenObject();
554 final Boolean result = this.checkSessionCharacteristics(this.remoteOpen);
555 if (result == null || !result) {
556 this.terminate(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR); // 1, 6
559 this.KEEP_ALIVE_TIMER_VALUE = this.remoteOpen.getKeepAliveTimerValue();
560 this.DEAD_TIMER_VALUE = this.remoteOpen.getDeadTimerValue();
561 logger.debug("New values for keepalive: " + this.remoteOpen.getKeepAliveTimerValue() + " deadtimer "
562 + this.remoteOpen.getDeadTimerValue());
563 this.sendMessage(new PCEPOpenMessage(this.remoteOpen));
565 this.restartKeepWaitTimer();
566 this.changeState(State.KEEP_WAIT);
568 this.keepWaitTimer.cancel();
569 this.restartOpenWait();
570 this.changeState(State.OPEN_WAIT);
576 * KeepAlive message should be explicitly parsed in FSM when its state is KEEP_WAIT. Otherwise is handled by the
577 * KeepAliveTimer or it's invalid.
579 private void handleKeepAliveMessage() {
580 if (this.state == State.KEEP_WAIT) {
582 this.keepWaitTimer.cancel();
584 if (this.DEAD_TIMER_VALUE != 0) {
585 this.stateTimer.schedule(new DeadTimer(this), this.DEAD_TIMER_VALUE * 1000);
587 this.changeState(State.UP);
588 this.listener.onSessionUp(this, this.localOpen, this.remoteOpen);
590 this.restartOpenWait();
591 this.changeState(State.OPEN_WAIT);
597 * Handles incoming message. If the session is up, it notifies the user. The user is notified about every message
600 * @param msg incoming message
603 public void handleMessage(final ProtocolMessage msg) {
604 // Update last reception time
605 final PCEPMessage pcepMsg = (PCEPMessage) msg;
607 this.lastMessageReceivedAt = System.nanoTime();
608 this.receivedMsgCount++;
610 if (pcepMsg instanceof PCEPRawMessage) {
611 List<PCEPMessage> msgs;
613 msgs = PCEPMessageValidator.getValidator(((PCEPRawMessage) pcepMsg).getMsgType()).validate(
614 ((PCEPRawMessage) pcepMsg).getAllObjects());
615 for (final PCEPMessage tmpMsg : msgs) {
616 this.handleMessage(tmpMsg);
618 } catch (final PCEPDeserializerException e) {
619 logger.error("Malformed message, terminating. ", e);
620 this.terminate(Reason.MALFORMED_MSG);
625 // Keepalives are handled internally
626 if (pcepMsg instanceof PCEPKeepAliveMessage) {
627 this.handleKeepAliveMessage();
631 // Open messages are handled internally
632 if (pcepMsg instanceof PCEPOpenMessage) {
633 this.handleOpenMessage((PCEPOpenMessage) pcepMsg);
638 * During initial handshake we handle all the messages.
640 if (this.state != State.UP) {
642 * In KEEP_WAIT, an Error message is a valid thing to see, because
643 * it is used in negotiation.
645 if (pcepMsg instanceof PCEPErrorMessage && this.state == State.KEEP_WAIT
646 && ((PCEPErrorMessage) pcepMsg).getOpenObject() != null) {
647 this.handleErrorMessage((PCEPErrorMessage) pcepMsg);
652 * OPEN and KEEPALIVE messages are handled at the top. ERROR
653 * messages are handled in the specific case of KEEP_WAIT above, so
654 * anything else is invalid here.
656 this.terminate(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
661 * Session is up, we are reporting all messages to user. One notable
662 * exception is CLOSE message, which needs to be converted into a
663 * session DOWN event.
665 if (pcepMsg instanceof PCEPCloseMessage) {
669 this.listener.onMessage(this, pcepMsg);
673 public ProtocolMessageFactory getMessageFactory() {
678 * @return the sentMsgCount
682 public Integer getSentMsgCount() {
683 return this.sentMsgCount;
687 * @return the receivedMsgCount
691 public Integer getReceivedMsgCount() {
692 return this.receivedMsgCount;
696 public Integer getDeadTimerValue() {
697 return this.DEAD_TIMER_VALUE;
701 public Integer getKeepAliveTimerValue() {
702 return this.KEEP_ALIVE_TIMER_VALUE;
706 public String getPeerAddress() {
707 return this.peerAddress;
711 public void tearDown() throws IOException {
716 public String getNodeIdentifier() {
717 if (!this.remoteOpen.getTlvs().isEmpty()) {
718 final PCEPTlv tlv = this.remoteOpen.getTlvs().iterator().next();
719 if (tlv != null && tlv instanceof NodeIdentifierTlv) {
720 return tlv.toString();
727 public String toString() {
728 final StringBuilder builder = new StringBuilder();
729 builder.append("PCEPSessionImpl [state=");
730 builder.append(this.state);
731 builder.append(", localOK=");
732 builder.append(this.localOK);
733 builder.append(", remoteOK=");
734 builder.append(this.remoteOK);
735 builder.append(", openRetry=");
736 builder.append(this.openRetry);
737 builder.append(", sessionId=");
738 builder.append(this.sessionId);
739 builder.append(", checker=");
740 builder.append(this.checker);
741 builder.append(", localOpen=");
742 builder.append(this.localOpen);
743 builder.append(", remoteOpen=");
744 builder.append(this.remoteOpen);
746 return builder.toString();