BUG-47 : removed PCEPMessage interface, switched to generated Message.
[bgpcep.git] / pcep / impl / src / main / java / org / opendaylight / protocol / pcep / impl / AbstractPCEPSessionNegotiatorFactory.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.channel.ChannelFuture;
12 import io.netty.channel.ChannelFutureListener;
13 import io.netty.util.concurrent.Promise;
14
15 import java.io.Closeable;
16 import java.io.IOException;
17 import java.net.InetSocketAddress;
18 import java.util.Comparator;
19 import java.util.Map;
20 import java.util.WeakHashMap;
21
22 import javax.annotation.concurrent.GuardedBy;
23
24 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
25 import org.opendaylight.protocol.framework.SessionListenerFactory;
26 import org.opendaylight.protocol.framework.SessionNegotiator;
27 import org.opendaylight.protocol.framework.SessionNegotiatorFactory;
28 import org.opendaylight.protocol.pcep.PCEPSessionListener;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import com.google.common.collect.BiMap;
34 import com.google.common.collect.HashBiMap;
35 import com.google.common.primitives.UnsignedBytes;
36
37 /**
38  * SessionNegotiator which takes care of making sure sessions between PCEP peers are kept unique. This needs to be
39  * further subclassed to provide either a client or server factory.
40  */
41 public abstract class AbstractPCEPSessionNegotiatorFactory implements
42                 SessionNegotiatorFactory<Message, PCEPSessionImpl, PCEPSessionListener> {
43         private static final Comparator<byte[]> comparator = UnsignedBytes.lexicographicalComparator();
44         private static final Logger logger = LoggerFactory.getLogger(AbstractPCEPSessionNegotiatorFactory.class);
45         private final BiMap<byte[], Closeable> sessions = HashBiMap.create();
46         private final Map<byte[], Short> sessionIds = new WeakHashMap<>();
47
48         /**
49          * Create a new negotiator. This method needs to be implemented by subclasses to actually provide a negotiator.
50          * 
51          * @param promise Session promise to be completed by the negotiator
52          * @param channel Associated channel
53          * @param sessionId Session ID assigned to the resulting session
54          * @return a PCEP session negotiator
55          */
56         protected abstract AbstractPCEPSessionNegotiator createNegotiator(Promise<PCEPSessionImpl> promise, PCEPSessionListener listener,
57                         Channel channel, short sessionId);
58
59         @Override
60         public final SessionNegotiator<PCEPSessionImpl> getSessionNegotiator(final SessionListenerFactory<PCEPSessionListener> factory,
61                         final Channel channel, final Promise<PCEPSessionImpl> promise) {
62
63                 final Object lock = this;
64
65                 logger.debug("Instantiating bootstrap negotiator for channel {}", channel);
66                 return new AbstractSessionNegotiator<Message, PCEPSessionImpl>(promise, channel) {
67                         @Override
68                         protected void startNegotiation() throws Exception {
69                                 logger.debug("Bootstrap negotiation for channel {} started", this.channel);
70
71                                 /*
72                                  * We have a chance to see if there's a client session already
73                                  * registered for this client.
74                                  */
75                                 final byte[] clientAddress = ((InetSocketAddress) this.channel.remoteAddress()).getAddress().getAddress();
76
77                                 synchronized (lock) {
78
79                                         if (AbstractPCEPSessionNegotiatorFactory.this.sessions.containsKey(clientAddress)) {
80                                                 // FIXME: cross-reference this to RFC5440
81
82                                                 final byte[] serverAddress = ((InetSocketAddress) this.channel.localAddress()).getAddress().getAddress();
83                                                 if (comparator.compare(serverAddress, clientAddress) > 0) {
84                                                         final Closeable n = AbstractPCEPSessionNegotiatorFactory.this.sessions.remove(clientAddress);
85                                                         try {
86                                                                 n.close();
87                                                         } catch (final IOException e) {
88                                                                 logger.warn("Unexpected failure to close old session", e);
89                                                         }
90                                                 } else {
91                                                         negotiationFailed(new RuntimeException("A conflicting session for address "
92                                                                         + ((InetSocketAddress) this.channel.remoteAddress()).getAddress() + " found."));
93                                                         return;
94                                                 }
95                                         }
96
97                                         final short sessionId = nextSession(clientAddress);
98                                         final AbstractPCEPSessionNegotiator n = createNegotiator(promise, factory.getSessionListener(), this.channel, sessionId);
99
100                                         AbstractPCEPSessionNegotiatorFactory.this.sessions.put(clientAddress, new Closeable() {
101                                                 @Override
102                                                 public void close() {
103                                                         channel.close();
104                                                 }
105                                         });
106
107                                         this.channel.closeFuture().addListener(new ChannelFutureListener() {
108                                                 @Override
109                                                 public void operationComplete(final ChannelFuture future) throws Exception {
110                                                         synchronized (lock) {
111                                                                 AbstractPCEPSessionNegotiatorFactory.this.sessions.inverse().remove(this);
112                                                         }
113                                                 }
114                                         });
115
116                                         logger.debug("Replacing bootstrap negotiator for channel {}", this.channel);
117                                         this.channel.pipeline().replace(this, "negotiator", n);
118                                         n.startNegotiation();
119                                 }
120                         }
121
122                         @Override
123                         protected void handleMessage(final Message msg) throws Exception {
124                                 throw new IllegalStateException("Bootstrap negotiator should have been replaced");
125                         }
126                 };
127         }
128
129         @GuardedBy("this")
130         private short nextSession(final byte[] clientAddress) {
131                 /*
132                  * FIXME: Improve the allocation algorithm to make sure:
133                  * - no duplicate IDs are assigned
134                  * - we retain former session IDs for a reasonable time
135                  */
136                 Short next = this.sessionIds.get(clientAddress);
137                 if (next == null) {
138                         next = 0;
139                 }
140
141                 this.sessionIds.put(clientAddress, (short) ((next + 1) % 255));
142                 return next;
143         }
144 }