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 org.opendaylight.protocol.framework.DeserializerException;
11 import org.opendaylight.protocol.framework.DocumentedException;
12 import org.opendaylight.protocol.framework.ProtocolMessage;
13 import org.opendaylight.protocol.framework.ProtocolMessageFactory;
14 import org.opendaylight.protocol.framework.ProtocolOutputStream;
15 import org.opendaylight.protocol.framework.ProtocolSession;
16 import org.opendaylight.protocol.framework.SessionParent;
17 import org.opendaylight.protocol.pcep.PCEPCloseTermination;
18 import org.opendaylight.protocol.pcep.PCEPConnection;
19 import org.opendaylight.protocol.pcep.PCEPDeserializerException;
20 import org.opendaylight.protocol.pcep.PCEPErrorTermination;
21 import org.opendaylight.protocol.pcep.PCEPErrors;
22 import org.opendaylight.protocol.pcep.PCEPMessage;
23 import org.opendaylight.protocol.pcep.PCEPSession;
24 import org.opendaylight.protocol.pcep.PCEPSessionListener;
25 import org.opendaylight.protocol.pcep.PCEPSessionPreferences;
26 import org.opendaylight.protocol.pcep.PCEPSessionProposalChecker;
27 import org.opendaylight.protocol.pcep.PCEPTlv;
28 import org.opendaylight.protocol.pcep.impl.message.PCEPRawMessage;
29 import org.opendaylight.protocol.pcep.message.PCEPCloseMessage;
30 import org.opendaylight.protocol.pcep.message.PCEPErrorMessage;
31 import org.opendaylight.protocol.pcep.message.PCEPKeepAliveMessage;
32 import org.opendaylight.protocol.pcep.message.PCEPOpenMessage;
33 import org.opendaylight.protocol.pcep.object.PCEPCloseObject;
34 import org.opendaylight.protocol.pcep.object.PCEPCloseObject.Reason;
35 import org.opendaylight.protocol.pcep.object.PCEPErrorObject;
36 import org.opendaylight.protocol.pcep.object.PCEPOpenObject;
37 import org.opendaylight.protocol.pcep.tlv.NodeIdentifierTlv;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import java.io.IOException;
42 import java.util.ArrayList;
43 import java.util.Date;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.Queue;
47 import java.util.Timer;
48 import java.util.TimerTask;
51 * Implementation of PCEPSession. (Not final for testing.)
53 class PCEPSessionImpl implements PCEPSession, ProtocolSession, PCEPSessionRuntimeMXBean {
56 * KeepAlive Timer is to be scheduled periodically, each time it starts, it sends KeepAlive Message.
58 private class KeepAliveTimer extends TimerTask {
59 private final PCEPSessionImpl parent;
61 public KeepAliveTimer(final PCEPSessionImpl parent) {
67 this.parent.handleKeepaliveTimer();
72 * DeadTimer is to be scheduled periodically, when it expires, it closes PCEP session.
74 private class DeadTimer extends TimerTask {
75 private final PCEPSessionImpl parent;
77 public DeadTimer(final PCEPSessionImpl parent) {
83 this.parent.handleDeadtimer();
88 * OpenWaitTimer runs just once, but can be rescheduled or canceled before expiration. When it expires, it sends an
89 * error message (1, 2)
91 private class OpenWaitTimer extends TimerTask {
93 private final PCEPSessionImpl parent;
95 public OpenWaitTimer(final PCEPSessionImpl parent) {
101 this.parent.handleOpenWait();
106 * KeepWaitTimer runs just once, but can be rescheduled or canceled before expiration. When it expires, it sends an
107 * error message (1, 7)
109 private class KeepWaitTimer extends TimerTask {
111 private final PCEPSessionImpl parent;
113 public KeepWaitTimer(final PCEPSessionImpl parent) {
114 this.parent = parent;
119 this.parent.handleKeepWait();
124 * Possible states for Finite State Machine
127 IDLE, OPEN_WAIT, KEEP_WAIT, UP
133 public static final int OPEN_WAIT_TIMER_VALUE = 60;
135 public static final int KEEP_WAIT_TIMER_VALUE = 60;
137 public int KEEP_ALIVE_TIMER_VALUE = 3;
139 public int DEAD_TIMER_VALUE = 4 * this.KEEP_ALIVE_TIMER_VALUE;
142 * Actual state of the FSM.
146 private OpenWaitTimer openWaitTimer;
148 private KeepWaitTimer keepWaitTimer;
151 * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
153 protected long lastMessageSentAt;
156 * System.nanoTime value about when was received the last message
158 private long lastMessageReceivedAt;
160 private boolean localOK = false;
162 private boolean remoteOK = false;
164 private boolean openRetry = false;
166 private final int sessionId;
169 * Protected for testing.
171 protected int maxUnknownMessages = 5;
173 protected final Queue<Long> unknownMessagesTimes = new LinkedList<Long>();
175 private final PCEPSessionListener listener;
177 private PCEPSessionProposalChecker checker = null;
180 * Open Object with session characteristics that were accepted by another PCE (sent from this session).
182 private PCEPOpenObject localOpen = null;
185 * Open Object with session characteristics for this session (sent from another PCE).
187 private PCEPOpenObject remoteOpen = null;
189 private final ProtocolOutputStream outputStream;
191 private static final Logger logger = LoggerFactory.getLogger(PCEPSessionImpl.class);
194 * Timer object grouping FSM Timers
196 private final Timer stateTimer;
198 private final SessionParent parent;
200 private final PCEPMessageFactory factory;
202 private int sentMsgCount = 0;
204 private int receivedMsgCount = 0;
206 private final String peerAddress;
208 PCEPSessionImpl(final SessionParent parent, final Timer timer, final PCEPConnection connection, final PCEPMessageFactory factory,
209 final int maxUnknownMessages, final int sessionId) {
210 this.state = State.IDLE;
211 this.listener = connection.getListener();
212 this.checker = connection.getProposalChecker();
213 this.sessionId = sessionId;
214 this.localOpen = connection.getProposal().getOpenObject();
215 this.outputStream = new ProtocolOutputStream();
216 this.peerAddress = connection.getPeerAddress().getHostString();
217 this.stateTimer = timer;
218 this.parent = parent;
219 this.factory = factory;
220 if (this.maxUnknownMessages != 0)
221 this.maxUnknownMessages = maxUnknownMessages;
225 public void startSession() {
226 logger.debug("Session started.");
227 this.sendMessage(new PCEPOpenMessage(this.localOpen));
228 this.restartOpenWait();
229 this.changeState(State.OPEN_WAIT);
233 * OpenWait timer can be canceled or rescheduled before its expiration. When it expires, it sends particular
234 * PCEPErrorMessage and closes PCEP session.
236 private synchronized void handleOpenWait() {
237 if (this.state != State.IDLE) {
238 this.terminate(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT); // 1, 1
243 * KeepWait timer can be canceled or rescheduled before its expiration. When it expires, it sends particular
244 * PCEPErrorMessage and closes PCEP session.
246 private synchronized void handleKeepWait() {
247 if (this.state != State.IDLE) {
248 this.terminate(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT); // 1, 7
253 * If DeadTimer expires, the session ends. If a message (whichever) was received during this period, the DeadTimer
254 * will be rescheduled by DEAD_TIMER_VALUE + the time that has passed from the start of the DeadTimer to the time at
255 * which the message was received. If the session was closed by the time this method starts to execute (the session
256 * state will become IDLE), that rescheduling won't occur.
258 private synchronized void handleDeadtimer() {
259 final long ct = System.nanoTime();
261 final long nextDead = (long) (this.lastMessageReceivedAt + (this.DEAD_TIMER_VALUE * 1E9));
263 if (this.state != State.IDLE) {
264 if (ct >= nextDead) {
265 logger.debug("DeadTimer expired. " + new Date());
266 this.terminate(Reason.EXP_DEADTIMER);
270 this.stateTimer.schedule(new DeadTimer(this), (long) ((nextDead - ct) / 1E6));
275 * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
276 * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
277 * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
278 * starts to execute (the session state will become IDLE), that rescheduling won't occur.
280 private synchronized void handleKeepaliveTimer() {
281 final long ct = System.nanoTime();
283 long nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
285 if (this.state != State.IDLE) {
286 if (ct >= nextKeepalive) {
287 this.sendMessage(new PCEPKeepAliveMessage());
288 nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
291 this.stateTimer.schedule(new KeepAliveTimer(this), (long) ((nextKeepalive - ct) / 1E6));
295 private void changeState(final State finalState) {
296 switch (finalState) {
298 logger.debug("Changed to state: " + State.IDLE);
299 this.state = State.IDLE;
302 logger.debug("Changed to state: " + State.OPEN_WAIT);
303 if (this.state == State.UP) {
304 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.OPEN_WAIT);
306 this.state = State.OPEN_WAIT;
309 logger.debug("Changed to state: " + State.KEEP_WAIT);
310 if (this.state == State.UP || this.state == State.IDLE) {
311 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.KEEP_WAIT);
313 this.state = State.KEEP_WAIT;
316 logger.debug("Changed to state: " + State.UP);
317 if (this.state == State.IDLE || this.state == State.UP) {
318 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.UP);
320 this.state = State.UP;
325 private void restartOpenWait() {
326 if (this.state == State.OPEN_WAIT && this.openWaitTimer != null) {
327 this.openWaitTimer.cancel();
329 this.openWaitTimer = new OpenWaitTimer(this);
330 this.stateTimer.schedule(this.openWaitTimer, OPEN_WAIT_TIMER_VALUE * 1000);
333 private void restartKeepWaitTimer() {
334 if (this.state == State.KEEP_WAIT && this.keepWaitTimer != null) {
335 this.keepWaitTimer.cancel();
337 this.keepWaitTimer = new KeepWaitTimer(this);
338 this.stateTimer.schedule(this.keepWaitTimer, KEEP_WAIT_TIMER_VALUE * 1000);
342 * Makes a callback to check if the session characteristics that FSM received, are acceptable.
344 * @param keepAliveTimerValue
345 * @param deadTimerValue
349 private boolean checkSessionCharacteristics(final PCEPOpenObject openObj) {
350 return this.checker.checkSessionCharacteristics(new PCEPSessionPreferences(openObj));
353 private PCEPOpenObject getNewProposal() {
354 return this.checker.getNewProposal(new PCEPSessionPreferences(this.localOpen)).getOpenObject();
358 * Sends message to serialization.
360 * @param msg to be sent
363 public void sendMessage(final PCEPMessage msg) {
364 this.outputStream.putMessage(msg, this.factory);
365 this.lastMessageSentAt = System.nanoTime();
366 if (!(msg instanceof PCEPKeepAliveMessage))
367 logger.debug("Sent message: " + msg);
368 this.parent.checkOutputBuffer(this);
373 public ProtocolOutputStream getStream() {
374 return this.outputStream;
377 private void commonClose() {
378 this.changeState(State.IDLE);
379 this.parent.onSessionClosed(this);
383 * Closes PCEP session from the parent with given reason. A message needs to be sent, but parent doesn't have to be
384 * modified, because he initiated the closing. (To prevent concurrent modification exception).
388 void closeWithoutMessage() {
389 logger.debug("Closing session: {}", this);
394 * Closes PCEP session, cancels all timers, returns to state Idle WITHOUT sending the Close Message. KeepAlive and
395 * DeadTimer are cancelled if the state of the session changes to IDLE. This method is used to close the PCEP
396 * session from inside the session or from the listener, therefore the parent of this session should be informed.
397 * The only closing reason is UNKNOWN.
400 public synchronized void close() {
401 logger.debug("Closing session: {}", this);
402 this.sendMessage(new PCEPCloseMessage(new PCEPCloseObject(Reason.UNKNOWN)));
406 private void terminate(final PCEPCloseObject.Reason reason) {
407 this.sendMessage(new PCEPCloseMessage(new PCEPCloseObject(reason)));
408 this.closeWithoutMessage();
409 this.listener.onSessionTerminated(this, new PCEPCloseTermination(reason));
412 private void terminate(final PCEPErrors error) {
413 this.sendErrorMessage(error);
414 this.closeWithoutMessage();
415 this.listener.onSessionTerminated(this, new PCEPErrorTermination(error));
419 public void endOfInput() {
420 if (this.state != State.IDLE) {
421 this.listener.onSessionDown(this, null, new IOException("End of input detected. Close the session."));
426 public int maximumMessageSize() {
430 private void sendErrorMessage(final PCEPErrors value) {
431 this.sendErrorMessage(value, null);
435 * Sends PCEP Error Message with one PCEPError and Open Object.
440 private void sendErrorMessage(final PCEPErrors value, final PCEPOpenObject open) {
441 final PCEPErrorObject error = new PCEPErrorObject(value);
442 final List<PCEPErrorObject> errors = new ArrayList<PCEPErrorObject>();
444 this.sendMessage(new PCEPErrorMessage(open, errors, null));
448 public void handleMalformedMessage(final DeserializerException e) {
454 public void handleMalformedMessage(final DocumentedException e) {
460 * The fact, that a message is malformed, comes from parser. In case of unrecognized message a particular error is
461 * sent (CAPABILITY_NOT_SUPPORTED) and the method checks if the MAX_UNKNOWN_MSG per minute wasn't overstepped.
462 * Second, any other error occurred that is specified by rfc. In this case, the an error message is generated and
465 * @param error documented error in RFC5440 or draft
467 public void handleMalformedMessage(final PCEPErrors error) {
468 final long ct = System.nanoTime();
469 this.sendErrorMessage(error);
470 if (error == PCEPErrors.CAPABILITY_NOT_SUPPORTED) {
471 this.unknownMessagesTimes.add(ct);
472 while (ct - this.unknownMessagesTimes.peek() > 60 * 1E9) {
473 this.unknownMessagesTimes.poll();
475 if (this.unknownMessagesTimes.size() > this.maxUnknownMessages) {
476 this.terminate(Reason.TOO_MANY_UNKNOWN_MSG);
482 * In case of syntactic error or some parsing error, the session needs to be closed with the Reason: malformed
483 * message. The user needs to be notified about this error.
485 * @param e exception that was thrown from parser
487 public void handleMalformedMessage(final Exception e) {
488 logger.warn("PCEP byte stream corruption detected", e);
489 this.terminate(Reason.MALFORMED_MSG);
493 * Open message should be handled only if the FSM is in OPEN_WAIT state.
497 private void handleOpenMessage(final PCEPOpenMessage msg) {
498 this.remoteOpen = msg.getOpenObject();
499 logger.debug("Received message: " + msg.toString());
500 if (this.state != State.OPEN_WAIT) {
501 this.sendErrorMessage(PCEPErrors.ATTEMPT_2ND_SESSION);
504 final Boolean result = this.checkSessionCharacteristics(this.remoteOpen);
505 if (result == null) {
506 this.terminate(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR); // 1, 3
509 this.DEAD_TIMER_VALUE = this.remoteOpen.getDeadTimerValue();
510 this.KEEP_ALIVE_TIMER_VALUE = this.remoteOpen.getKeepAliveTimerValue();
511 logger.debug("Session chars are acceptable. Overwriting: deadtimer: " + this.DEAD_TIMER_VALUE + "keepalive: "
512 + this.KEEP_ALIVE_TIMER_VALUE);
513 this.remoteOK = true;
514 this.openWaitTimer.cancel();
515 this.sendMessage(new PCEPKeepAliveMessage());
516 // if the timer is not disabled
517 if (this.KEEP_ALIVE_TIMER_VALUE != 0) {
518 this.stateTimer.schedule(new KeepAliveTimer(this), this.KEEP_ALIVE_TIMER_VALUE * 1000);
521 // if the timer is not disabled
522 if (this.DEAD_TIMER_VALUE != 0) {
523 this.stateTimer.schedule(new DeadTimer(this), this.DEAD_TIMER_VALUE * 1000);
525 this.changeState(State.UP);
526 this.listener.onSessionUp(this, this.localOpen, this.remoteOpen);
528 this.restartKeepWaitTimer();
529 this.changeState(State.KEEP_WAIT);
532 } else if (!result) {
533 this.localOpen = this.getNewProposal();
534 if (this.openRetry) {
535 this.terminate(PCEPErrors.SECOND_OPEN_MSG); // 1, 5
537 this.openRetry = true;
538 this.sendErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, this.localOpen); // 1, 4
540 this.restartOpenWait();
541 this.changeState(State.OPEN_WAIT);
543 this.restartKeepWaitTimer();
544 this.changeState(State.KEEP_WAIT);
551 * Error message should be handled in FSM if its state is KEEP_WAIT, otherwise it is just passed to session listener
556 private void handleErrorMessage(final PCEPErrorMessage msg) {
557 this.remoteOpen = msg.getOpenObject();
558 final Boolean result = this.checkSessionCharacteristics(this.remoteOpen);
559 if (result == null || !result) {
560 this.terminate(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR); // 1, 6
563 this.KEEP_ALIVE_TIMER_VALUE = this.remoteOpen.getKeepAliveTimerValue();
564 this.DEAD_TIMER_VALUE = this.remoteOpen.getDeadTimerValue();
565 logger.debug("New values for keepalive: " + this.remoteOpen.getKeepAliveTimerValue() + " deadtimer "
566 + this.remoteOpen.getDeadTimerValue());
567 this.sendMessage(new PCEPOpenMessage(this.remoteOpen));
569 this.restartKeepWaitTimer();
570 this.changeState(State.KEEP_WAIT);
572 this.keepWaitTimer.cancel();
573 this.restartOpenWait();
574 this.changeState(State.OPEN_WAIT);
580 * KeepAlive message should be explicitly parsed in FSM when its state is KEEP_WAIT. Otherwise is handled by the
581 * KeepAliveTimer or it's invalid.
583 private void handleKeepAliveMessage() {
584 if (this.state == State.KEEP_WAIT) {
586 this.keepWaitTimer.cancel();
588 if (this.DEAD_TIMER_VALUE != 0) {
589 this.stateTimer.schedule(new DeadTimer(this), this.DEAD_TIMER_VALUE * 1000);
591 this.changeState(State.UP);
592 this.listener.onSessionUp(this, this.localOpen, this.remoteOpen);
594 this.restartOpenWait();
595 this.changeState(State.OPEN_WAIT);
601 * Handles incoming message. If the session is up, it notifies the user. The user is notified about every message
604 * @param msg incoming message
607 public void handleMessage(final ProtocolMessage msg) {
608 // Update last reception time
609 final PCEPMessage pcepMsg = (PCEPMessage) msg;
611 this.lastMessageReceivedAt = System.nanoTime();
612 this.receivedMsgCount++;
614 if (pcepMsg instanceof PCEPRawMessage) {
615 List<PCEPMessage> msgs;
617 msgs = PCEPMessageValidator.getValidator(((PCEPRawMessage) pcepMsg).getMsgType()).validate(
618 ((PCEPRawMessage) pcepMsg).getAllObjects());
619 for (final PCEPMessage tmpMsg : msgs) {
620 this.handleMessage(tmpMsg);
622 } catch (final PCEPDeserializerException e) {
623 logger.error("Malformed message, terminating. ", e);
624 this.terminate(Reason.MALFORMED_MSG);
629 // Keepalives are handled internally
630 if (pcepMsg instanceof PCEPKeepAliveMessage) {
631 this.handleKeepAliveMessage();
635 // Open messages are handled internally
636 if (pcepMsg instanceof PCEPOpenMessage) {
637 this.handleOpenMessage((PCEPOpenMessage) pcepMsg);
642 * During initial handshake we handle all the messages.
644 if (this.state != State.UP) {
646 * In KEEP_WAIT, an Error message is a valid thing to see, because
647 * it is used in negotiation.
649 if (pcepMsg instanceof PCEPErrorMessage && this.state == State.KEEP_WAIT
650 && ((PCEPErrorMessage) pcepMsg).getOpenObject() != null) {
651 this.handleErrorMessage((PCEPErrorMessage) pcepMsg);
656 * OPEN and KEEPALIVE messages are handled at the top. ERROR
657 * messages are handled in the specific case of KEEP_WAIT above, so
658 * anything else is invalid here.
660 this.terminate(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
665 * Session is up, we are reporting all messages to user. One notable
666 * exception is CLOSE message, which needs to be converted into a
667 * session DOWN event.
669 if (pcepMsg instanceof PCEPCloseMessage) {
670 this.listener.onSessionTerminated(this, new PCEPCloseTermination(((PCEPCloseMessage) pcepMsg).getCloseObject().getReason()));
671 this.closeWithoutMessage();
674 this.listener.onMessage(this, pcepMsg);
678 public ProtocolMessageFactory getMessageFactory() {
683 public void onConnectionFailed(final IOException e) {
684 logger.info("Connection failed before finishing: {}", e.getMessage(), e);
685 this.listener.onSessionDown(this, new PCEPCloseObject(Reason.UNKNOWN), e);
689 * @return the sentMsgCount
693 public Integer getSentMsgCount() {
694 return this.sentMsgCount;
698 * @return the receivedMsgCount
702 public Integer getReceivedMsgCount() {
703 return this.receivedMsgCount;
708 public Integer getDeadTimerValue() {
709 return this.DEAD_TIMER_VALUE;
714 public Integer getKeepAliveTimerValue() {
715 return this.KEEP_ALIVE_TIMER_VALUE;
720 public String getPeerAddress() {
721 return this.peerAddress;
725 public void tearDown() throws IOException {
731 public String getNodeIdentifier() {
732 if (!this.remoteOpen.getTlvs().isEmpty()) {
733 final PCEPTlv tlv = this.remoteOpen.getTlvs().iterator().next();
734 if (tlv != null && tlv instanceof NodeIdentifierTlv) {
735 return tlv.toString();
742 public String toString() {
743 final StringBuilder builder = new StringBuilder();
744 builder.append("PCEPSessionImpl [state=");
745 builder.append(this.state);
746 builder.append(", localOK=");
747 builder.append(this.localOK);
748 builder.append(", remoteOK=");
749 builder.append(this.remoteOK);
750 builder.append(", openRetry=");
751 builder.append(this.openRetry);
752 builder.append(", sessionId=");
753 builder.append(this.sessionId);
754 builder.append(", checker=");
755 builder.append(this.checker);
756 builder.append(", localOpen=");
757 builder.append(this.localOpen);
758 builder.append(", remoteOpen=");
759 builder.append(this.remoteOpen);
760 builder.append(", outputStream=");
761 builder.append(this.outputStream);
763 return builder.toString();