Allow additional header only for hello messages.
Introduce customized (en|decoders) for hello message to parse/serialize additional header.
These handlers are replaced after successful negotiation.
Change-Id: I54c441bc166e7141f503888f0eb1934882971045
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
package org.opendaylight.controller.netconf.persist.impl;
-import io.netty.channel.EventLoopGroup;
-
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import org.opendaylight.controller.netconf.client.NetconfClient;
import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
import org.opendaylight.controller.netconf.util.NetconfUtil;
-import org.opendaylight.controller.netconf.util.messages.NetconfMessageAdditionalHeader;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
-import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
+import io.netty.channel.EventLoopGroup;
@Immutable
public class ConfigPusher {
- private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class);
+ private static final Logger logger = LoggerFactory.getLogger(ConfigPusher.class);
private static final int NETCONF_SEND_ATTEMPT_MS_DELAY = 1000;
private static final int NETCONF_SEND_ATTEMPTS = 20;
final long deadlineNanos = pollingStartNanos + TimeUnit.MILLISECONDS.toNanos(maxWaitForCapabilitiesMillis);
int attempt = 0;
- String additionalHeader = NetconfMessageAdditionalHeader.toString("unknown", address.getAddress().getHostAddress(),
- Integer.toString(address.getPort()), "tcp", Optional.of("persister"));
+ NetconfHelloMessageAdditionalHeader additionalHeader = new NetconfHelloMessageAdditionalHeader("unknown", address.getAddress().getHostAddress(),
+ Integer.toString(address.getPort()), "tcp", "persister");
Set<String> latestCapabilities = null;
while (System.nanoTime() < deadlineNanos) {
import org.w3c.dom.Document;
-import com.google.common.base.Optional;
-
/**
* NetconfMessage represents a wrapper around org.w3c.dom.Document. Needed for
* implementing ProtocolMessage interface.
*/
-public final class NetconfMessage {
- private final String additionalHeader;
+public class NetconfMessage {
private final Document doc;
public NetconfMessage(final Document doc) {
- this(doc, null);
- }
-
- public NetconfMessage(Document doc, String additionalHeader) {
this.doc = doc;
- this.additionalHeader = additionalHeader;
}
public Document getDocument() {
return this.doc;
}
-
- public Optional<String> getAdditionalHeader() {
- return additionalHeader== null ? Optional.<String>absent() : Optional.of(additionalHeader);
- }
}
import java.net.InetSocketAddress;
import org.opendaylight.controller.netconf.util.AbstractChannelInitializer;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.protocol.framework.AbstractDispatcher;
import org.opendaylight.protocol.framework.ReconnectStrategy;
import org.opendaylight.protocol.framework.ReconnectStrategyFactory;
private static final Logger logger = LoggerFactory.getLogger(NetconfClientDispatcher.class);
- private final NetconfClientSessionNegotiatorFactory negotatorFactory;
+ private final NetconfClientSessionNegotiatorFactory negotiatorFactory;
private final HashedWheelTimer timer;
- public NetconfClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup, long clientConnectionTimeoutMillis) {
+ public NetconfClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup,
+ long clientConnectionTimeoutMillis) {
super(bossGroup, workerGroup);
timer = new HashedWheelTimer();
- this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.<String>absent(), clientConnectionTimeoutMillis);
+ this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer,
+ Optional.<NetconfHelloMessageAdditionalHeader> absent(), clientConnectionTimeoutMillis);
}
- public NetconfClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup, String additionalHeader, long connectionTimeoutMillis) {
+ public NetconfClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup,
+ NetconfHelloMessageAdditionalHeader additionalHeader, long connectionTimeoutMillis) {
super(bossGroup, workerGroup);
timer = new HashedWheelTimer();
- this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader), connectionTimeoutMillis);
+ this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader),
+ connectionTimeoutMillis);
}
public Future<NetconfClientSession> createClient(InetSocketAddress address,
}
private void initialize(SocketChannel ch, Promise<NetconfClientSession> promise) {
- new ClientChannelInitializer( negotatorFactory, sessionListener).initialize(ch, promise);
+ new ClientChannelInitializer(negotiatorFactory, sessionListener).initialize(ch, promise);
}
});
}
public Future<Void> createReconnectingClient(final InetSocketAddress address,
final NetconfClientSessionListener listener,
final ReconnectStrategyFactory connectStrategyFactory, final ReconnectStrategy reestablishStrategy) {
- final ClientChannelInitializer init = new ClientChannelInitializer(negotatorFactory, listener);
+ final ClientChannelInitializer init = new ClientChannelInitializer(negotiatorFactory, listener);
return super.createReconnectingClient(address, connectStrategyFactory, reestablishStrategy,
new PipelineInitializer<NetconfClientSession>() {
}
@Override
- protected void initializeAfterDecoder(SocketChannel ch, Promise<NetconfClientSession> promise) {
- ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(
- new SessionListenerFactory<NetconfClientSessionListener>() {
- @Override
- public NetconfClientSessionListener getSessionListener() {
- return sessionListener;
- }
- }, ch, promise));
+ public void initialize(SocketChannel ch, Promise<NetconfClientSession> promise) {
+ super.initialize(ch,promise);
+ }
+
+ @Override
+ protected void initializeSessionNegotiator(SocketChannel ch, Promise<NetconfClientSession> promise) {
+ ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
+ negotiatorFactory.getSessionNegotiator(
+ new SessionListenerFactory<NetconfClientSessionListener>() {
+ @Override
+ public NetconfClientSessionListener getSessionListener() {
+ return sessionListener;
+ }
+ }, ch, promise));
}
}
package org.opendaylight.controller.netconf.client;
-import io.netty.channel.Channel;
-import io.netty.util.Timer;
-import io.netty.util.concurrent.Promise;
-
import java.util.Collection;
import java.util.List;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
-import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.api.NetconfSessionPreferences;
import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
+import io.netty.channel.Channel;
+import io.netty.util.Timer;
+import io.netty.util.concurrent.Promise;
+
public class NetconfClientSessionNegotiator extends
AbstractNetconfSessionNegotiator<NetconfSessionPreferences, NetconfClientSession, NetconfClientSessionListener> {
}
@Override
- protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfMessage message) {
+ protected NetconfClientSession getSession(NetconfClientSessionListener sessionListener, Channel channel, NetconfHelloMessage message) {
return new NetconfClientSession(sessionListener, channel, extractSessionId(message.getDocument()),
getCapabilities(message.getDocument()));
}
import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.api.NetconfSessionPreferences;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.protocol.framework.SessionListenerFactory;
import org.opendaylight.protocol.framework.SessionNegotiator;
public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorFactory<NetconfMessage, NetconfClientSession, NetconfClientSessionListener> {
- private final Optional<String> additionalHeader;
+ private final Optional<NetconfHelloMessageAdditionalHeader> additionalHeader;
private final long connectionTimeoutMillis;
private final Timer timer;
- public NetconfClientSessionNegotiatorFactory(Timer timer, Optional<String> additionalHeader, long connectionTimeoutMillis) {
+ public NetconfClientSessionNegotiatorFactory(Timer timer, Optional<NetconfHelloMessageAdditionalHeader> additionalHeader, long connectionTimeoutMillis) {
this.timer = Preconditions.checkNotNull(timer);
this.additionalHeader = additionalHeader;
this.connectionTimeoutMillis = connectionTimeoutMillis;
Promise<NetconfClientSession> promise) {
// Hello message needs to be recreated every time
NetconfMessage helloMessage = loadHelloMessageTemplate();
+
if(this.additionalHeader.isPresent()) {
- helloMessage = new NetconfMessage(helloMessage.getDocument(), additionalHeader.get());
- }
+ helloMessage = new NetconfHelloMessage(helloMessage.getDocument(), additionalHeader.get());
+ } else
+ helloMessage = new NetconfHelloMessage(helloMessage.getDocument());
+
NetconfSessionPreferences proposal = new NetconfSessionPreferences(helloMessage);
return new NetconfClientSessionNegotiator(proposal, promise, channel, timer,
sessionListenerFactory.getSessionListener(), connectionTimeoutMillis);
import org.opendaylight.controller.netconf.util.handler.ssh.SshHandler;
import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.protocol.framework.ReconnectStrategy;
import org.opendaylight.protocol.framework.ReconnectStrategyFactory;
import org.opendaylight.protocol.framework.SessionListenerFactory;
private final AuthenticationHandler authHandler;
private final HashedWheelTimer timer;
- private final NetconfClientSessionNegotiatorFactory negotatorFactory;
+ private final NetconfClientSessionNegotiatorFactory negotiatorFactory;
public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup,
EventLoopGroup workerGroup, long connectionTimeoutMillis) {
super(bossGroup, workerGroup, connectionTimeoutMillis);
this.authHandler = authHandler;
this.timer = new HashedWheelTimer();
- this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.<String>absent(), connectionTimeoutMillis);
+ this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer,
+ Optional.<NetconfHelloMessageAdditionalHeader> absent(), connectionTimeoutMillis);
}
public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup,
- EventLoopGroup workerGroup, String additionalHeader, long socketTimeoutMillis) {
+ EventLoopGroup workerGroup, NetconfHelloMessageAdditionalHeader additionalHeader, long socketTimeoutMillis) {
super(bossGroup, workerGroup, additionalHeader, socketTimeoutMillis);
this.authHandler = authHandler;
this.timer = new HashedWheelTimer();
- this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader), socketTimeoutMillis);
+ this.negotiatorFactory = new NetconfClientSessionNegotiatorFactory(timer, Optional.of(additionalHeader),
+ socketTimeoutMillis);
}
@Override
@Override
public void initializeChannel(SocketChannel arg0, Promise<NetconfClientSession> arg1) {
- new NetconfSshClientInitializer(authHandler, negotatorFactory, sessionListener).initialize(arg0, arg1);
+ new NetconfSshClientInitializer(authHandler, negotiatorFactory, sessionListener).initialize(arg0, arg1);
}
});
public Future<Void> createReconnectingClient(final InetSocketAddress address,
final NetconfClientSessionListener listener,
final ReconnectStrategyFactory connectStrategyFactory, final ReconnectStrategy reestablishStrategy) {
- final NetconfSshClientInitializer init = new NetconfSshClientInitializer(authHandler, negotatorFactory, listener);
+ final NetconfSshClientInitializer init = new NetconfSshClientInitializer(authHandler, negotiatorFactory, listener);
return super.createReconnectingClient(address, connectStrategyFactory, reestablishStrategy,
new PipelineInitializer<NetconfClientSession>() {
}
@Override
- protected void initializeAfterDecoder(SocketChannel ch, Promise<NetconfClientSession> promise) {
- ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
+ protected void initializeSessionNegotiator(SocketChannel ch,
+ Promise<NetconfClientSession> promise) {
+ ch.pipeline().addAfter(NETCONF_MESSAGE_DECODER, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR,
+ negotiatorFactory.getSessionNegotiator(new SessionListenerFactory<NetconfClientSessionListener>() {
@Override
public NetconfClientSessionListener getSessionListener() {
return sessionListener;
}
}, ch, promise));
-
}
}
}
public static class ServerChannelInitializer extends AbstractChannelInitializer<NetconfServerSession> {
+ public static final String DESERIALIZER_EX_HANDLER_KEY = "deserializerExHandler";
+
private final NetconfServerSessionNegotiatorFactory negotiatorFactory;
private final NetconfServerSessionListenerFactory listenerFactory;
}
@Override
- protected void initializeAfterDecoder(SocketChannel ch, Promise<NetconfServerSession> promise) {
- ch.pipeline().addLast("deserializerExHandler", new DeserializerExceptionHandler());
- ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(listenerFactory, ch, promise));
+ protected void initializeMessageDecoder(SocketChannel ch) {
+ super.initializeMessageDecoder(ch);
+ ch.pipeline().addLast(DESERIALIZER_EX_HANDLER_KEY, new DeserializerExceptionHandler());
+ }
+
+ @Override
+ protected void initializeSessionNegotiator(SocketChannel ch, Promise<NetconfServerSession> promise) {
+ ch.pipeline().addAfter(DESERIALIZER_EX_HANDLER_KEY, AbstractChannelInitializer.NETCONF_SESSION_NEGOTIATOR, negotiatorFactory.getSessionNegotiator(listenerFactory, ch, promise));
}
}
import org.opendaylight.controller.netconf.api.AbstractNetconfSession;
import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.DomainName;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Host;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.NetconfTcp;
private static final Logger logger = LoggerFactory.getLogger(NetconfServerSession.class);
- private final NetconfServerSessionNegotiator.AdditionalHeader header;
+ private final NetconfHelloMessageAdditionalHeader header;
private Date loginTime;
private long inRpcSuccess, inRpcFail, outRpcError;
public NetconfServerSession(NetconfServerSessionListener sessionListener, Channel channel, long sessionId,
- NetconfServerSessionNegotiator.AdditionalHeader header) {
+ NetconfHelloMessageAdditionalHeader header) {
super(sessionListener, channel, sessionId);
this.header = header;
logger.debug("Session {} created", toString());
public static final String ISO_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
+ private static final String dateTimePatternString = DateAndTime.PATTERN_CONSTANTS.get(0);
+ private static final Pattern dateTimePattern = Pattern.compile(dateTimePatternString);
+
@Override
public Session toManagementSession() {
SessionBuilder builder = new SessionBuilder();
Preconditions.checkState(DateAndTime.PATTERN_CONSTANTS.size() == 1);
String formattedDateTime = formatDateTime(loginTime);
- String pattern = DateAndTime.PATTERN_CONSTANTS.get(0);
- Matcher matcher = Pattern.compile(pattern).matcher(formattedDateTime);
- Preconditions.checkState(matcher.matches(), "Formatted datetime %s does not match pattern %s", formattedDateTime, pattern);
+
+ Matcher matcher = dateTimePattern.matcher(formattedDateTime);
+ Preconditions.checkState(matcher.matches(), "Formatted datetime %s does not match pattern %s", formattedDateTime, dateTimePattern);
builder.setLoginTime(new DateAndTime(formattedDateTime));
builder.setInBadRpcs(new ZeroBasedCounter32(inRpcFail));
builder.setInRpcs(new ZeroBasedCounter32(inRpcSuccess));
builder.setOutRpcErrors(new ZeroBasedCounter32(outRpcError));
- builder.setUsername(header.getUsername());
+ builder.setUsername(header.getUserName());
builder.setTransport(getTransportForString(header.getTransport()));
builder.setOutNotifications(new ZeroBasedCounter32(0L));
builder.setKey(new SessionKey(getSessionId()));
Session1Builder builder1 = new Session1Builder();
- builder1.setSessionIdentifier(header.getSessionType());
+ builder1.setSessionIdentifier(header.getSessionIdentifier());
builder.addAugmentation(Session1.class, builder1.build());
return builder.build();
package org.opendaylight.controller.netconf.impl;
-import io.netty.channel.Channel;
-import io.netty.util.Timer;
-import io.netty.util.concurrent.Promise;
-
import java.net.InetSocketAddress;
-import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.api.NetconfServerSessionPreferences;
-import org.opendaylight.controller.netconf.impl.util.AdditionalHeaderUtil;
import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
+import io.netty.channel.Channel;
+import io.netty.util.Timer;
+import io.netty.util.concurrent.Promise;
+
public class NetconfServerSessionNegotiator extends
AbstractNetconfSessionNegotiator<NetconfServerSessionPreferences, NetconfServerSession, NetconfServerSessionListener> {
}
@Override
- protected NetconfServerSession getSession(NetconfServerSessionListener sessionListener, Channel channel, NetconfMessage message) {
- Optional<String> additionalHeader = message.getAdditionalHeader();
+ protected NetconfServerSession getSession(NetconfServerSessionListener sessionListener, Channel channel, NetconfHelloMessage message) {
+ Optional<NetconfHelloMessageAdditionalHeader> additionalHeader = message.getAdditionalHeader();
- AdditionalHeader parsedHeader;
+ NetconfHelloMessageAdditionalHeader parsedHeader;
if (additionalHeader.isPresent()) {
- parsedHeader = AdditionalHeaderUtil.fromString(additionalHeader.get());
+ parsedHeader = additionalHeader.get();
} else {
- parsedHeader = new AdditionalHeader("unknown", ((InetSocketAddress)channel.localAddress()).getHostString(),
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.localAddress();
+ parsedHeader = new NetconfHelloMessageAdditionalHeader("unknown", inetSocketAddress.getHostString(), Integer.toString(inetSocketAddress.getPort()),
"tcp", "client");
}
+
logger.debug("Additional header from hello parsed as {} from {}", parsedHeader, additionalHeader);
return new NetconfServerSession(sessionListener, channel, sessionPreferences.getSessionId(), parsedHeader);
}
- public static class AdditionalHeader {
-
- private final String username;
- private final String address;
- private final String transport;
- private final String sessionIdentifier;
-
- public AdditionalHeader(String userName, String hostAddress, String transport, String sessionIdentifier) {
- this.address = hostAddress;
- this.username = userName;
- this.transport = transport;
- this.sessionIdentifier = sessionIdentifier;
- }
-
- String getUsername() {
- return username;
- }
-
- String getAddress() {
- return address;
- }
-
- String getTransport() {
- return transport;
- }
-
- String getSessionType() {
- return sessionIdentifier;
- }
-
- @Override
- public String toString() {
- final StringBuffer sb = new StringBuffer("AdditionalHeader{");
- sb.append("username='").append(username).append('\'');
- sb.append(", address='").append(address).append('\'');
- sb.append(", transport='").append(transport).append('\'');
- sb.append('}');
- return sb.toString();
- }
- }
-
-}
+ }
package org.opendaylight.controller.netconf.impl;
+import com.google.common.base.Preconditions;
import io.netty.channel.Channel;
import io.netty.util.Timer;
import io.netty.util.concurrent.Promise;
-
-import java.io.InputStream;
-
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpression;
-
-import org.opendaylight.controller.netconf.api.NetconfMessage;
import org.opendaylight.controller.netconf.api.NetconfServerSessionPreferences;
import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider;
import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListener;
import org.opendaylight.controller.netconf.util.NetconfUtil;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
-import com.google.common.base.Preconditions;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import java.io.InputStream;
-public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorFactory<NetconfMessage, NetconfServerSession, NetconfServerSessionListener> {
+public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorFactory<NetconfHelloMessage, NetconfServerSession, NetconfServerSessionListener> {
public static final String SERVER_HELLO_XML_LOCATION = "/server_hello.xml";
private static final XPathExpression capabilitiesXPath = XMLNetconfUtil
.compileXPath("/netconf:hello/netconf:capabilities");
- private NetconfMessage createHelloMessage(long sessionId) {
+ private NetconfHelloMessage createHelloMessage(long sessionId) {
Document helloMessageTemplate = getHelloTemplateClone();
// change session ID
capabilityElement.setTextContent(capability);
capabilitiesElement.appendChild(capabilityElement);
}
- return new NetconfMessage(helloMessageTemplate);
+ return new NetconfHelloMessage(helloMessageTemplate);
}
private synchronized Document getHelloTemplateClone() {
+++ /dev/null
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-
-package org.opendaylight.controller.netconf.impl.util;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiator.AdditionalHeader;
-
-import com.google.common.base.Preconditions;
-
-public class AdditionalHeaderUtil {
-
- private static final Pattern pattern = Pattern
- .compile("\\[(?<username>[^;]+);(?<address>[0-9\\.]+)[:/](?<port>[0-9]+);(?<transport>[a-z]+)[^\\]]+\\]");
- private static final Pattern customHeaderPattern = Pattern
- .compile("\\[(?<username>[^;]+);(?<address>[0-9\\.]+)[:/](?<port>[0-9]+);(?<transport>[a-z]+);(?<sessionIdentifier>[a-z]+)[^\\]]+\\]");
-
- public static AdditionalHeader fromString(String additionalHeader) {
- additionalHeader = additionalHeader.trim();
- Matcher matcher = pattern.matcher(additionalHeader);
- Matcher matcher2 = customHeaderPattern.matcher(additionalHeader);
- Preconditions.checkArgument(matcher.matches(), "Additional header in wrong format %s, expected %s",
- additionalHeader, pattern);
- String username = matcher.group("username");
- String address = matcher.group("address");
- String transport = matcher.group("transport");
- String sessionIdentifier = "client";
- if (matcher2.matches()) {
- sessionIdentifier = matcher2.group("sessionIdentifier");
- }
- return new AdditionalHeader(username, address, transport, sessionIdentifier);
- }
-
-}
import junit.framework.Assert;
import org.junit.Test;
-import org.opendaylight.controller.netconf.impl.util.AdditionalHeaderUtil;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
public class AdditionalHeaderParserTest {
@Test
public void testParsing() throws Exception {
String s = "[netconf;10.12.0.102:48528;ssh;;;;;;]";
- NetconfServerSessionNegotiator.AdditionalHeader header = AdditionalHeaderUtil.fromString(s);
- Assert.assertEquals("netconf", header.getUsername());
+ NetconfHelloMessageAdditionalHeader header = NetconfHelloMessageAdditionalHeader.fromString(s);
+ Assert.assertEquals("netconf", header.getUserName());
Assert.assertEquals("10.12.0.102", header.getAddress());
Assert.assertEquals("ssh", header.getTransport());
}
@Test
public void testParsing2() throws Exception {
String s = "[tomas;10.0.0.0/10000;tcp;1000;1000;;/home/tomas;;]";
- NetconfServerSessionNegotiator.AdditionalHeader header = AdditionalHeaderUtil.fromString(s);
- Assert.assertEquals("tomas", header.getUsername());
+ NetconfHelloMessageAdditionalHeader header = NetconfHelloMessageAdditionalHeader.fromString(s);
+ Assert.assertEquals("tomas", header.getUserName());
Assert.assertEquals("10.0.0.0", header.getAddress());
Assert.assertEquals("tcp", header.getTransport());
}
@Test(expected = IllegalArgumentException.class)
public void testParsingNoUsername() throws Exception {
String s = "[10.12.0.102:48528;ssh;;;;;;]";
- AdditionalHeaderUtil.fromString(s);
+ NetconfHelloMessageAdditionalHeader.fromString(s);
}
}
import org.opendaylight.controller.netconf.mapping.api.NetconfOperationFilter;
import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService;
import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.slf4j.Logger;
}
nettyGroup = new NioEventLoopGroup();
- netconfClientDispatcher = new NetconfClientDispatcher( nettyGroup, nettyGroup, 5000);
+ NetconfHelloMessageAdditionalHeader additionalHeader = new NetconfHelloMessageAdditionalHeader("uname", "10.10.10.1", "830", "tcp", "client");
+ netconfClientDispatcher = new NetconfClientDispatcher( nettyGroup, nettyGroup, additionalHeader, 5000);
NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
factoriesListener.onAddNetconfOperationServiceFactory(mockOpF());
import org.opendaylight.controller.netconf.api.NetconfSession;
import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory;
+import org.opendaylight.controller.netconf.util.handler.NetconfHelloMessageToXMLEncoder;
import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator;
-import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder;
-import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder;
+import org.opendaylight.controller.netconf.util.handler.NetconfXMLToHelloMessageDecoder;
import org.opendaylight.controller.netconf.util.messages.FramingMechanism;
public abstract class AbstractChannelInitializer<S extends NetconfSession> {
- public void initialize(SocketChannel ch, Promise<S> promise){
- ch.pipeline().addLast("aggregator", new NetconfMessageAggregator(FramingMechanism.EOM));
- ch.pipeline().addLast(new NetconfXMLToMessageDecoder());
- initializeAfterDecoder(ch, promise);
- ch.pipeline().addLast("frameEncoder", FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
- ch.pipeline().addLast(new NetconfMessageToXMLEncoder());
+ public static final String NETCONF_MESSAGE_DECODER = "netconfMessageDecoder";
+ public static final String NETCONF_MESSAGE_AGGREGATOR = "aggregator";
+ public static final String NETCONF_MESSAGE_ENCODER = "netconfMessageEncoder";
+ public static final String NETCONF_MESSAGE_FRAME_ENCODER = "frameEncoder";
+ public static final String NETCONF_SESSION_NEGOTIATOR = "negotiator";
+
+ public void initialize(SocketChannel ch, Promise<S> promise) {
+ ch.pipeline().addLast(NETCONF_MESSAGE_AGGREGATOR, new NetconfMessageAggregator(FramingMechanism.EOM));
+ initializeMessageDecoder(ch);
+ ch.pipeline().addLast(NETCONF_MESSAGE_FRAME_ENCODER, FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
+ initializeMessageEncoder(ch);
+
+ initializeSessionNegotiator(ch, promise);
+ }
+
+ protected void initializeMessageEncoder(SocketChannel ch) {
+ // Special encoding handler for hello message to include additional header if available,
+ // it is thrown away after successful negotiation
+ ch.pipeline().addLast(NETCONF_MESSAGE_ENCODER, new NetconfHelloMessageToXMLEncoder());
+ }
+
+ protected void initializeMessageDecoder(SocketChannel ch) {
+ // Special decoding handler for hello message to parse additional header if available,
+ // it is thrown away after successful negotiation
+ ch.pipeline().addLast(NETCONF_MESSAGE_DECODER, new NetconfXMLToHelloMessageDecoder());
}
- protected abstract void initializeAfterDecoder(SocketChannel ch, Promise<S> promise);
+ /**
+ * Insert session negotiator into the pipeline. It must be inserted after message decoder
+ * identified by {@link AbstractChannelInitializer#NETCONF_MESSAGE_DECODER}, (or any other custom decoder processor)
+ */
+ protected abstract void initializeSessionNegotiator(SocketChannel ch, Promise<S> promise);
}
package org.opendaylight.controller.netconf.util;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.ssl.SslHandler;
-import io.netty.util.Timeout;
-import io.netty.util.Timer;
-import io.netty.util.TimerTask;
-import io.netty.util.concurrent.Future;
-import io.netty.util.concurrent.GenericFutureListener;
-import io.netty.util.concurrent.Promise;
-
import java.util.concurrent.TimeUnit;
import org.opendaylight.controller.netconf.api.AbstractNetconfSession;
import org.opendaylight.controller.netconf.api.NetconfSessionListener;
import org.opendaylight.controller.netconf.api.NetconfSessionPreferences;
import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator;
import org.opendaylight.controller.netconf.util.handler.NetconfMessageChunkDecoder;
+import org.opendaylight.controller.netconf.util.handler.NetconfMessageToXMLEncoder;
+import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder;
import org.opendaylight.controller.netconf.util.messages.FramingMechanism;
-import org.opendaylight.controller.netconf.util.xml.XmlElement;
-import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
import org.slf4j.Logger;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.Timeout;
+import io.netty.util.Timer;
+import io.netty.util.TimerTask;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import io.netty.util.concurrent.Promise;
+
public abstract class AbstractNetconfSessionNegotiator<P extends NetconfSessionPreferences, S extends AbstractNetconfSession<S, L>, L extends NetconfSessionListener<S>>
-extends AbstractSessionNegotiator<NetconfMessage, S> {
+extends AbstractSessionNegotiator<NetconfHelloMessage, S> {
private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSessionNegotiator.class);
public static final String NAME_OF_EXCEPTION_HANDLER = "lastExceptionHandler";
+ public static final String CHUNK_DECODER_CHANNEL_HANDLER_KEY = "chunkDecoder";
protected final P sessionPreferences;
}
@Override
- protected void handleMessage(NetconfMessage netconfMessage) {
+ protected void handleMessage(NetconfHelloMessage netconfMessage) {
final Document doc = netconfMessage.getDocument();
- if (isHelloMessage(doc)) {
- if (containsBase11Capability(doc)
- && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument())) {
- channel.pipeline().replace("frameEncoder", "frameEncoder",
- FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK));
- channel.pipeline().replace("aggregator", "aggregator",
- new NetconfMessageAggregator(FramingMechanism.CHUNK));
- channel.pipeline().addAfter("aggregator", "chunkDecoder", new NetconfMessageChunkDecoder());
+ // Only Hello message should arrive during negotiation
+ if (netconfMessage instanceof NetconfHelloMessage) {
+
+ replaceHelloMessageHandlers();
+
+ if (shouldUseChunkFraming(doc)) {
+ insertChunkFramingToPipeline();
}
+
changeState(State.ESTABLISHED);
- S session = getSession(sessionListener, channel, netconfMessage);
+ S session = getSession(sessionListener, channel, (NetconfHelloMessage)netconfMessage);
+
negotiationSuccessful(session);
} else {
final IllegalStateException cause = new IllegalStateException(
}
}
- protected abstract S getSession(L sessionListener, Channel channel, NetconfMessage message);
+ /**
+ * Insert chunk framing handlers into the pipeline
+ */
+ private void insertChunkFramingToPipeline() {
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_FRAME_ENCODER,
+ FramingMechanismHandlerFactory.createHandler(FramingMechanism.CHUNK));
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR,
+ new NetconfMessageAggregator(FramingMechanism.CHUNK));
+ channel.pipeline().addAfter(AbstractChannelInitializer.NETCONF_MESSAGE_AGGREGATOR,
+ CHUNK_DECODER_CHANNEL_HANDLER_KEY, new NetconfMessageChunkDecoder());
+ }
+
+ private boolean shouldUseChunkFraming(Document doc) {
+ return containsBase11Capability(doc)
+ && containsBase11Capability(sessionPreferences.getHelloMessage().getDocument());
+ }
- private boolean isHelloMessage(Document doc) {
- try {
- XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), "hello",
- XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
+ /**
+ * Remove special handlers for hello message. Insert regular netconf xml message (en|de)coders.
+ */
+ private void replaceHelloMessageHandlers() {
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_DECODER, new NetconfXMLToMessageDecoder());
+ replaceChannelHandler(channel, AbstractChannelInitializer.NETCONF_MESSAGE_ENCODER, new NetconfMessageToXMLEncoder());
+ }
- } catch (IllegalArgumentException | IllegalStateException e) {
- return false;
- }
- return true;
+ private static ChannelHandler replaceChannelHandler(Channel channel, String handlerKey, ChannelHandler decoder) {
+ return channel.pipeline().replace(handlerKey, handlerKey, decoder);
}
+ protected abstract S getSession(L sessionListener, Channel channel, NetconfHelloMessage message);
+
private synchronized void changeState(final State newState) {
logger.debug("Changing state from : {} to : {}", state, newState);
Preconditions.checkState(isStateChangePermitted(state, newState), "Cannot change state from %s to %s", state,
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.util.handler;
+
+import java.nio.ByteBuffer;
+
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+/**
+ * Customized NetconfMessageToXMLEncoder that serializes additional header with
+ * session metadata along with
+ * {@link org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage}
+ * . Used by netconf clients to send information about the user, ip address,
+ * protocol etc.
+ * <p/>
+ * Hello message with header example:
+ * <p/>
+ *
+ * <pre>
+ * {@code
+ * [tomas;10.0.0.0/10000;tcp;1000;1000;;/home/tomas;;]
+ * <hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ * <capabilities>
+ * <capability>urn:ietf:params:netconf:base:1.0</capability>
+ * </capabilities>
+ * </hello>
+ * }
+ * </pre>
+ */
+public final class NetconfHelloMessageToXMLEncoder extends NetconfMessageToXMLEncoder {
+
+ @Override
+ protected ByteBuffer encodeMessage(NetconfMessage msg) {
+ Preconditions.checkState(msg instanceof NetconfHelloMessage, "Netconf message of type %s expected, was %s",
+ NetconfHelloMessage.class, msg.getClass());
+ Optional<NetconfHelloMessageAdditionalHeader> headerOptional = ((NetconfHelloMessage) msg)
+ .getAdditionalHeader();
+
+ // If additional header present, serialize it along with netconf hello
+ // message
+ if (headerOptional.isPresent()) {
+ byte[] bytesFromHeader = headerOptional.get().toFormattedString().getBytes(Charsets.UTF_8);
+ byte[] bytesFromMessage = xmlToString(msg.getDocument()).getBytes(Charsets.UTF_8);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(bytesFromHeader.length + bytesFromMessage.length)
+ .put(bytesFromHeader).put(bytesFromMessage);
+ byteBuffer.flip();
+ return byteBuffer;
+ }
+
+ return super.encodeMessage(msg);
+ }
+}
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
-public final class NetconfMessageToXMLEncoder extends MessageToByteEncoder<NetconfMessage> {
+public class NetconfMessageToXMLEncoder extends MessageToByteEncoder<NetconfMessage> {
private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageToXMLEncoder.class);
private final Optional<String> clientId;
msg.getDocument().appendChild(comment);
}
- final ByteBuffer msgBytes;
- if(msg.getAdditionalHeader().isPresent()) {
- final String header = msg.getAdditionalHeader().get();
- LOG.trace("Header of netconf message parsed \n{}", header);
- // FIXME: this can be written in pieces
- msgBytes = Charsets.UTF_8.encode(header + xmlToString(msg.getDocument()));
- } else {
- msgBytes = Charsets.UTF_8.encode(xmlToString(msg.getDocument()));
- }
+ final ByteBuffer msgBytes = encodeMessage(msg);
LOG.trace("Putting message \n{}", xmlToString(msg.getDocument()));
out.writeBytes(msgBytes);
}
- private String xmlToString(Document doc) {
+ protected ByteBuffer encodeMessage(NetconfMessage msg) {
+ return Charsets.UTF_8.encode(xmlToString(msg.getDocument()));
+ }
+
+ protected String xmlToString(Document doc) {
return XmlUtil.toString(doc, false);
}
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.util.handler;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
+import org.w3c.dom.Document;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Customized NetconfXMLToMessageDecoder that reads additional header with
+ * session metadata from
+ * {@link org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage}
+ * . Used by netconf server to retrieve information about session metadata.
+ */
+public class NetconfXMLToHelloMessageDecoder extends NetconfXMLToMessageDecoder {
+
+ private static final List<byte[]> POSSIBLE_ENDS = ImmutableList.of(
+ new byte[] { ']', '\n' },
+ new byte[] { ']', '\r', '\n' });
+ private static final List<byte[]> POSSIBLE_STARTS = ImmutableList.of(
+ new byte[] { '[' },
+ new byte[] { '\r', '\n', '[' },
+ new byte[] { '\n', '[' });
+
+ private String additionalHeaderCache;
+
+ @Override
+ protected byte[] preprocessMessageBytes(byte[] bytes) {
+ // Extract bytes containing header with additional metadata
+
+ if (startsWithAdditionalHeader(bytes)) {
+ // Auth information containing username, ip address... extracted for monitoring
+ int endOfAuthHeader = getAdditionalHeaderEndIndex(bytes);
+ if (endOfAuthHeader > -1) {
+ byte[] additionalHeaderBytes = Arrays.copyOfRange(bytes, 0, endOfAuthHeader + 2);
+ additionalHeaderCache = additionalHeaderToString(additionalHeaderBytes);
+ bytes = Arrays.copyOfRange(bytes, endOfAuthHeader + 2, bytes.length);
+ }
+ }
+
+ return bytes;
+ }
+
+ @Override
+ protected void cleanUpAfterDecode() {
+ additionalHeaderCache = null;
+ }
+
+ @Override
+ protected NetconfMessage buildNetconfMessage(Document doc) {
+ return new NetconfHelloMessage(doc, additionalHeaderCache == null ? null
+ : NetconfHelloMessageAdditionalHeader.fromString(additionalHeaderCache));
+ }
+
+ private int getAdditionalHeaderEndIndex(byte[] bytes) {
+ for (byte[] possibleEnd : POSSIBLE_ENDS) {
+ int idx = findByteSequence(bytes, possibleEnd);
+
+ if (idx != -1) {
+ return idx;
+ }
+ }
+
+ return -1;
+ }
+
+ private static int findByteSequence(final byte[] bytes, final byte[] sequence) {
+ if (bytes.length < sequence.length) {
+ throw new IllegalArgumentException("Sequence to be found is longer than the given byte array.");
+ }
+ if (bytes.length == sequence.length) {
+ if (Arrays.equals(bytes, sequence)) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+ int j = 0;
+ for (int i = 0; i < bytes.length; i++) {
+ if (bytes[i] == sequence[j]) {
+ j++;
+ if (j == sequence.length) {
+ return i - j + 1;
+ }
+ } else {
+ j = 0;
+ }
+ }
+ return -1;
+ }
+
+ private boolean startsWithAdditionalHeader(byte[] bytes) {
+ for (byte[] possibleStart : POSSIBLE_STARTS) {
+ int i = 0;
+ for (byte b : possibleStart) {
+ if(bytes[i++] != b)
+ break;
+
+ if(i == possibleStart.length)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private String additionalHeaderToString(byte[] bytes) {
+ return Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString();
+ }
+
+}
*/
package org.opendaylight.controller.netconf.util.handler;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.ByteToMessageDecoder;
-
import java.io.ByteArrayInputStream;
-import java.io.IOException;
import java.nio.ByteBuffer;
-import java.util.Arrays;
import java.util.List;
import org.opendaylight.controller.netconf.api.NetconfDeserializerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
-import org.xml.sax.SAXException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-public final class NetconfXMLToMessageDecoder extends ByteToMessageDecoder {
- private static final Logger LOG = LoggerFactory.getLogger(NetconfXMLToMessageDecoder.class);
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
- private static final List<byte[]> POSSIBLE_ENDS = ImmutableList.of(
- new byte[] { ']', '\n' },
- new byte[] { ']', '\r', '\n' });
- private static final List<byte[]> POSSIBLE_STARTS = ImmutableList.of(
- new byte[] { '[' },
- new byte[] { '\r', '\n', '[' },
- new byte[] { '\n', '[' });
+public class NetconfXMLToMessageDecoder extends ByteToMessageDecoder {
+ private static final Logger LOG = LoggerFactory.getLogger(NetconfXMLToMessageDecoder.class);
@Override
@VisibleForTesting
logMessage(bytes);
- String additionalHeader = null;
-
- // FIXME: this has to be moved into the negotiator and explained as to what the heck
- // is going on. This is definitely not specified in NETCONF and has no place here. It
- // requires reading all data and incurs inefficiency by being unable to pipe the ByteBuf
- // directly into the XML decoder.
- if (startsWithAdditionalHeader(bytes)) {
- // Auth information containing username, ip address... extracted for monitoring
- int endOfAuthHeader = getAdditionalHeaderEndIndex(bytes);
- if (endOfAuthHeader > -1) {
- byte[] additionalHeaderBytes = Arrays.copyOfRange(bytes, 0, endOfAuthHeader + 2);
- additionalHeader = additionalHeaderToString(additionalHeaderBytes);
- bytes = Arrays.copyOfRange(bytes, endOfAuthHeader + 2, bytes.length);
- }
- }
+ bytes = preprocessMessageBytes(bytes);
NetconfMessage message;
try {
Document doc = XmlUtil.readXmlToDocument(new ByteArrayInputStream(bytes));
- message = new NetconfMessage(doc, additionalHeader);
- } catch (final SAXException | IOException | IllegalStateException e) {
+ message = buildNetconfMessage(doc);
+ } catch (Exception e) {
throw new NetconfDeserializerException("Could not parse message from " + new String(bytes), e);
}
out.add(message);
} finally {
in.discardReadBytes();
+ cleanUpAfterDecode();
}
}
- private int getAdditionalHeaderEndIndex(byte[] bytes) {
- for (byte[] possibleEnd : POSSIBLE_ENDS) {
- int idx = findByteSequence(bytes, possibleEnd);
-
- if (idx != -1) {
- return idx;
- }
- }
+ protected void cleanUpAfterDecode() {}
- return -1;
+ protected NetconfMessage buildNetconfMessage(Document doc) {
+ return new NetconfMessage(doc);
}
- private static int findByteSequence(final byte[] bytes, final byte[] sequence) {
- if (bytes.length < sequence.length) {
- throw new IllegalArgumentException("Sequence to be found is longer than the given byte array.");
- }
- if (bytes.length == sequence.length) {
- if (Arrays.equals(bytes, sequence)) {
- return 0;
- } else {
- return -1;
- }
- }
- int j = 0;
- for (int i = 0; i < bytes.length; i++) {
- if (bytes[i] == sequence[j]) {
- j++;
- if (j == sequence.length) {
- return i - j + 1;
- }
- } else {
- j = 0;
- }
- }
- return -1;
+ protected byte[] preprocessMessageBytes(byte[] bytes) {
+ return bytes;
}
- private boolean startsWithAdditionalHeader(byte[] bytes) {
- for (byte[] possibleStart : POSSIBLE_STARTS) {
- int i = 0;
- for (byte b : possibleStart) {
- if(bytes[i] != b)
- break;
-
- return true;
- }
- }
-
- return false;
- };
-
private void logMessage(byte[] bytes) {
String s = Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString();
LOG.debug("Parsing message \n{}", s);
}
- private String additionalHeaderToString(byte[] bytes) {
- return Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString();
- }
-
}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.util.messages;
+
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.xml.XmlElement;
+import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.w3c.dom.Document;
+
+import com.google.common.base.Optional;
+
+/**
+ * NetconfMessage that can carry additional header with session metadata. See {@link org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader}
+ */
+public final class NetconfHelloMessage extends NetconfMessage {
+
+ public static final String HELLO_TAG = "hello";
+
+ private final NetconfHelloMessageAdditionalHeader additionalHeader;
+
+ public NetconfHelloMessage(Document doc, NetconfHelloMessageAdditionalHeader additionalHeader) {
+ super(doc);
+ checkHelloMessage(doc);
+ this.additionalHeader = additionalHeader;
+ }
+
+ public NetconfHelloMessage(Document doc) {
+ this(doc, null);
+ }
+
+ public Optional<NetconfHelloMessageAdditionalHeader> getAdditionalHeader() {
+ return additionalHeader== null ? Optional.<NetconfHelloMessageAdditionalHeader>absent() : Optional.of(additionalHeader);
+ }
+
+ private static void checkHelloMessage(Document doc) {
+ try {
+ XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), HELLO_TAG,
+ XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
+
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new IllegalArgumentException(String.format(
+ "Hello message invalid format, should contain %s tag from namespace %s, but is: %s", HELLO_TAG,
+ XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, XmlUtil.toString(doc)), e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.util.messages;
+
+import com.google.common.base.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Additional header can be used with hello message to carry information about
+ * session's connection. Provided information can be reported via netconf
+ * monitoring.
+ * <pre>
+ * It has pattern "[username; host-address:port; transport; session-identifier;]"
+ * username - name of account on a remote
+ * host-address - client's IP address
+ * port - port number
+ * transport - tcp, ssh
+ * session-identifier - persister, client
+ * Session-identifier is optional, others mandatory.
+ * </pre>
+ * This header is inserted in front of a netconf hello message followed by a newline.
+ */
+public class NetconfHelloMessageAdditionalHeader {
+
+ private static final String SC = ";";
+
+ private final String userName;
+ private final String hostAddress;
+ private final String port;
+ private final String transport;
+ private final String sessionIdentifier;
+
+ public NetconfHelloMessageAdditionalHeader(String userName, String hostAddress, String port, String transport, String sessionIdentifier) {
+ this.userName = userName;
+ this.hostAddress = hostAddress;
+ this.port = port;
+ this.transport = transport;
+ this.sessionIdentifier = sessionIdentifier;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public String getAddress() {
+ return hostAddress;
+ }
+
+ public String getPort() {
+ return port;
+ }
+
+ public String getTransport() {
+ return transport;
+ }
+
+ public String getSessionIdentifier() {
+ return sessionIdentifier;
+ }
+
+ /**
+ * Format additional header into a string suitable as a prefix for netconf hello message
+ */
+ public String toFormattedString() {
+ Preconditions.checkNotNull(userName);
+ Preconditions.checkNotNull(hostAddress);
+ Preconditions.checkNotNull(port);
+ Preconditions.checkNotNull(transport);
+ Preconditions.checkNotNull(sessionIdentifier);
+ return "[" + userName + SC + hostAddress + ":" + port + SC + transport + SC + sessionIdentifier + SC + "]"
+ + System.lineSeparator();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("NetconfHelloMessageAdditionalHeader{");
+ sb.append("userName='").append(userName).append('\'');
+ sb.append(", hostAddress='").append(hostAddress).append('\'');
+ sb.append(", port='").append(port).append('\'');
+ sb.append(", transport='").append(transport).append('\'');
+ sb.append(", sessionIdentifier='").append(sessionIdentifier).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+
+ // TODO IPv6
+ private static final Pattern pattern = Pattern
+ .compile("\\[(?<username>[^;]+);(?<address>[0-9\\.]+)[:/](?<port>[0-9]+);(?<transport>[a-z]+)[^\\]]+\\]");
+ private static final Pattern customHeaderPattern = Pattern
+ .compile("\\[(?<username>[^;]+);(?<address>[0-9\\.]+)[:/](?<port>[0-9]+);(?<transport>[a-z]+);(?<sessionIdentifier>[a-z]+)[^\\]]+\\]");
+
+ /**
+ * Parse additional header from a formatted string
+ */
+ public static NetconfHelloMessageAdditionalHeader fromString(String additionalHeader) {
+ additionalHeader = additionalHeader.trim();
+ Matcher matcher = pattern.matcher(additionalHeader);
+ Matcher matcher2 = customHeaderPattern.matcher(additionalHeader);
+ Preconditions.checkArgument(matcher.matches(), "Additional header in wrong format %s, expected %s",
+ additionalHeader, pattern);
+
+ String username = matcher.group("username");
+ String address = matcher.group("address");
+ String port = matcher.group("port");
+ String transport = matcher.group("transport");
+ String sessionIdentifier = "client";
+ if (matcher2.matches()) {
+ sessionIdentifier = matcher2.group("sessionIdentifier");
+ }
+ return new NetconfHelloMessageAdditionalHeader(username, address, port, transport, sessionIdentifier);
+ }
+
+}
+++ /dev/null
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-
-package org.opendaylight.controller.netconf.util.messages;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-
-/**
- * Additional header can be used with hello message to carry information about
- * session's connection. Provided information can be reported via netconf
- * monitoring.
- * <pre>
- * It has pattern "[username; host-address:port; transport; session-identifier;]"
- * username - name of account on a remote
- * host-address - client's IP address
- * port - port number
- * transport - tcp, ssh
- * session-identifier - persister, client
- * Session-identifier is optional, others mandatory.
- * </pre>
- */
-public class NetconfMessageAdditionalHeader {
-
- private static final String SC = ";";
-
- public static String toString(String userName, String hostAddress, String port, String transport,
- Optional<String> sessionIdentifier) {
- Preconditions.checkNotNull(userName);
- Preconditions.checkNotNull(hostAddress);
- Preconditions.checkNotNull(port);
- Preconditions.checkNotNull(transport);
- String identifier = sessionIdentifier.isPresent() ? sessionIdentifier.get() : "";
- return "[" + userName + SC + hostAddress + ":" + port + SC + transport + SC + identifier + SC + "]"
- + System.lineSeparator();
- }
-}
import java.util.List;
import org.junit.Test;
+import org.opendaylight.controller.netconf.util.handler.NetconfXMLToHelloMessageDecoder;
import org.opendaylight.controller.netconf.util.handler.NetconfXMLToMessageDecoder;
import com.google.common.io.Files;
public class NetconfMessageFactoryTest {
@Test
public void testAuth() throws Exception {
- NetconfXMLToMessageDecoder parser = new NetconfXMLToMessageDecoder();
+ NetconfXMLToMessageDecoder parser = new NetconfXMLToHelloMessageDecoder();
File authHelloFile = new File(getClass().getResource("/netconfMessages/client_hello_with_auth.xml").getFile());
final List<Object> out = new ArrayList<>();