From: Ed Warnicke Date: Fri, 22 Nov 2013 11:59:14 +0000 (+0000) Subject: Merge "MD-SAL Statistics Manager -Added group-id to group-statistics API" X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~359 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=c57785bd755dfdeaf829ce80a4b8b383d8c04602;hp=4b476e761f086dbac89f086eb684e19515eefe69 Merge "MD-SAL Statistics Manager -Added group-id to group-statistics API" --- diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java index f93409f99e..84c2c6dd4d 100644 --- a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java @@ -275,6 +275,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator .createModuleJMXRegistrator(); + OsgiRegistration osgiRegistration = null; if (entry.hasOldModule()) { ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo(); DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo @@ -282,19 +283,21 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe currentConfig.remove(entry.getName()); // test if old instance == new instance - if (oldReadableConfigBean.getInstance().equals( - module.getInstance())) { + if (oldReadableConfigBean.getInstance().equals(module.getInstance())) { // reused old instance: // wrap in readable dynamic mbean reusedInstances.add(primaryReadOnlyON); + osgiRegistration = oldInternalInfo.getOsgiRegistration(); } else { // recreated instance: // it is responsibility of module to call the old instance - // we just need to unregister configbean recreatedInstances.add(primaryReadOnlyON); + + // close old osgi registration + oldInternalInfo.getOsgiRegistration().close(); } - // close old osgi registration in any case - oldInternalInfo.getOsgiRegistration().close(); + // close old module jmx registrator oldInternalInfo.getModuleJMXRegistrator().close(); } else { @@ -316,10 +319,10 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe } // register to OSGi - OsgiRegistration osgiRegistration = beanToOsgiServiceManager - .registerToOsgi(module.getClass(), - newReadableConfigBean.getInstance(), - entry.getName()); + if (osgiRegistration == null) { + osgiRegistration = beanToOsgiServiceManager.registerToOsgi(module.getClass(), + newReadableConfigBean.getInstance(), entry.getName()); + } RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators .get(entry.getName()); diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java index e71aef4c04..c4f40fbeeb 100644 --- a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java @@ -36,6 +36,11 @@ public class ModuleInternalTransactionalInfo implements Identifiable + + org.opendaylight.controller + config-subsystem + 0.2.3-SNAPSHOT + + 4.0.0 + netty-config-api + ${project.artifactId} + bundle + + 3.0.4 + + + + + org.opendaylight.controller + config-api + + + com.google.guava + guava + + + io.netty + netty-transport + + + + + + org.apache.felix + maven-bundle-plugin + + + + org.opendaylight.controller.config.api.*, + io.netty.channel, + io.netty.util, + io.netty.util.concurrent + + + org.opendaylight.controller.config.yang.netty + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + \ No newline at end of file diff --git a/opendaylight/config/netty-config-api/src/main/yang/netty.yang b/opendaylight/config/netty-config-api/src/main/yang/netty.yang new file mode 100644 index 0000000000..7f7a3ff4ce --- /dev/null +++ b/opendaylight/config/netty-config-api/src/main/yang/netty.yang @@ -0,0 +1,52 @@ +// vi: set smarttab et sw=4 tabstop=4: +module netty { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:netty"; + prefix "netty"; + + import config { prefix config; revision-date 2013-04-05; } + + organization "Cisco Systems, Inc."; + + contact "Milos Fabian "; + + description + "This module contains the base YANG definitions for + netty services. + + Copyright (c)2013 Cisco Systems, Inc. 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"; + + revision "2013-11-19" { + description + "Initial revision."; + } + + identity netty-threadgroup { + description + "Configuration wrapper around netty's threadgroup"; + + base "config:service-type"; + config:java-class "io.netty.channel.EventLoopGroup"; + } + + identity netty-event-executor { + description + "Configuration wrapper around netty's event executor"; + + base "config:service-type"; + config:java-class "io.netty.util.concurrent.EventExecutor"; + } + + identity netty-timer { + description + "Configuration wrapper around netty's timer"; + + base "config:service-type"; + config:java-class "io.netty.util.Timer"; + } +} \ No newline at end of file diff --git a/opendaylight/config/netty-event-executor-config/pom.xml b/opendaylight/config/netty-event-executor-config/pom.xml index a2ce94f340..3d5384d171 100644 --- a/opendaylight/config/netty-event-executor-config/pom.xml +++ b/opendaylight/config/netty-event-executor-config/pom.xml @@ -21,17 +21,13 @@ org.opendaylight.controller - threadpool-config-api + netty-config-api ${project.version} org.slf4j slf4j-api - - com.google.guava - guava - @@ -80,7 +76,7 @@ com.google.common.base, - org.opendaylight.controller.config.yang.threadpool, + org.opendaylight.controller.config.yang.netty, io.netty.util.concurrent, org.opendaylight.controller.config.api, org.opendaylight.controller.config.api.annotations, diff --git a/opendaylight/config/netty-event-executor-config/src/main/java/org/opendaylight/controller/config/yang/netty/eventexecutor/GlobalEventExecutorModule.java b/opendaylight/config/netty-event-executor-config/src/main/java/org/opendaylight/controller/config/yang/netty/eventexecutor/GlobalEventExecutorModule.java index 3707f016a2..aed3a0cc15 100644 --- a/opendaylight/config/netty-event-executor-config/src/main/java/org/opendaylight/controller/config/yang/netty/eventexecutor/GlobalEventExecutorModule.java +++ b/opendaylight/config/netty-event-executor-config/src/main/java/org/opendaylight/controller/config/yang/netty/eventexecutor/GlobalEventExecutorModule.java @@ -37,7 +37,6 @@ public final class GlobalEventExecutorModule extends @Override public void validate() { super.validate(); - // Add custom validation for module attributes here. } @Override diff --git a/opendaylight/config/netty-event-executor-config/src/main/yang/netty-event-executor.yang b/opendaylight/config/netty-event-executor-config/src/main/yang/netty-event-executor.yang index d45eccded8..16e5c07356 100644 --- a/opendaylight/config/netty-event-executor-config/src/main/yang/netty-event-executor.yang +++ b/opendaylight/config/netty-event-executor-config/src/main/yang/netty-event-executor.yang @@ -2,18 +2,18 @@ module netty-event-executor { yang-version 1; namespace "urn:opendaylight:params:xml:ns:yang:controller:netty:eventexecutor"; - prefix "netty-t"; + prefix "netty-ee"; import config { prefix config; revision-date 2013-04-05; } - import threadpool { prefix th; revision-date 2013-04-09; } + import netty { prefix netty; revision-date 2013-11-19; } organization "Cisco Systems, Inc."; contact "Milos Fabian "; description - "This module contains the base YANG definitions for NS-OS - thread-related services. + "This module contains the base YANG definitions for + netty event executor implementation. Copyright (c)2013 Cisco Systems, Inc. All rights reserved.; @@ -29,7 +29,7 @@ module netty-event-executor { identity netty-global-event-executor { base config:module-type; - config:provided-service th:netty-event-executor; + config:provided-service netty:netty-event-executor; config:java-name-prefix GlobalEventExecutor; } @@ -39,6 +39,4 @@ module netty-event-executor { } } - - } diff --git a/opendaylight/config/netty-threadgroup-config/pom.xml b/opendaylight/config/netty-threadgroup-config/pom.xml index ef63fce2ce..8dc989e728 100644 --- a/opendaylight/config/netty-threadgroup-config/pom.xml +++ b/opendaylight/config/netty-threadgroup-config/pom.xml @@ -25,21 +25,14 @@ org.opendaylight.controller - threadpool-config-api + netty-config-api ${project.version} - org.slf4j slf4j-api - - - com.google.guava - guava - - junit @@ -82,7 +75,6 @@ org.apache.felix maven-bundle-plugin - 2.3.7 true @@ -92,7 +84,7 @@ com.google.common.base, io.netty.channel.nio, - org.opendaylight.controller.config.yang.threadpool, + org.opendaylight.controller.config.yang.netty, io.netty.util.concurrent, org.opendaylight.controller.config.api, org.opendaylight.controller.config.api.annotations, diff --git a/opendaylight/config/netty-threadgroup-config/src/main/java/org/opendaylight/controller/config/yang/netty/threadgroup/NettyThreadgroupModule.java b/opendaylight/config/netty-threadgroup-config/src/main/java/org/opendaylight/controller/config/yang/netty/threadgroup/NettyThreadgroupModule.java index fd6b216f53..9ceef3116a 100644 --- a/opendaylight/config/netty-threadgroup-config/src/main/java/org/opendaylight/controller/config/yang/netty/threadgroup/NettyThreadgroupModule.java +++ b/opendaylight/config/netty-threadgroup-config/src/main/java/org/opendaylight/controller/config/yang/netty/threadgroup/NettyThreadgroupModule.java @@ -9,9 +9,10 @@ */ package org.opendaylight.controller.config.yang.netty.threadgroup; -import com.google.common.base.Preconditions; import io.netty.channel.nio.NioEventLoopGroup; +import org.opendaylight.controller.config.api.JmxAttributeValidationException; + /** * */ @@ -28,7 +29,8 @@ public final class NettyThreadgroupModule extends org.opendaylight.controller.co @Override public void validate(){ if(getThreadCount()!=null) { - Preconditions.checkArgument(getThreadCount() > 0, "Thread count cannot be < 0"); + JmxAttributeValidationException.checkCondition(getThreadCount() > 0, "value must be greater than 0", + threadCountJmxAttribute); } } diff --git a/opendaylight/config/netty-threadgroup-config/src/main/yang/nsos-netty-threadgroup.yang b/opendaylight/config/netty-threadgroup-config/src/main/yang/netty-threadgroup.yang similarity index 83% rename from opendaylight/config/netty-threadgroup-config/src/main/yang/nsos-netty-threadgroup.yang rename to opendaylight/config/netty-threadgroup-config/src/main/yang/netty-threadgroup.yang index f13cf391bf..e648c5328a 100644 --- a/opendaylight/config/netty-threadgroup-config/src/main/yang/nsos-netty-threadgroup.yang +++ b/opendaylight/config/netty-threadgroup-config/src/main/yang/netty-threadgroup.yang @@ -1,19 +1,19 @@ // vi: set smarttab et sw=4 tabstop=4: -module nsos-threadpool { +module threadgroup { yang-version 1; namespace "urn:opendaylight:params:xml:ns:yang:controller:netty:threadgroup"; - prefix "netty-t"; + prefix "netty-th"; import config { prefix config; revision-date 2013-04-05; } - import threadpool { prefix th; revision-date 2013-04-09; } + import netty { prefix netty; revision-date 2013-11-19; } organization "Cisco Systems, Inc."; contact "Robert Varga "; description - "This module contains the base YANG definitions for NS-OS - thread-related services. + "This module contains the base YANG definitions for + netty threadgroup implementation. Copyright (c)2013 Cisco Systems, Inc. All rights reserved."; @@ -24,7 +24,7 @@ module nsos-threadpool { identity netty-threadgroup-fixed { base config:module-type; - config:provided-service th:netty-threadgroup; + config:provided-service netty:netty-threadgroup; config:java-name-prefix NettyThreadgroup; } diff --git a/opendaylight/config/netty-timer-config/pom.xml b/opendaylight/config/netty-timer-config/pom.xml new file mode 100644 index 0000000000..095e71fcf5 --- /dev/null +++ b/opendaylight/config/netty-timer-config/pom.xml @@ -0,0 +1,114 @@ + + + org.opendaylight.controller + config-subsystem + 0.2.3-SNAPSHOT + + 4.0.0 + netty-timer-config + Configuration Wrapper around netty's timer + bundle + ${project.artifactId} + + 3.0.4 + + + + + org.opendaylight.controller + config-api + + + org.opendaylight.controller + netty-config-api + ${project.version} + + + org.opendaylight.controller + threadpool-config-api + ${project.version} + + + org.slf4j + slf4j-api + + + + + junit + junit + test + + + org.opendaylight.controller + config-manager + test + test-jar + + + org.opendaylight.controller + config-manager + test + + + org.opendaylight.controller + config-util + test + + + org.opendaylight.bgpcep + mockito-configuration + test + + + org.opendaylight.controller + threadpool-config-impl + ${project.version} + test + + + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + + + + javax.management, + com.google.common.base, + org.opendaylight.controller.config.yang.netty, + org.opendaylight.controller.config.yang.threadpool, + io.netty.util, + org.opendaylight.controller.config.api, + org.opendaylight.controller.config.api.annotations, + org.opendaylight.controller.config.api.runtime, + org.opendaylight.controller.config.spi, + org.slf4j, + org.osgi.framework + + + + + + + + + + ${project.artifactId} + NETTY-TIMER-CONFIG Module site + ${basedir}/target/site/${project.artifactId} + + + \ No newline at end of file diff --git a/opendaylight/config/netty-timer-config/src/main/java/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModule.java b/opendaylight/config/netty-timer-config/src/main/java/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModule.java new file mode 100644 index 0000000000..cc78124680 --- /dev/null +++ b/opendaylight/config/netty-timer-config/src/main/java/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModule.java @@ -0,0 +1,99 @@ +/** + * Generated file + + * Generated from: yang module name: netty-event-executor yang module local name: netty-hashed-wheel-timer + * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + * Generated at: Tue Nov 19 12:49:59 CET 2013 + * + * Do not modify this file unless it is present under src/main directory + */ +package org.opendaylight.controller.config.yang.netty.timer; + +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.opendaylight.controller.config.api.JmxAttributeValidationException; + +/** +* +*/ +public final class HashedWheelTimerModule extends + org.opendaylight.controller.config.yang.netty.timer.AbstractHashedWheelTimerModule { + + public HashedWheelTimerModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, + org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public HashedWheelTimerModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, + org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, + HashedWheelTimerModule oldModule, java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void validate() { + super.validate(); + if (getTickDuration() != null) { + JmxAttributeValidationException.checkCondition(getTickDuration() > 0, "value must be greater than 0", + tickDurationJmxAttribute); + } + if (getTicksPerWheel() != null) { + JmxAttributeValidationException.checkCondition(getTicksPerWheel() > 0, "value must be greater than 0", + ticksPerWheelJmxAttribute); + } + } + + @Override + public java.lang.AutoCloseable createInstance() { + TimeUnit unit = TimeUnit.MILLISECONDS; + if (getTickDuration() != null && getThreadFactoryDependency() == null && getTicksPerWheel() == null) { + return new HashedWheelTimerCloseable(new HashedWheelTimer(getTickDuration(), unit)); + } + if (getTickDuration() != null && getThreadFactoryDependency() == null && getTicksPerWheel() != null) { + return new HashedWheelTimerCloseable(new HashedWheelTimer(getTickDuration(), unit, getTicksPerWheel())); + } + if (getTickDuration() == null && getThreadFactoryDependency() != null && getTicksPerWheel() == null) { + return new HashedWheelTimerCloseable(new HashedWheelTimer(getThreadFactoryDependency())); + } + if (getTickDuration() != null && getThreadFactoryDependency() != null && getTicksPerWheel() == null) { + return new HashedWheelTimerCloseable(new HashedWheelTimer(getThreadFactoryDependency(), getTickDuration(), + unit)); + } + if (getTickDuration() != null && getThreadFactoryDependency() != null && getTicksPerWheel() != null) { + return new HashedWheelTimerCloseable(new HashedWheelTimer(getThreadFactoryDependency(), getTickDuration(), + unit, getTicksPerWheel())); + } + return new HashedWheelTimerCloseable(new HashedWheelTimer()); + } + + static final private class HashedWheelTimerCloseable implements AutoCloseable, Timer { + + private final Timer timer; + + public HashedWheelTimerCloseable(Timer timer) { + this.timer = timer; + } + + @Override + public void close() throws Exception { + stop(); + } + + @Override + public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { + return this.timer.newTimeout(task, delay, unit); + } + + @Override + public Set stop() { + return this.timer.stop(); + } + + } +} diff --git a/opendaylight/config/netty-timer-config/src/main/java/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModuleFactory.java b/opendaylight/config/netty-timer-config/src/main/java/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModuleFactory.java new file mode 100644 index 0000000000..e291ab5465 --- /dev/null +++ b/opendaylight/config/netty-timer-config/src/main/java/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModuleFactory.java @@ -0,0 +1,18 @@ +/** + * Generated file + + * Generated from: yang module name: netty-event-executor yang module local name: netty-hashed-wheel-timer + * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + * Generated at: Tue Nov 19 12:49:59 CET 2013 + * + * Do not modify this file unless it is present under src/main directory + */ +package org.opendaylight.controller.config.yang.netty.timer; + +/** +* +*/ +public class HashedWheelTimerModuleFactory extends + org.opendaylight.controller.config.yang.netty.timer.AbstractHashedWheelTimerModuleFactory { + +} diff --git a/opendaylight/config/netty-timer-config/src/main/yang/netty-timer.yang b/opendaylight/config/netty-timer-config/src/main/yang/netty-timer.yang new file mode 100644 index 0000000000..b53b13f5a8 --- /dev/null +++ b/opendaylight/config/netty-timer-config/src/main/yang/netty-timer.yang @@ -0,0 +1,59 @@ +// vi: set smarttab et sw=4 tabstop=4: +module netty-timer { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:netty:timer"; + prefix "netty-timer"; + + import config { prefix config; revision-date 2013-04-05; } + import netty { prefix netty; revision-date 2013-11-19; } + import threadpool { prefix th; revision-date 2013-04-09; } + + organization "Cisco Systems, Inc."; + + contact "Milos Fabian "; + + description + "This module contains the base YANG definitions for + netty timer implementation. + + Copyright (c)2013 Cisco Systems, Inc. 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"; + + revision "2013-11-19" { + description + "Initial revision"; + } + + identity netty-hashed-wheel-timer { + base config:module-type; + config:provided-service netty:netty-timer; + config:java-name-prefix HashedWheelTimer; + } + + augment "/config:modules/config:module/config:configuration" { + case netty-hashed-wheel-timer { + when "/config:modules/config:module/config:type = 'netty-hashed-wheel-timer'"; + + leaf tick-duration { + type uint32; + } + + leaf ticks-per-wheel { + type uint16; + } + + container thread-factory { + uses config:service-ref { + refine type { + mandatory false; + config:required-identity th:threadfactory; + } + } + } + } + } +} \ No newline at end of file diff --git a/opendaylight/config/netty-timer-config/src/test/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModuleTest.java b/opendaylight/config/netty-timer-config/src/test/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModuleTest.java new file mode 100644 index 0000000000..8bc4d95d60 --- /dev/null +++ b/opendaylight/config/netty-timer-config/src/test/org/opendaylight/controller/config/yang/netty/timer/HashedWheelTimerModuleTest.java @@ -0,0 +1,131 @@ +package org.opendaylight.controller.config.yang.netty.timer; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.config.api.ConflictingVersionException; +import org.opendaylight.controller.config.api.ValidationException; +import org.opendaylight.controller.config.api.jmx.CommitStatus; +import org.opendaylight.controller.config.manager.impl.AbstractConfigTest; +import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; +import org.opendaylight.controller.config.util.ConfigTransactionJMXClient; +import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFactoryModuleFactory; +import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFactoryModuleMXBean; + +public class HashedWheelTimerModuleTest extends AbstractConfigTest { + + private HashedWheelTimerModuleFactory factory; + private NamingThreadFactoryModuleFactory threadFactory; + private final String instanceName = "hashed-wheel-timer1"; + + @Before + public void setUp() { + factory = new HashedWheelTimerModuleFactory(); + threadFactory = new NamingThreadFactoryModuleFactory(); + super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(factory, threadFactory)); + } + + public void testValidationExceptionTickDuration() throws InstanceAlreadyExistsException { + ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); + try { + createInstance(transaction, instanceName, 0L, 10, true); + transaction.validateConfig(); + Assert.fail(); + } catch (ValidationException e) { + Assert.assertTrue(e.getMessage().contains("TickDuration value must be greater than 0")); + } + } + + public void testValidationExceptionTicksPerWheel() throws InstanceAlreadyExistsException { + ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); + try { + createInstance(transaction, instanceName, 500L, 0, true); + transaction.validateConfig(); + Assert.fail(); + } catch (ValidationException e) { + Assert.assertTrue(e.getMessage().contains("TicksPerWheel value must be greater than 0")); + } + } + + @Test + public void testCreateBean() throws InstanceAlreadyExistsException, ValidationException, + ConflictingVersionException { + ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); + + createInstance(transaction, instanceName, 500L, 10, true); + createInstance(transaction, instanceName + 1, null, null, false); + createInstance(transaction, instanceName + 2, 500L, 10, false); + createInstance(transaction, instanceName + 3, 500L, null, false); + transaction.validateConfig(); + CommitStatus status = transaction.commit(); + + assertBeanCount(4, factory.getImplementationName()); + assertStatus(status, 5, 0, 0); + } + + @Test + public void testReusingOldInstance() throws InstanceAlreadyExistsException, ConflictingVersionException, + ValidationException { + + ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); + createInstance(transaction, instanceName, 500L, 10, true); + + transaction.commit(); + + transaction = configRegistryClient.createTransaction(); + assertBeanCount(1, factory.getImplementationName()); + CommitStatus status = transaction.commit(); + + assertBeanCount(1, factory.getImplementationName()); + assertStatus(status, 0, 0, 2); + } + + @Test + public void testReconfigure() throws InstanceAlreadyExistsException, ConflictingVersionException, + ValidationException, InstanceNotFoundException { + + ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); + createInstance(transaction, instanceName, 500L, 10, true); + transaction.commit(); + + transaction = configRegistryClient.createTransaction(); + assertBeanCount(1, factory.getImplementationName()); + HashedWheelTimerModuleMXBean mxBean = transaction.newMBeanProxy( + transaction.lookupConfigBean(factory.getImplementationName(), instanceName), + HashedWheelTimerModuleMXBean.class); + mxBean.setTicksPerWheel(20); + CommitStatus status = transaction.commit(); + + assertBeanCount(1, factory.getImplementationName()); + assertStatus(status, 0, 1, 1); + } + + private ObjectName createInstance(ConfigTransactionJMXClient transaction, String instanceName, + final Long tickDuration, final Integer ticksPerWheel, final boolean hasThreadfactory) + throws InstanceAlreadyExistsException { + ObjectName nameCreated = transaction.createModule(factory.getImplementationName(), instanceName); + HashedWheelTimerModuleMXBean mxBean = transaction + .newMBeanProxy(nameCreated, HashedWheelTimerModuleMXBean.class); + mxBean.setTickDuration(tickDuration); + mxBean.setTicksPerWheel(ticksPerWheel); + if (hasThreadfactory) { + mxBean.setThreadFactory(createThreadfactoryInstance(transaction, "thread-factory1", "th")); + } + return nameCreated; + } + + private ObjectName createThreadfactoryInstance(ConfigTransactionJMXClient transaction, String instanceName, + final String namePrefix) throws InstanceAlreadyExistsException { + ObjectName nameCreated = transaction.createModule(threadFactory.getImplementationName(), instanceName); + NamingThreadFactoryModuleMXBean mxBean = transaction.newMBeanProxy(nameCreated, + NamingThreadFactoryModuleMXBean.class); + mxBean.setNamePrefix(namePrefix); + return nameCreated; + } + +} diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml index 3f27ff1055..22be6f162b 100755 --- a/opendaylight/config/pom.xml +++ b/opendaylight/config/pom.xml @@ -30,9 +30,11 @@ yang-test logback-config threadpool-config-api + netty-config-api threadpool-config-impl netty-threadgroup-config netty-event-executor-config + netty-timer-config diff --git a/opendaylight/config/threadpool-config-api/pom.xml b/opendaylight/config/threadpool-config-api/pom.xml index cddfb64830..5c70ac7958 100644 --- a/opendaylight/config/threadpool-config-api/pom.xml +++ b/opendaylight/config/threadpool-config-api/pom.xml @@ -22,10 +22,6 @@ com.google.guava guava - - io.netty - netty-transport - @@ -38,8 +34,6 @@ org.opendaylight.controller.config.api.*, com.google.common.eventbus, - io.netty.channel, - io.netty.util.concurrent org.opendaylight.controller.config.threadpool, diff --git a/opendaylight/config/threadpool-config-api/src/main/yang/threadpool.yang b/opendaylight/config/threadpool-config-api/src/main/yang/threadpool.yang index 9c73711c17..8f3064822b 100644 --- a/opendaylight/config/threadpool-config-api/src/main/yang/threadpool.yang +++ b/opendaylight/config/threadpool-config-api/src/main/yang/threadpool.yang @@ -73,23 +73,4 @@ module threadpool { base "threadpool"; config:java-class "org.opendaylight.controller.config.threadpool.ScheduledThreadPool"; } - - - identity netty-threadgroup { - description - "Configuration wrapper around netty's threadgroup"; - - base "config:service-type"; - config:java-class "io.netty.channel.EventLoopGroup"; - } - - identity netty-event-executor { - description - "Configuration wrapper around netty's event executor"; - - base "config:service-type"; - config:java-class "io.netty.util.concurrent.EventExecutor"; - } - - } diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 75424ba1b6..74a133f913 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -242,6 +242,11 @@ threadpool-config-api ${config.version} + + org.opendaylight.controller + netty-config-api + ${config.version} + org.opendaylight.controller threadpool-config-impl @@ -249,7 +254,7 @@ org.opendaylight.controller - netty-event-executor-config + netty-threadgroup-config ${config.version} @@ -257,6 +262,11 @@ netty-event-executor-config ${config.version} + + org.opendaylight.controller + netty-timer-config + ${config.version} + diff --git a/opendaylight/md-sal/forwardingrules-manager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/consumer/impl/FlowConsumerImpl.java b/opendaylight/md-sal/forwardingrules-manager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/consumer/impl/FlowConsumerImpl.java index 82db78e7b9..9edb690a01 100644 --- a/opendaylight/md-sal/forwardingrules-manager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/consumer/impl/FlowConsumerImpl.java +++ b/opendaylight/md-sal/forwardingrules-manager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/consumer/impl/FlowConsumerImpl.java @@ -71,7 +71,7 @@ public class FlowConsumerImpl implements IForwardingRulesManager { private IContainer container; private static final String NAMEREGEX = "^[a-zA-Z0-9]+$"; private static ConcurrentMap staticFlows; - private static ConcurrentMap staticFlowsOrdinal; + private static ConcurrentMap staticFlowsOrdinal = new ConcurrentHashMap(); /* * Inactive flow list. This is for the global instance of FRM It will * contain all the flow entries which were installed on the global container @@ -499,14 +499,14 @@ public class FlowConsumerImpl implements IForwardingRulesManager { @Override public void onNodeErrorNotification(NodeErrorNotification notification) { // TODO Auto-generated method stub - + } @Override public void onNodeExperimenterErrorNotification( NodeExperimenterErrorNotification notification) { // TODO Auto-generated method stub - + }; } diff --git a/opendaylight/md-sal/model/model-flow-base/src/main/yang/flow-types.yang b/opendaylight/md-sal/model/model-flow-base/src/main/yang/flow-types.yang index 4b50c0ee72..67c6933cc7 100644 --- a/opendaylight/md-sal/model/model-flow-base/src/main/yang/flow-types.yang +++ b/opendaylight/md-sal/model/model-flow-base/src/main/yang/flow-types.yang @@ -72,6 +72,15 @@ module opendaylight-flow-types { bit SEND_FLOW_REM; } } + + typedef removed_reason_flags { + type bits { + bit IDLE_TIMEOUT; + bit HARD_TIMEOUT; + bit DELETE; + bit GROUP_DELETE; + } + } grouping generic_flow_attributes { leaf priority { @@ -185,6 +194,10 @@ module opendaylight-flow-types { grouping flow-mod-removed { uses generic_flow_attributes; + leaf removed_reason { + type removed_reason_flags; + } + leaf duration_nsec { type uint32; } @@ -192,22 +205,15 @@ module opendaylight-flow-types { leaf duration_sec { type uint32; } - - leaf idle_timeout { - type uint16; - } - - leaf hard_timeout { - type uint16; - } - + leaf packet_count { type uint64; } - + leaf byte_count { type uint64; } + container match { uses match:match; } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java index c36a79c5d9..a22ea62397 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java @@ -15,10 +15,9 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; -import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.restconf.impl.StructuredData; -import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; /** @@ -57,7 +56,6 @@ public interface RestconfService extends RestconfServiceLegacy { @GET public Object getRoot(); - @GET @Path("/modules") @Produces({API+JSON,API+XML}) @@ -68,23 +66,20 @@ public interface RestconfService extends RestconfServiceLegacy { @Produces({Draft02.MediaTypes.API+JSON,Draft02.MediaTypes.API+XML,API+JSON,API+XML}) public StructuredData invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload); - @GET @Path("/config/{identifier:.+}") @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML}) public StructuredData readConfigurationData(@PathParam("identifier") String identifier); - - @PUT @Path("/config/{identifier:.+}") @Produces({API+JSON,API+XML}) - public RpcResult createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); + public Response createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); @POST @Path("/config/{identifier:.+}") @Produces({API+JSON,API+XML}) - public RpcResult updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); + public Response updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload); @GET @Path("/operational/{identifier:.+}") @@ -94,12 +89,11 @@ public interface RestconfService extends RestconfServiceLegacy { @PUT @Path("/operational/{identifier:.+}") @Produces({API+JSON,API+XML}) - public RpcResult createOperationalData(@PathParam("identifier") String identifier, CompositeNode payload); + public Response createOperationalData(@PathParam("identifier") String identifier, CompositeNode payload); @POST @Path("/operational/{identifier:.+}") @Produces({API+JSON,API+XML}) - public RpcResult updateOperationalData(@PathParam("identifier") String identifier, CompositeNode payload); + public Response updateOperationalData(@PathParam("identifier") String identifier, CompositeNode payload); - } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java index 6683fd1835..242e7f3150 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java @@ -8,10 +8,9 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; -import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.restconf.impl.StructuredData; -import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; public interface RestconfServiceLegacy { @@ -35,12 +34,12 @@ public interface RestconfServiceLegacy { @PUT @Path("/datastore/{identifier:.+}") @Produces({API+JSON,API+XML}) - public RpcResult createConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload); + public Response createConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload); @Deprecated @POST @Path("/datastore/{identifier:.+}") @Produces({API+JSON,API+XML}) - public RpcResult updateConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload); + public Response updateConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java index a0acaf156f..a2ae1c9f7f 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java @@ -19,12 +19,12 @@ class JsonReader { public CompositeNodeWrapper read(InputStream entityStream) throws UnsupportedFormatException { JsonParser parser = new JsonParser(); - + JsonElement rootElement = parser.parse(new InputStreamReader(entityStream)); if (!rootElement.isJsonObject()) { throw new UnsupportedFormatException("Root element of Json has to be Object"); } - + Set> entrySetsOfRootJsonObject = rootElement.getAsJsonObject().entrySet(); if (entrySetsOfRootJsonObject.size() != 1) { throw new UnsupportedFormatException("Json Object should contain one element"); @@ -41,13 +41,15 @@ class JsonReader { if (firstElementInArray.isJsonObject()) { return createStructureWithRoot(firstElementName, firstElementInArray.getAsJsonObject()); } - throw new UnsupportedFormatException("Array as the first element in Json Object can have only Object element"); + throw new UnsupportedFormatException( + "Array as the first element in Json Object can have only Object element"); } } - throw new UnsupportedFormatException("First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."); + throw new UnsupportedFormatException( + "First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."); } } - + private CompositeNodeWrapper createStructureWithRoot(String rootObjectName, JsonObject rootObject) { CompositeNodeWrapper firstNode = new CompositeNodeWrapper(getNamespaceFrom(rootObjectName), getLocalNameFrom(rootObjectName)); @@ -56,7 +58,7 @@ class JsonReader { } return firstNode; } - + private void addChildToParent(String childName, JsonElement childType, CompositeNodeWrapper parent) { if (childType.isJsonObject()) { CompositeNodeWrapper child = new CompositeNodeWrapper(getNamespaceFrom(childName), @@ -66,19 +68,18 @@ class JsonReader { addChildToParent(childOfChild.getKey(), childOfChild.getValue(), child); } } else if (childType.isJsonArray()) { - for (JsonElement childOfChildType : childType.getAsJsonArray()) { - addChildToParent(childName, childOfChildType, parent); + if (childType.getAsJsonArray().size() == 1 && childType.getAsJsonArray().get(0).isJsonNull()) { + parent.addValue(new SimpleNodeWrapper(getNamespaceFrom(childName), getLocalNameFrom(childName), null)); + + } else { + for (JsonElement childOfChildType : childType.getAsJsonArray()) { + addChildToParent(childName, childOfChildType, parent); + } } } else if (childType.isJsonPrimitive()) { JsonPrimitive childPrimitive = childType.getAsJsonPrimitive(); String value = childPrimitive.getAsString(); - SimpleNodeWrapper child = null; - if (value.equals("[null]")) { - child = new SimpleNodeWrapper(getNamespaceFrom(childName), getLocalNameFrom(childName), null); - } else { - child = new SimpleNodeWrapper(getNamespaceFrom(childName), getLocalNameFrom(childName), value); - } - parent.addValue(child); + parent.addValue(new SimpleNodeWrapper(getNamespaceFrom(childName), getLocalNameFrom(childName), value)); } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend index a41a48287d..d9ac53589f 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend @@ -1,10 +1,12 @@ package org.opendaylight.controller.sal.restconf.impl import java.util.List +import javax.ws.rs.core.Response import org.opendaylight.controller.sal.rest.api.RestconfService import org.opendaylight.yangtools.yang.data.api.CompositeNode import org.opendaylight.yangtools.yang.model.api.DataNodeContainer import org.opendaylight.yangtools.yang.model.api.DataSchemaNode +import org.opendaylight.controller.md.sal.common.api.TransactionStatus class RestconfImpl implements RestconfService { @@ -48,13 +50,21 @@ class RestconfImpl implements RestconfService { override createConfigurationData(String identifier, CompositeNode payload) { val identifierWithSchemaNode = identifier.toInstanceIdentifier val value = resolveNodeNamespaceBySchema(payload, identifierWithSchemaNode.schemaNode) - return broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + switch status.result { + case TransactionStatus.COMMITED: Response.status(Response.Status.OK).build + default: Response.status(Response.Status.INTERNAL_SERVER_ERROR).build + } } override updateConfigurationData(String identifier, CompositeNode payload) { val identifierWithSchemaNode = identifier.toInstanceIdentifier val value = resolveNodeNamespaceBySchema(payload, identifierWithSchemaNode.schemaNode) - return broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + val status = broker.commitConfigurationDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + switch status.result { + case TransactionStatus.COMMITED: Response.status(Response.Status.NO_CONTENT).build + default: Response.status(Response.Status.INTERNAL_SERVER_ERROR).build + } } override invokeRpc(String identifier, CompositeNode payload) { @@ -88,13 +98,21 @@ class RestconfImpl implements RestconfService { override createOperationalData(String identifier, CompositeNode payload) { val identifierWithSchemaNode = identifier.toInstanceIdentifier val value = resolveNodeNamespaceBySchema(payload, identifierWithSchemaNode.schemaNode) - return broker.commitOperationalDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + val status = broker.commitOperationalDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + switch status.result { + case TransactionStatus.COMMITED: Response.status(Response.Status.OK).build + default: Response.status(Response.Status.INTERNAL_SERVER_ERROR).build + } } override updateOperationalData(String identifier, CompositeNode payload) { val identifierWithSchemaNode = identifier.toInstanceIdentifier val value = resolveNodeNamespaceBySchema(payload, identifierWithSchemaNode.schemaNode) - return broker.commitOperationalDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + val status = broker.commitOperationalDataPut(identifierWithSchemaNode.instanceIdentifier,value).get(); + switch status.result { + case TransactionStatus.COMMITED: Response.status(Response.Status.NO_CONTENT).build + default: Response.status(Response.Status.INTERNAL_SERVER_ERROR).build + } } private def CompositeNode resolveNodeNamespaceBySchema(CompositeNode node, DataSchemaNode schema) { diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java index a32a3479ba..251b212513 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java @@ -6,30 +6,85 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.yangtools.yang.common.RpcResult; public class DummyFuture implements Future> { + + private final boolean cancel; + private final boolean isCancelled; + private final boolean isDone; + private final RpcResult result; + + public DummyFuture() { + cancel = false; + isCancelled = false; + isDone = false; + result = null; + } + + private DummyFuture(Builder builder) { + cancel = builder.cancel; + isCancelled = builder.isCancelled; + isDone = builder.isDone; + result = builder.result; + } + + public static Builder builder() { + return new DummyFuture.Builder(); + } @Override public boolean cancel(boolean mayInterruptIfRunning) { - return false; + return cancel; } @Override public boolean isCancelled() { - return false; + return isCancelled; } @Override public boolean isDone() { - return false; + return isDone; } @Override public RpcResult get() throws InterruptedException, ExecutionException { - return null; + return result; } @Override public RpcResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return null; + return result; + } + + public static class Builder { + + private boolean cancel; + private boolean isCancelled; + private boolean isDone; + private RpcResult result; + + public Builder cancel(boolean cancel) { + this.cancel = cancel; + return this; + } + + public Builder isCancelled(boolean isCancelled) { + this.isCancelled = isCancelled; + return this; + } + + public Builder isDone(boolean isDone) { + this.isDone = isDone; + return this; + } + + public Builder rpcResult(RpcResult result) { + this.result = result; + return this; + } + + public Future> build() { + return new DummyFuture(this); + } } } \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java new file mode 100644 index 0000000000..5ab4f99fdc --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java @@ -0,0 +1,72 @@ +package org.opendaylight.controller.sal.restconf.impl.test; + +import java.util.Collection; + +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; + +public class DummyRpcResult implements RpcResult { + + private final boolean isSuccessful; + private final TransactionStatus result; + private final Collection errors; + + public DummyRpcResult() { + isSuccessful = false; + result = null; + errors = null; + } + + private DummyRpcResult(Builder builder) { + isSuccessful = builder.isSuccessful; + result = builder.result; + errors = builder.errors; + } + + public static Builder builder() { + return new DummyRpcResult.Builder(); + } + + @Override + public boolean isSuccessful() { + return isSuccessful; + } + + @Override + public TransactionStatus getResult() { + return result; + } + + @Override + public Collection getErrors() { + return errors; + } + + public static class Builder { + private boolean isSuccessful; + private TransactionStatus result; + private Collection errors; + + public Builder isSuccessful(boolean isSuccessful) { + this.isSuccessful = isSuccessful; + return this; + } + + public Builder result(TransactionStatus result) { + this.result = result; + return this; + } + + public Builder errors(Collection errors) { + this.errors = errors; + return this; + } + + public RpcResult build() { + return new DummyRpcResult(this); + } + + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromJsonToCompositeNode.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromJsonToCompositeNodeTest.java similarity index 71% rename from opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromJsonToCompositeNode.java rename to opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromJsonToCompositeNodeTest.java index dbbb4a6996..ede225c709 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromJsonToCompositeNode.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromJsonToCompositeNodeTest.java @@ -25,20 +25,53 @@ import org.opendaylight.yangtools.yang.model.api.*; import com.google.gson.JsonSyntaxException; -public class FromJsonToCompositeNode { +public class FromJsonToCompositeNodeTest { - private static Logger LOG = LoggerFactory.getLogger(FromJsonToCompositeNode.class); + private static final Logger LOG = LoggerFactory.getLogger(FromJsonToCompositeNodeTest.class); @Test public void simpleListTest() { simpleTest("/json-to-composite-node/simple-list.json", "/json-to-composite-node/simple-list-yang", "lst", - "simple:data:types"); + "simple:list:yang1", "simple-list-yang1"); } @Test public void simpleContainerTest() { simpleTest("/json-to-composite-node/simple-container.json", "/json-to-composite-node/simple-container-yang", - "cont", "simple:data:types"); + "cont", "simple:container:yang", "simple-container-yang"); + } + + /** + * test if for every leaf list item is simple node instance created + */ + @Test + public void multipleItemsInLeafList() { + CompositeNode compositeNode = compositeContainerFromJson( + "/json-to-composite-node/multiple-leaflist-items.json", true); + assertNotNull(compositeNode); + assertEquals(3, compositeNode.getChildren().size()); + + boolean lflst1_1 = false; + boolean lflst1_2 = false; + boolean lflst1_3 = false; + + for (Node node : compositeNode.getChildren()) { + assertEquals("lflst1", node.getNodeType().getLocalName()); + assertTrue(node instanceof SimpleNode); + SimpleNode simpleNode = (SimpleNode) node; + if (simpleNode.getValue().equals("45")) { + lflst1_1 = true; + } else if (simpleNode.getValue().equals("55")) { + lflst1_2 = true; + } else if (simpleNode.getValue().equals("66")) { + lflst1_3 = true; + } + } + + assertTrue(lflst1_1); + assertTrue(lflst1_2); + assertTrue(lflst1_3); + } /** @@ -56,6 +89,21 @@ public class FromJsonToCompositeNode { verityMultipleItemsInList(compositeNode); } + @Test + public void nullArrayToCompositeNodeWithNullValueTest() { + CompositeNode compositeNode = compositeContainerFromJson("/json-to-composite-node/array-with-null.json", true); + assertNotNull(compositeNode); + assertEquals("cont", compositeNode.getNodeType().getLocalName()); + + assertNotNull(compositeNode.getChildren()); + assertEquals(1, compositeNode.getChildren().size()); + Node lfNode = compositeNode.getChildren().iterator().next(); + + assertTrue(lfNode instanceof SimpleNode); + assertEquals(null, ((SimpleNode) lfNode).getValue()); + + } + @Test public void incorrectTopLevelElementsTest() { Throwable cause1 = null; @@ -124,13 +172,57 @@ public class FromJsonToCompositeNode { } - private void simpleTest(String jsonPath, String yangPath, String topLevelElementName, String namespace) { + /** + * Tests whether namespace stay unchanged if concrete values are + * present in composite or simple node and if the method for update is + * called. + * + */ + @Test + public void notSupplyNamespaceIfAlreadySupplied() { + + CompositeNode compositeNode = compositeContainerFromJson("/json-to-composite-node/simple-list.json"); + assertNotNull(compositeNode); + + DataSchemaNode dataSchemaNode1 = null; + DataSchemaNode dataSchemaNode2 = null; + try { + dataSchemaNode1 = TestUtils.obtainSchemaFromYang("/json-to-composite-node/simple-list-yang", + "simple-list-yang1"); + dataSchemaNode2 = TestUtils.obtainSchemaFromYang("/json-to-composite-node/simple-list-yang", + "simple-list-yang2"); + } catch (FileNotFoundException e) { + LOG.error(e.getMessage()); + assertTrue(false); + } + assertNotNull(dataSchemaNode1); + assertNotNull(dataSchemaNode2); + + // supplement namespaces according to first data schema - + // "simple:data:types1" + TestUtils.supplementNamespace(dataSchemaNode1, compositeNode); + + assertTrue(compositeNode instanceof CompositeNodeWrapper); + CompositeNode compNode = ((CompositeNodeWrapper) compositeNode).unwrap(null); + + assertEquals("lst", compNode.getNodeType().getLocalName()); + verifyCompositeNode(compNode, "simple:list:yang1"); + + // dataSchemaNode2 should't be taken into account, because compNode + // isn't CompositeNodeWrapper + TestUtils.supplementNamespace(dataSchemaNode2, compNode); + verifyCompositeNode(compNode, "simple:list:yang1"); + + } + + private void simpleTest(String jsonPath, String yangPath, String topLevelElementName, String namespace, + String moduleName) { CompositeNode compositeNode = compositeContainerFromJson(jsonPath); assertNotNull(compositeNode); DataSchemaNode dataSchemaNode = null; try { - dataSchemaNode = TestUtils.obtainSchemaFromYang(yangPath); + dataSchemaNode = TestUtils.obtainSchemaFromYang(yangPath, moduleName); } catch (FileNotFoundException e) { LOG.error(e.getMessage()); assertTrue(false); @@ -234,7 +326,7 @@ public class FromJsonToCompositeNode { throws WebApplicationException { JsonToCompositeNodeProvider jsonToCompositeNodeProvider = JsonToCompositeNodeProvider.INSTANCE; - InputStream jsonStream = FromJsonToCompositeNode.class.getResourceAsStream(jsonPath); + InputStream jsonStream = FromJsonToCompositeNodeTest.class.getResourceAsStream(jsonPath); try { CompositeNode compositeNode = jsonToCompositeNodeProvider .readFrom(null, null, null, null, null, jsonStream); diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromXmlToCompositeNode.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromXmlToCompositeNodeTest.java similarity index 97% rename from opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromXmlToCompositeNode.java rename to opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromXmlToCompositeNodeTest.java index 093cac57d0..6249d2a5b0 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromXmlToCompositeNode.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/FromXmlToCompositeNodeTest.java @@ -16,8 +16,8 @@ import org.opendaylight.yangtools.yang.data.api.*; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.slf4j.*; -public class FromXmlToCompositeNode { - private static Logger LOG = LoggerFactory.getLogger(FromXmlToCompositeNode.class); +public class FromXmlToCompositeNodeTest { + private static final Logger LOG = LoggerFactory.getLogger(FromXmlToCompositeNodeTest.class); /** * top level element represents container. second level element is list with @@ -230,7 +230,7 @@ public class FromXmlToCompositeNode { private CompositeNode compositeContainerFromXml(String xmlPath, boolean dummyNamespaces) { XmlToCompositeNodeProvider xmlToCompositeNodeProvider = XmlToCompositeNodeProvider.INSTANCE; try { - InputStream xmlStream = FromXmlToCompositeNode.class.getResourceAsStream(xmlPath); + InputStream xmlStream = FromXmlToCompositeNodeTest.class.getResourceAsStream(xmlPath); CompositeNode compositeNode = xmlToCompositeNodeProvider.readFrom(null, null, null, null, null, xmlStream); if (dummyNamespaces) { try { diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java index 3d06e4a759..1d8d7495f9 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java @@ -10,16 +10,22 @@ import java.io.*; import java.net.*; import java.sql.Date; import java.util.*; +import java.util.concurrent.Future; import javax.ws.rs.WebApplicationException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLStreamException; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider; import org.opendaylight.controller.sal.restconf.impl.*; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.*; import org.opendaylight.yangtools.yang.data.impl.XmlTreeBuilder; import org.opendaylight.yangtools.yang.model.api.*; @@ -27,6 +33,9 @@ import org.opendaylight.yangtools.yang.model.parser.api.YangModelParser; import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; import org.slf4j.*; import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import com.google.common.base.Preconditions; final class TestUtils { @@ -90,8 +99,20 @@ final class TestUtils { } return (CompositeNode) dataTree; } + + public static Document loadDocumentFrom(InputStream inputStream) { + try { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + return docBuilder.parse(inputStream); + } catch (SAXException | IOException | ParserConfigurationException e) { + logger.error("Error during loading Document from XML", e); + return null; + } + } public static String getDocumentInPrintableForm(Document doc) { + Preconditions.checkNotNull(doc); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); TransformerFactory tf = TransformerFactory.newInstance(); @@ -272,9 +293,10 @@ final class TestUtils { ControllerContext controllerContext = mock(ControllerContext.class); BrokerFacade broker = mock(BrokerFacade.class); + RpcResult rpcResult = DummyRpcResult.builder().result(TransactionStatus.COMMITED).build(); + Future> future = DummyFuture.builder().rpcResult(rpcResult).build(); when(controllerContext.toInstanceIdentifier(any(String.class))).thenReturn(instIdAndSchema); - when(broker.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn( - new DummyFuture()); + when(broker.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(future); restconf.setControllerContext(controllerContext); restconf.setBroker(broker); diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java index baf226712f..7b63c5fd94 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java @@ -1,22 +1,19 @@ package org.opendaylight.controller.sal.restconf.impl.test; -import static org.mockito.Mockito.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; -import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -24,11 +21,7 @@ import javax.ws.rs.client.Entity; import javax.ws.rs.core.Application; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; @@ -43,15 +36,11 @@ import org.opendaylight.controller.sal.restconf.impl.BrokerFacade; import org.opendaylight.controller.sal.restconf.impl.ControllerContext; import org.opendaylight.controller.sal.restconf.impl.MediaTypes; import org.opendaylight.controller.sal.restconf.impl.RestconfImpl; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcError; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.w3c.dom.Document; -import org.xml.sax.SAXException; import com.google.common.base.Charsets; @@ -60,15 +49,11 @@ public class XmlProvidersTest extends JerseyTest { private static ControllerContext controllerContext; private static BrokerFacade brokerFacade; private static RestconfImpl restconfImpl; + private static final MediaType MEDIA_TYPE = new MediaType("application", "vnd.yang.api+xml"); @BeforeClass - public static void init() { - Set allModules = null; - try { - allModules = TestUtils.loadModules(RestconfImplTest.class.getResource("/full-versions/yangs").getPath()); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } + public static void init() throws FileNotFoundException { + Set allModules = TestUtils.loadModules(RestconfImplTest.class.getResource("/full-versions/yangs").getPath()); SchemaContext schemaContext = TestUtils.loadSchemaContext(allModules); controllerContext = ControllerContext.getInstance(); controllerContext.setSchemas(schemaContext); @@ -87,96 +72,100 @@ public class XmlProvidersTest extends JerseyTest { } @Test - public void testStructuredDataToXmlProvider() throws FileNotFoundException { - URI uri = null; - try { - uri = new URI("/datastore/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString()); - } catch (UnsupportedEncodingException | URISyntaxException e) { - e.printStackTrace(); - } + public void testStructuredDataToXmlProvider() throws FileNotFoundException, UnsupportedEncodingException { + String uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0"); InputStream xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); CompositeNode loadedCompositeNode = TestUtils.loadCompositeNode(xmlStream); when(brokerFacade.readOperationalData(any(InstanceIdentifier.class))).thenReturn(loadedCompositeNode); - Response response = target(uri.toASCIIString()).request(MediaTypes.API+RestconfService.XML).get(); + Response response = target(uri).request(MEDIA_TYPE).get(); assertEquals(200, response.getStatus()); } @Test - public void testXmlToCompositeNodeProvider() throws ParserConfigurationException, SAXException, IOException { - URI uri = null; - try { - uri = new URI("/config/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString()); - } catch (UnsupportedEncodingException | URISyntaxException e) { - e.printStackTrace(); - } - InputStream xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); - final CompositeNode loadedCompositeNode = TestUtils.loadCompositeNode(xmlStream); - when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(new Future>() { - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return false; - } - @Override - public boolean isCancelled() { - return false; - } - @Override - public boolean isDone() { - return false; - } - @Override - public RpcResult get() throws InterruptedException, ExecutionException { - return null; - } - @Override - public RpcResult get(long timeout, TimeUnit unit) throws InterruptedException, - ExecutionException, TimeoutException { - return null; - } - }); + public void testBadFormatXmlToCompositeNodeProvider() throws UnsupportedEncodingException, URISyntaxException { + String uri = createUri("/operations/", "ietf-interfaces:interfaces/interface/eth0"); - DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); - xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); - Document doc = docBuilder.parse(xmlStream); + Response response = target(uri).request(MediaTypes.API + RestconfService.XML).post( + Entity.entity("", MEDIA_TYPE)); + assertEquals(400, response.getStatus()); - Response response = target(uri.toASCIIString()).request(MediaTypes.API+RestconfService.XML).post(Entity.entity(TestUtils.getDocumentInPrintableForm(doc), new MediaType("application","vnd.yang.api+xml"))); - assertEquals(204, response.getStatus()); + response = target(uri).request(MediaTypes.API + RestconfService.XML).post( + Entity.entity("", MEDIA_TYPE)); + assertEquals(400, response.getStatus()); } @Test - public void testXmlToCompositeNodeProviderExceptions() { - URI uri = null; - try { - uri = new URI("/operations/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString()); - } catch (UnsupportedEncodingException | URISyntaxException e) { - e.printStackTrace(); - } + public void testXmlToCompositeNode404NotFound() throws UnsupportedEncodingException, URISyntaxException { + String uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0"); - Response response = target(uri.toASCIIString()).request(MediaTypes.API + RestconfService.XML).post( - Entity.entity("", new MediaType("application", "vnd.yang.api+xml"))); - assertEquals(400, response.getStatus()); + when(brokerFacade.readOperationalData(any(InstanceIdentifier.class))).thenReturn(null); - response = target(uri.toASCIIString()).request(MediaTypes.API + RestconfService.XML).post( - Entity.entity("", new MediaType("application", "vnd.yang.api+xml"))); - assertEquals(400, response.getStatus()); + Response response = target(uri).request(MediaTypes.API+RestconfService.XML).get(); + assertEquals(404, response.getStatus()); } @Test - public void testXmlToCompositeNode404NotFound() { - URI uri = null; - try { - uri = new URI("/datastore/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString()); - } catch (UnsupportedEncodingException | URISyntaxException e) { - e.printStackTrace(); - } + public void testRpcResultCommitedToStatusCodes() throws UnsupportedEncodingException { + InputStream xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); + String xml = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(xmlStream)); + Entity entity = Entity.entity(xml, MEDIA_TYPE); + RpcResult rpcResult = DummyRpcResult.builder().result(TransactionStatus.COMMITED).build(); + Future> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build(); + when(brokerFacade.commitOperationalDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(dummyFuture); + when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(dummyFuture); - when(brokerFacade.readOperationalData(any(InstanceIdentifier.class))).thenReturn(null); + String uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0"); + Response response = target(uri).request(MEDIA_TYPE).put(entity); + assertEquals(200, response.getStatus()); + response = target(uri).request(MEDIA_TYPE).post(entity); + assertEquals(204, response.getStatus()); - Response response = target(uri.toASCIIString()).request(MediaTypes.API+RestconfService.XML).get(); - assertEquals(404, response.getStatus()); + uri = createUri("/operational/", "ietf-interfaces:interfaces/interface/eth0"); + response = target(uri).request(MEDIA_TYPE).put(entity); + assertEquals(200, response.getStatus()); + response = target(uri).request(MEDIA_TYPE).post(entity); + assertEquals(204, response.getStatus()); + + uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0"); + response = target(uri).request(MEDIA_TYPE).put(entity); + assertEquals(200, response.getStatus()); + response = target(uri).request(MEDIA_TYPE).post(entity); + assertEquals(204, response.getStatus()); + } + + @Test + public void testRpcResultOtherToStatusCodes() throws UnsupportedEncodingException { + InputStream xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); + String xml = TestUtils.getDocumentInPrintableForm(TestUtils.loadDocumentFrom(xmlStream)); + Entity entity = Entity.entity(xml, MEDIA_TYPE); + RpcResult rpcResult = DummyRpcResult.builder().result(TransactionStatus.FAILED).build(); + Future> dummyFuture = DummyFuture.builder().rpcResult(rpcResult).build(); + when(brokerFacade.commitOperationalDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(dummyFuture); + when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(dummyFuture); + + String uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0"); + Response response = target(uri).request(MEDIA_TYPE).put(entity); + assertEquals(500, response.getStatus()); + response = target(uri).request(MEDIA_TYPE).post(entity); + assertEquals(500, response.getStatus()); + + uri = createUri("/operational/", "ietf-interfaces:interfaces/interface/eth0"); + response = target(uri).request(MEDIA_TYPE).put(entity); + assertEquals(500, response.getStatus()); + response = target(uri).request(MEDIA_TYPE).post(entity); + assertEquals(500, response.getStatus()); + + uri = createUri("/datastore/", "ietf-interfaces:interfaces/interface/eth0"); + response = target(uri).request(MEDIA_TYPE).put(entity); + assertEquals(500, response.getStatus()); + response = target(uri).request(MEDIA_TYPE).post(entity); + assertEquals(500, response.getStatus()); + } + + private String createUri(String prefix, String encodedPart) throws UnsupportedEncodingException { + return URI.create(prefix + URLEncoder.encode(encodedPart, Charsets.US_ASCII.name()).toString()).toASCIIString(); } @Override diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/array-with-null.json b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/array-with-null.json new file mode 100644 index 0000000000..a19d9485f6 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/array-with-null.json @@ -0,0 +1,5 @@ +{ + "cont": { + "lf":[null] + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/multiple-leaflist-items.json b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/multiple-leaflist-items.json new file mode 100644 index 0000000000..b61a8a8f2e --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/multiple-leaflist-items.json @@ -0,0 +1,5 @@ +{ + "cont": { + "lflst1":[45,55,66] + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-container-yang/simple-container.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-container-yang/simple-container.yang index ddd67f7f80..493101ced1 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-container-yang/simple-container.yang +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-container-yang/simple-container.yang @@ -1,5 +1,5 @@ -module simple-data-types { - namespace "simple:data:types"; +module simple-container-yang { + namespace "simple:container:yang"; prefix "smpdtp"; revision 2013-11-12 { diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list1.yang similarity index 68% rename from opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list.yang rename to opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list1.yang index af8edfaf7f..0ce8ea428c 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list.yang +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list1.yang @@ -1,7 +1,7 @@ -module simple-data-types { - namespace "simple:data:types"; +module simple-list-yang1 { + namespace "simple:list:yang1"; - prefix "smpdtp"; + prefix "smplstyg"; revision 2013-11-12 { } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list2.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list2.yang new file mode 100644 index 0000000000..0872a4754d --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-composite-node/simple-list-yang/simple-list2.yang @@ -0,0 +1,20 @@ +module simple-list-yang2 { + namespace "simple:list:yang2"; + + prefix "smplstyg"; + revision 2013-11-12 { + } + + list lst { + container cont1 { + } + list lst1 { + } + leaf-list lflst1 { + type string; + } + leaf lf1 { + type string; + } + } +} \ No newline at end of file diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java index b3483a737a..b8113a0903 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java @@ -41,12 +41,12 @@ public class TransactionProvider implements AutoCloseable { @Override public synchronized void close() { for (ObjectName tx : allOpenedTransactions) { - if (isStillOpenTransaction(tx)) { - try { + try { + if (isStillOpenTransaction(tx)) { configRegistryClient.getConfigTransactionClient(tx).abortConfig(); - } catch (Exception e) { - logger.debug("Ignoring {} while closing transaction {}", e.toString(), tx, e); } + } catch (Exception e) { + logger.debug("Ignoring exception while closing transaction {}", tx, e); } } allOpenedTransactions.clear(); diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java index a20e00bcff..0d68e25f67 100644 --- a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java @@ -54,9 +54,9 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class); private final InetSocketAddress address; - private final NetconfClientDispatcher dispatcher; private final EventLoopGroup nettyThreadgroup; + private NetconfClientDispatcher netconfClientDispatcher; private NetconfClient netconfClient; private final Persister persister; @@ -81,7 +81,6 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, this.timeout = timeout; this.nettyThreadgroup = new NioEventLoopGroup(); - this.dispatcher = new NetconfClientDispatcher(Optional.absent(), nettyThreadgroup, nettyThreadgroup); } public void init() throws InterruptedException { @@ -125,11 +124,12 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, while (true) { attempt++; + netconfClientDispatcher = new NetconfClientDispatcher(Optional.absent(), nettyThreadgroup, nettyThreadgroup); try { - netconfClient = new NetconfClient(this.toString(), address, delay, dispatcher); - // TODO is this correct ex to catch ? + netconfClient = new NetconfClient(this.toString(), address, delay, netconfClientDispatcher); } catch (IllegalStateException e) { logger.debug("Netconf {} was not initialized or is not stable, attempt {}", address, attempt, e); + netconfClientDispatcher.close(); Thread.sleep(delay); continue; } @@ -148,11 +148,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, logger.debug("Polling hello from netconf, attempt {}, capabilities {}", attempt, currentCapabilities); - try { - netconfClient.close(); - } catch (IOException e) { - throw new RuntimeException("Error closing temporary client " + netconfClient); - } + closeClientAndDispatcher(netconfClient, netconfClientDispatcher); Thread.sleep(delay); } @@ -162,6 +158,25 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, } + private static void closeClientAndDispatcher(Closeable client, Closeable dispatcher) { + Exception fromClient = null; + try { + client.close(); + } catch (Exception e) { + fromClient = e; + } finally { + try { + dispatcher.close(); + } catch (Exception e) { + if (fromClient != null) { + e.addSuppressed(fromClient); + } + + throw new RuntimeException("Error closing temporary client ", e); + } + } + } + private boolean isSubset(Set currentCapabilities, Set expectedCaps) { for (String exCap : expectedCaps) { if (currentCapabilities.contains(exCap) == false) @@ -318,10 +333,18 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, } } + if (netconfClientDispatcher != null) { + try { + netconfClientDispatcher.close(); + } catch (Exception e) { + logger.warn("Unable to close connection to netconf {}", netconfClientDispatcher, e); + } + } + try { nettyThreadgroup.shutdownGracefully(); } catch (Exception e) { - logger.warn("Unable to close netconf client thread group {}", dispatcher, e); + logger.warn("Unable to close netconf client thread group {}", netconfClientDispatcher, e); } // unregister from JMX diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java index 6fc4da026f..62c2113056 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java @@ -23,20 +23,27 @@ import org.opendaylight.protocol.framework.AbstractDispatcher; import org.opendaylight.protocol.framework.ReconnectStrategy; import org.opendaylight.protocol.framework.SessionListener; import org.opendaylight.protocol.framework.SessionListenerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import java.io.Closeable; import java.net.InetSocketAddress; -public class NetconfClientDispatcher extends AbstractDispatcher { +public class NetconfClientDispatcher extends AbstractDispatcher implements Closeable { + + private static final Logger logger = LoggerFactory.getLogger(NetconfClient.class); private final Optional maybeContext; private final NetconfClientSessionNegotiatorFactory negotatorFactory; + private final HashedWheelTimer timer; public NetconfClientDispatcher(final Optional maybeContext, EventLoopGroup bossGroup, EventLoopGroup workerGroup) { super(bossGroup, workerGroup); this.maybeContext = Preconditions.checkNotNull(maybeContext); - this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(new HashedWheelTimer()); + timer = new HashedWheelTimer(); + this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer); } public Future createClient(InetSocketAddress address, @@ -83,4 +90,12 @@ public class NetconfClientDispatcher extends AbstractDispatcher x11_magic_cookies = new HashMap(); - - private final List channels = new Vector(); - private int nextLocalChannel = 100; - private boolean shutdown = false; - private int globalSuccessCounter = 0; - private int globalFailedCounter = 0; - - private final HashMap remoteForwardings = new HashMap(); - - private final List listenerThreads = new Vector(); - - private boolean listenerThreadsAllowed = true; - - /** - * Constructor for client-mode. - * @param tm - */ - public ChannelManager(TransportManager tm) - { - this.server_state = null; - this.tm = tm; - tm.registerMessageHandler(this, 80, 100); - } - - /** - * Constructor for server-mode. - * @param state - */ - public ChannelManager(ServerConnectionState state) - { - this.server_state = state; - this.tm = state.tm; - tm.registerMessageHandler(this, 80, 100); - } - - private Channel getChannel(int id) - { - synchronized (channels) - { - for (Channel c : channels) - { - if (c.localID == id) - return c; - } - } - return null; - } - - private void removeChannel(int id) - { - synchronized (channels) - { - for (Channel c : channels) - { - if (c.localID == id) - { - channels.remove(c); - break; - } - } - } - } - - private int addChannel(Channel c) - { - synchronized (channels) - { - channels.add(c); - return nextLocalChannel++; - } - } - - private void waitUntilChannelOpen(Channel c) throws IOException - { - boolean wasInterrupted = false; - - synchronized (c) - { - while (c.state == Channel.STATE_OPENING) - { - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - if (c.state != Channel.STATE_OPEN) - { - removeChannel(c.localID); - - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("Could not open channel (" + detail + ")"); - } - } - - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - - private void waitForGlobalSuccessOrFailure() throws IOException - { - boolean wasInterrupted = false; - - try - { - synchronized (channels) - { - while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) - { - if (shutdown) - { - throw new IOException("The connection is being shutdown"); - } - - try - { - channels.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - if (globalFailedCounter != 0) - { - throw new IOException("The server denied the request (did you enable port forwarding?)"); - } - - if (globalSuccessCounter == 0) - { - throw new IOException("Illegal state."); - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - private void waitForChannelSuccessOrFailure(Channel c) throws IOException - { - boolean wasInterrupted = false; - - try - { - synchronized (c) - { - while ((c.successCounter == 0) && (c.failedCounter == 0)) - { - if (c.state != Channel.STATE_OPEN) - { - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("This SSH2 channel is not open (" + detail + ")"); - } - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - if (c.failedCounter != 0) - { - throw new IOException("The server denied the request."); - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public void registerX11Cookie(String hexFakeCookie, X11ServerData data) - { - synchronized (x11_magic_cookies) - { - x11_magic_cookies.put(hexFakeCookie, data); - } - } - - public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) - { - if (hexFakeCookie == null) - throw new IllegalStateException("hexFakeCookie may not be null"); - - synchronized (x11_magic_cookies) - { - x11_magic_cookies.remove(hexFakeCookie); - } - - if (killChannels == false) - return; - - log.debug("Closing all X11 channels for the given fake cookie"); - - List channel_copy = new Vector(); - - synchronized (channels) - { - channel_copy.addAll(channels); - } - - for (Channel c : channel_copy) - { - synchronized (c) - { - if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) - continue; - } - - try - { - closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); - } - catch (IOException ignored) - { - } - } - } - - public X11ServerData checkX11Cookie(String hexFakeCookie) - { - synchronized (x11_magic_cookies) - { - if (hexFakeCookie != null) - return x11_magic_cookies.get(hexFakeCookie); - } - return null; - } - - public void closeAllChannels() - { - log.debug("Closing all channels"); - - List channel_copy = new Vector(); - - synchronized (channels) - { - channel_copy.addAll(channels); - } - - for (Channel c : channel_copy) - { - try - { - closeChannel(c, "Closing all channels", true); - } - catch (IOException ignored) - { - } - } - } - - public void closeChannel(Channel c, String reason, boolean force) throws IOException - { - byte msg[] = new byte[5]; - - synchronized (c) - { - if (force) - { - c.state = Channel.STATE_CLOSED; - c.EOF = true; - } - - c.setReasonClosed(reason); - - msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - - c.notifyAll(); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(msg); - c.closeMessageSent = true; - } - - log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); - } - - public void sendEOF(Channel c) throws IOException - { - byte[] msg = new byte[5]; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - return; - - msg[0] = Packets.SSH_MSG_CHANNEL_EOF; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(msg); - } - - - log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); - } - - public void sendOpenConfirmation(Channel c) throws IOException - { - PacketChannelOpenConfirmation pcoc = null; - - synchronized (c) - { - if (c.state != Channel.STATE_OPENING) - return; - - c.state = Channel.STATE_OPEN; - - pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(pcoc.getPayload()); - } - } - - public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException - { - boolean wasInterrupted = false; - - try - { - while (len > 0) - { - int thislen = 0; - byte[] msg; - - synchronized (c) - { - while (true) - { - if (c.state == Channel.STATE_CLOSED) - throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - if (c.state != Channel.STATE_OPEN) - throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")"); - - if (c.remoteWindow != 0) - break; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - /* len > 0, no sign extension can happen when comparing */ - - thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; - - int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); - - /* The worst case scenario =) a true bottleneck */ - - if (estimatedMaxDataLen <= 0) - { - estimatedMaxDataLen = 1; - } - - if (thislen > estimatedMaxDataLen) - thislen = estimatedMaxDataLen; - - c.remoteWindow -= thislen; - - msg = new byte[1 + 8 + thislen]; - - msg[0] = Packets.SSH_MSG_CHANNEL_DATA; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - msg[5] = (byte) (thislen >> 24); - msg[6] = (byte) (thislen >> 16); - msg[7] = (byte) (thislen >> 8); - msg[8] = (byte) (thislen); - - System.arraycopy(buffer, pos, msg, 9, thislen); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - tm.sendMessage(msg); - } - - pos += thislen; - len -= thislen; - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) - throws IOException - { - RemoteForwardingData rfd = new RemoteForwardingData(); - - rfd.bindAddress = bindAddress; - rfd.bindPort = bindPort; - rfd.targetAddress = targetAddress; - rfd.targetPort = targetPort; - - synchronized (remoteForwardings) - { - Integer key = new Integer(bindPort); - - if (remoteForwardings.get(key) != null) - { - throw new IOException("There is already a forwarding for remote port " + bindPort); - } - - remoteForwardings.put(key, rfd); - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); - tm.sendMessage(pgf.getPayload()); - - log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); - - try - { - waitForGlobalSuccessOrFailure(); - } - catch (IOException e) - { - synchronized (remoteForwardings) - { - remoteForwardings.remove(rfd); - } - throw e; - } - - return bindPort; - } + private static final Logger log = Logger.getLogger(ChannelManager.class); + + private final ServerConnectionState server_state; + private final TransportManager tm; + + private final HashMap x11_magic_cookies = new HashMap(); + + private final List channels = new Vector(); + private int nextLocalChannel = 100; + private boolean shutdown = false; + private int globalSuccessCounter = 0; + private int globalFailedCounter = 0; + + private final HashMap remoteForwardings = new HashMap(); + + private final List listenerThreads = new Vector(); + + private boolean listenerThreadsAllowed = true; + + /** + * Constructor for client-mode. + * @param tm + */ + public ChannelManager(TransportManager tm) + { + this.server_state = null; + this.tm = tm; + tm.registerMessageHandler(this, 80, 100); + } + + /** + * Constructor for server-mode. + * @param state + */ + public ChannelManager(ServerConnectionState state) + { + this.server_state = state; + this.tm = state.tm; + tm.registerMessageHandler(this, 80, 100); + } + + private Channel getChannel(int id) + { + synchronized (channels) + { + for (Channel c : channels) + { + if (c.localID == id) + return c; + } + } + return null; + } + + private void removeChannel(int id) + { + synchronized (channels) + { + for (Channel c : channels) + { + if (c.localID == id) + { + channels.remove(c); + break; + } + } + } + } + + private int addChannel(Channel c) + { + synchronized (channels) + { + channels.add(c); + return nextLocalChannel++; + } + } + + private void waitUntilChannelOpen(Channel c) throws IOException + { + boolean wasInterrupted = false; + + synchronized (c) + { + while (c.state == Channel.STATE_OPENING) + { + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + wasInterrupted = true; + } + } + + if (c.state != Channel.STATE_OPEN) + { + removeChannel(c.localID); + + String detail = c.getReasonClosed(); + + if (detail == null) + detail = "state: " + c.state; + + throw new IOException("Could not open channel (" + detail + ")"); + } + } + + if (wasInterrupted) + Thread.currentThread().interrupt(); + } + + private void waitForGlobalSuccessOrFailure() throws IOException + { + boolean wasInterrupted = false; + + try + { + synchronized (channels) + { + while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) + { + if (shutdown) + { + throw new IOException("The connection is being shutdown"); + } + + try + { + channels.wait(); + } + catch (InterruptedException ignore) + { + wasInterrupted = true; + } + } + + if (globalFailedCounter != 0) + { + throw new IOException("The server denied the request (did you enable port forwarding?)"); + } + + if (globalSuccessCounter == 0) + { + throw new IOException("Illegal state."); + } + } + } + finally + { + if (wasInterrupted) + Thread.currentThread().interrupt(); + } + } + + private void waitForChannelSuccessOrFailure(Channel c) throws IOException + { + boolean wasInterrupted = false; + + try + { + synchronized (c) + { + while ((c.successCounter == 0) && (c.failedCounter == 0)) + { + if (c.state != Channel.STATE_OPEN) + { + String detail = c.getReasonClosed(); + + if (detail == null) + detail = "state: " + c.state; + + throw new IOException("This SSH2 channel is not open (" + detail + ")"); + } + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + wasInterrupted = true; + } + } + + if (c.failedCounter != 0) + { + throw new IOException("The server denied the request."); + } + } + } + finally + { + if (wasInterrupted) + Thread.currentThread().interrupt(); + } + } + + public void registerX11Cookie(String hexFakeCookie, X11ServerData data) + { + synchronized (x11_magic_cookies) + { + x11_magic_cookies.put(hexFakeCookie, data); + } + } + + public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) + { + if (hexFakeCookie == null) + throw new IllegalStateException("hexFakeCookie may not be null"); + + synchronized (x11_magic_cookies) + { + x11_magic_cookies.remove(hexFakeCookie); + } + + if (killChannels == false) + return; + + log.debug("Closing all X11 channels for the given fake cookie"); + + List channel_copy = new Vector(); + + synchronized (channels) + { + channel_copy.addAll(channels); + } + + for (Channel c : channel_copy) + { + synchronized (c) + { + if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) + continue; + } + + try + { + closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); + } + catch (IOException ignored) + { + } + } + } + + public X11ServerData checkX11Cookie(String hexFakeCookie) + { + synchronized (x11_magic_cookies) + { + if (hexFakeCookie != null) + return x11_magic_cookies.get(hexFakeCookie); + } + return null; + } + + public void closeAllChannels() + { + log.debug("Closing all channels"); + + List channel_copy = new Vector(); + + synchronized (channels) + { + channel_copy.addAll(channels); + } + + for (Channel c : channel_copy) + { + try + { + closeChannel(c, "Closing all channels", true); + } + catch (IOException ignored) + { + } + } + } + + public void closeChannel(Channel c, String reason, boolean force) throws IOException + { + byte msg[] = new byte[5]; + + synchronized (c) + { + if (force) + { + c.state = Channel.STATE_CLOSED; + c.EOF = true; + } + + c.setReasonClosed(reason); + + msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + + c.notifyAll(); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(msg); + c.closeMessageSent = true; + } + + log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); + } + + public void sendEOF(Channel c) throws IOException + { + byte[] msg = new byte[5]; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + return; + + msg[0] = Packets.SSH_MSG_CHANNEL_EOF; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(msg); + } + + + log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); + } + + public void sendOpenConfirmation(Channel c) throws IOException + { + PacketChannelOpenConfirmation pcoc = null; + + synchronized (c) + { + if (c.state != Channel.STATE_OPENING) + return; + + c.state = Channel.STATE_OPEN; + + pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(pcoc.getPayload()); + } + } + + public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException + { + boolean wasInterrupted = false; + + try + { + while (len > 0) + { + int thislen = 0; + byte[] msg; + + synchronized (c) + { + while (true) + { + if (c.state == Channel.STATE_CLOSED) + throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + if (c.state != Channel.STATE_OPEN) + throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")"); + + if (c.remoteWindow != 0) + break; + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + wasInterrupted = true; + } + } + + /* len > 0, no sign extension can happen when comparing */ + + thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; + + int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); + + /* The worst case scenario =) a true bottleneck */ + + if (estimatedMaxDataLen <= 0) + { + estimatedMaxDataLen = 1; + } + + if (thislen > estimatedMaxDataLen) + thislen = estimatedMaxDataLen; + + c.remoteWindow -= thislen; + + msg = new byte[1 + 8 + thislen]; + + msg[0] = Packets.SSH_MSG_CHANNEL_DATA; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + msg[5] = (byte) (thislen >> 24); + msg[6] = (byte) (thislen >> 16); + msg[7] = (byte) (thislen >> 8); + msg[8] = (byte) (thislen); + + System.arraycopy(buffer, pos, msg, 9, thislen); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + tm.sendMessage(msg); + } + + pos += thislen; + len -= thislen; + } + } + finally + { + if (wasInterrupted) + Thread.currentThread().interrupt(); + } + } + + public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) + throws IOException + { + RemoteForwardingData rfd = new RemoteForwardingData(); + + rfd.bindAddress = bindAddress; + rfd.bindPort = bindPort; + rfd.targetAddress = targetAddress; + rfd.targetPort = targetPort; + + synchronized (remoteForwardings) + { + Integer key = new Integer(bindPort); + + if (remoteForwardings.get(key) != null) + { + throw new IOException("There is already a forwarding for remote port " + bindPort); + } + + remoteForwardings.put(key, rfd); + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); + tm.sendMessage(pgf.getPayload()); + + log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); + + try + { + waitForGlobalSuccessOrFailure(); + } + catch (IOException e) + { + synchronized (remoteForwardings) + { + remoteForwardings.remove(rfd); + } + throw e; + } + + return bindPort; + } - public void requestCancelGlobalForward(int bindPort) throws IOException - { - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) - { - rfd = remoteForwardings.get(new Integer(bindPort)); - - if (rfd == null) - throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, - rfd.bindPort); - tm.sendMessage(pgcf.getPayload()); - - log.debug("Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); - - waitForGlobalSuccessOrFailure(); - - /* Only now we are sure that no more forwarded connections will arrive */ - - synchronized (remoteForwardings) - { - remoteForwardings.remove(rfd); - } - } - - public void registerThread(IChannelWorkerThread thr) throws IOException - { - synchronized (listenerThreads) - { - if (listenerThreadsAllowed == false) - throw new IOException("Too late, this connection is closed."); - listenerThreads.add(thr); - } - } - - public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, - int originator_port) throws IOException - { - Channel c = new Channel(this); - - synchronized (c) - { - c.localID = addChannel(c); - // end of synchronized block forces writing out to main memory - } - - PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, - c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); - - tm.sendMessage(dtc.getPayload()); - - waitUntilChannelOpen(c); - - return c; - } - - public Channel openSessionChannel() throws IOException - { - Channel c = new Channel(this); - - synchronized (c) - { - c.localID = addChannel(c); - // end of synchronized block forces the writing out to main memory - } - - log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); - - PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); - tm.sendMessage(smo.getPayload()); - - waitUntilChannelOpen(c); - - return c; - } - - public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, - int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException - { - PacketSessionPtyRequest spr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - - spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, - term_width_pixels, term_height_pixels, terminal_modes); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(spr.getPayload()); - } - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("PTY request failed").initCause(e); - } - } - - public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, - String x11AuthenticationCookie, int x11ScreenNumber) throws IOException - { - PacketSessionX11Request psr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - - psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, - x11AuthenticationCookie, x11ScreenNumber); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(psr.getPayload()); - } - - log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The X11 request failed.").initCause(e); - } - } - - public void requestSubSystem(Channel c, String subSystemName) throws IOException - { - PacketSessionSubsystemRequest ssr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - - ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(ssr.getPayload()); - } - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The subsystem request failed.").initCause(e); - } - } - - public void requestExecCommand(Channel c, String cmd) throws IOException - { - this.requestExecCommand(c, cmd, null); - } - - /** - * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings - */ - public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException - { - PacketSessionExecCommand sm; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionExecCommand(c.remoteID, true, cmd); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(sm.getPayload(charsetName)); - } - - log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The execute request failed.").initCause(e); - } - } - - public void requestShell(Channel c) throws IOException - { - PacketSessionStartShell sm; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionStartShell(c.remoteID, true); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(sm.getPayload()); - } - - try - { - waitForChannelSuccessOrFailure(c); - } - catch (IOException e) - { - throw (IOException) new IOException("The shell request failed.").initCause(e); - } - } - - public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException - { - if (msglen <= 13) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); - - if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); - - if (len != (msglen - 13)) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) - + ", got " + len + ")"); - - log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) - { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" - + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - - System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); - c.stderrWritepos += len; - - c.notifyAll(); - } - } - - /** - * Wait until for a condition. - * - * @param c Channel - * @param timeout in ms, 0 means no timeout. - * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled) - * @return all current events - */ - public int waitForCondition(Channel c, long timeout, int condition_mask) - { - boolean wasInterrupted = false; - - try - { - long end_time = 0; - boolean end_time_set = false; - - synchronized (c) - { - while (true) - { - int current_cond = 0; - - int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - int stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if (stdoutAvail > 0) - current_cond = current_cond | ChannelCondition.STDOUT_DATA; - - if (stderrAvail > 0) - current_cond = current_cond | ChannelCondition.STDERR_DATA; - - if (c.EOF) - current_cond = current_cond | ChannelCondition.EOF; - - if (c.getExitStatus() != null) - current_cond = current_cond | ChannelCondition.EXIT_STATUS; - - if (c.getExitSignal() != null) - current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; - - if (c.state == Channel.STATE_CLOSED) - return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; - - if ((current_cond & condition_mask) != 0) - return current_cond; - - if (timeout > 0) - { - if (!end_time_set) - { - end_time = System.currentTimeMillis() + timeout; - end_time_set = true; - } - else - { - timeout = end_time - System.currentTimeMillis(); - - if (timeout <= 0) - return current_cond | ChannelCondition.TIMEOUT; - } - } - - try - { - if (timeout > 0) - c.wait(timeout); - else - c.wait(); - } - catch (InterruptedException e) - { - wasInterrupted = true; - } - } - } - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } - } - - public int getAvailable(Channel c, boolean extended) throws IOException - { - synchronized (c) - { - int avail; - - if (extended) - avail = c.stderrWritepos - c.stderrReadpos; - else - avail = c.stdoutWritepos - c.stdoutReadpos; - - return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); - } - } - - public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException - { - boolean wasInterrupted = false; - - try - { - int copylen = 0; - int increment = 0; - int remoteID = 0; - int localID = 0; - - synchronized (c) - { - int stdoutAvail = 0; - int stderrAvail = 0; - - while (true) - { - /* - * Data available? We have to return remaining data even if the - * channel is already closed. - */ - - stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if ((!extended) && (stdoutAvail != 0)) - break; - - if ((extended) && (stderrAvail != 0)) - break; - - /* Do not wait if more data will never arrive (EOF or CLOSED) */ - - if ((c.EOF) || (c.state != Channel.STATE_OPEN)) - return -1; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - wasInterrupted = true; - } - } - - /* OK, there is some data. Return it. */ - - if (!extended) - { - copylen = (stdoutAvail > len) ? len : stdoutAvail; - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); - c.stdoutReadpos += copylen; - - if (c.stdoutReadpos != c.stdoutWritepos) - - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - - c.stdoutReadpos); - - c.stdoutWritepos -= c.stdoutReadpos; - c.stdoutReadpos = 0; - } - else - { - copylen = (stderrAvail > len) ? len : stderrAvail; - System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); - c.stderrReadpos += copylen; - - if (c.stderrReadpos != c.stderrWritepos) - - System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - - c.stderrReadpos); - - c.stderrWritepos -= c.stderrReadpos; - c.stderrReadpos = 0; - } - - if (c.state != Channel.STATE_OPEN) - return copylen; - - if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) - { - int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, - Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); - - increment = minFreeSpace - c.localWindow; - c.localWindow = minFreeSpace; - } - - remoteID = c.remoteID; /* read while holding the lock */ - localID = c.localID; /* read while holding the lock */ - } + public void requestCancelGlobalForward(int bindPort) throws IOException + { + RemoteForwardingData rfd = null; + + synchronized (remoteForwardings) + { + rfd = remoteForwardings.get(new Integer(bindPort)); + + if (rfd == null) + throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, + rfd.bindPort); + tm.sendMessage(pgcf.getPayload()); + + log.debug("Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); + + waitForGlobalSuccessOrFailure(); + + /* Only now we are sure that no more forwarded connections will arrive */ + + synchronized (remoteForwardings) + { + remoteForwardings.remove(rfd); + } + } + + public void registerThread(IChannelWorkerThread thr) throws IOException + { + synchronized (listenerThreads) + { + if (listenerThreadsAllowed == false) + throw new IOException("Too late, this connection is closed."); + listenerThreads.add(thr); + } + } + + public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) throws IOException + { + Channel c = new Channel(this); + + synchronized (c) + { + c.localID = addChannel(c); + // end of synchronized block forces writing out to main memory + } + + PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, + c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); + + tm.sendMessage(dtc.getPayload()); + + waitUntilChannelOpen(c); + + return c; + } + + public Channel openSessionChannel() throws IOException + { + Channel c = new Channel(this); + + synchronized (c) + { + c.localID = addChannel(c); + // end of synchronized block forces the writing out to main memory + } + + log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); + + PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); + tm.sendMessage(smo.getPayload()); + + waitUntilChannelOpen(c); + + return c; + } + + public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException + { + PacketSessionPtyRequest spr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); + + spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels, terminal_modes); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(spr.getPayload()); + } + + try + { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) + { + throw (IOException) new IOException("PTY request failed").initCause(e); + } + } + + public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, + String x11AuthenticationCookie, int x11ScreenNumber) throws IOException + { + PacketSessionX11Request psr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); + + psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, + x11AuthenticationCookie, x11ScreenNumber); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(psr.getPayload()); + } + + log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); + + try + { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) + { + throw (IOException) new IOException("The X11 request failed.").initCause(e); + } + } + + public void requestSubSystem(Channel c, String subSystemName) throws IOException + { + PacketSessionSubsystemRequest ssr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); + + ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(ssr.getPayload()); + } + + try + { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) + { + throw (IOException) new IOException("The subsystem request failed.").initCause(e); + } + } + + public void requestExecCommand(Channel c, String cmd) throws IOException + { + this.requestExecCommand(c, cmd, null); + } + + /** + * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings + */ + public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException + { + PacketSessionExecCommand sm; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); + + sm = new PacketSessionExecCommand(c.remoteID, true, cmd); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(sm.getPayload(charsetName)); + } + + log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); + + try + { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) + { + throw (IOException) new IOException("The execute request failed.").initCause(e); + } + } + + public void requestShell(Channel c) throws IOException + { + PacketSessionStartShell sm; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); + + sm = new PacketSessionStartShell(c.remoteID, true); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(sm.getPayload()); + } + + try + { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) + { + throw (IOException) new IOException("The shell request failed.").initCause(e); + } + } + + public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException + { + if (msglen <= 13) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); + + if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); + + if (len != (msglen - 13)) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) + + ", got " + len + ")"); + + log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) + { + if (c.state == Channel.STATE_CLOSED) + return; // ignore + + if (c.state != Channel.STATE_OPEN) + throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + + c.state + ")"); + + if (c.localWindow < len) + throw new IOException("Remote sent too much data, does not fit into window."); + + c.localWindow -= len; + + System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); + c.stderrWritepos += len; + + c.notifyAll(); + } + } + + /** + * Wait until for a condition. + * + * @param c Channel + * @param timeout in ms, 0 means no timeout. + * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled) + * @return all current events + */ + public int waitForCondition(Channel c, long timeout, int condition_mask) + { + boolean wasInterrupted = false; + + try + { + long end_time = 0; + boolean end_time_set = false; + + synchronized (c) + { + while (true) + { + int current_cond = 0; + + int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + int stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if (stdoutAvail > 0) + current_cond = current_cond | ChannelCondition.STDOUT_DATA; + + if (stderrAvail > 0) + current_cond = current_cond | ChannelCondition.STDERR_DATA; + + if (c.EOF) + current_cond = current_cond | ChannelCondition.EOF; + + if (c.getExitStatus() != null) + current_cond = current_cond | ChannelCondition.EXIT_STATUS; + + if (c.getExitSignal() != null) + current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; + + if (c.state == Channel.STATE_CLOSED) + return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; + + if ((current_cond & condition_mask) != 0) + return current_cond; + + if (timeout > 0) + { + if (!end_time_set) + { + end_time = System.currentTimeMillis() + timeout; + end_time_set = true; + } + else + { + timeout = end_time - System.currentTimeMillis(); + + if (timeout <= 0) + return current_cond | ChannelCondition.TIMEOUT; + } + } + + try + { + if (timeout > 0) + c.wait(timeout); + else + c.wait(); + } + catch (InterruptedException e) + { + wasInterrupted = true; + } + } + } + } + finally + { + if (wasInterrupted) + Thread.currentThread().interrupt(); + } + } + + public int getAvailable(Channel c, boolean extended) throws IOException + { + synchronized (c) + { + int avail; + + if (extended) + avail = c.stderrWritepos - c.stderrReadpos; + else + avail = c.stdoutWritepos - c.stdoutReadpos; + + return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); + } + } + + public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException + { + boolean wasInterrupted = false; + + try + { + int copylen = 0; + int increment = 0; + int remoteID = 0; + int localID = 0; + + synchronized (c) + { + int stdoutAvail = 0; + int stderrAvail = 0; + + while (true) + { + /* + * Data available? We have to return remaining data even if the + * channel is already closed. + */ + + stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if ((!extended) && (stdoutAvail != 0)) + break; + + if ((extended) && (stderrAvail != 0)) + break; + + /* Do not wait if more data will never arrive (EOF or CLOSED) */ + + if ((c.EOF) || (c.state != Channel.STATE_OPEN)) + return -1; + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + wasInterrupted = true; + } + } + + /* OK, there is some data. Return it. */ + + if (!extended) + { + copylen = (stdoutAvail > len) ? len : stdoutAvail; + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); + c.stdoutReadpos += copylen; + + if (c.stdoutReadpos != c.stdoutWritepos) + + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos + - c.stdoutReadpos); + + c.stdoutWritepos -= c.stdoutReadpos; + c.stdoutReadpos = 0; + } + else + { + copylen = (stderrAvail > len) ? len : stderrAvail; + System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); + c.stderrReadpos += copylen; + + if (c.stderrReadpos != c.stderrWritepos) + + System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos + - c.stderrReadpos); + + c.stderrWritepos -= c.stderrReadpos; + c.stderrReadpos = 0; + } + + if (c.state != Channel.STATE_OPEN) + return copylen; + + if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) + { + int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, + Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); + + increment = minFreeSpace - c.localWindow; + c.localWindow = minFreeSpace; + } + + remoteID = c.remoteID; /* read while holding the lock */ + localID = c.localID; /* read while holding the lock */ + } - /* - * If a consumer reads stdout and stdin in parallel, we may end up with - * sending two msgWindowAdjust messages. Luckily, it - * does not matter in which order they arrive at the server. - */ + /* + * If a consumer reads stdout and stdin in parallel, we may end up with + * sending two msgWindowAdjust messages. Luckily, it + * does not matter in which order they arrive at the server. + */ - if (increment > 0) - { - log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); + if (increment > 0) + { + log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); - synchronized (c.channelSendLock) - { - byte[] msg = c.msgWindowAdjust; + synchronized (c.channelSendLock) + { + byte[] msg = c.msgWindowAdjust; - msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; - msg[1] = (byte) (remoteID >> 24); - msg[2] = (byte) (remoteID >> 16); - msg[3] = (byte) (remoteID >> 8); - msg[4] = (byte) (remoteID); - msg[5] = (byte) (increment >> 24); - msg[6] = (byte) (increment >> 16); - msg[7] = (byte) (increment >> 8); - msg[8] = (byte) (increment); + msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; + msg[1] = (byte) (remoteID >> 24); + msg[2] = (byte) (remoteID >> 16); + msg[3] = (byte) (remoteID >> 8); + msg[4] = (byte) (remoteID); + msg[5] = (byte) (increment >> 24); + msg[6] = (byte) (increment >> 16); + msg[7] = (byte) (increment >> 8); + msg[8] = (byte) (increment); - if (c.closeMessageSent == false) - tm.sendMessage(msg); - } - } + if (c.closeMessageSent == false) + tm.sendMessage(msg); + } + } - return copylen; - } - finally - { - if (wasInterrupted) - Thread.currentThread().interrupt(); - } + return copylen; + } + finally + { + if (wasInterrupted) + Thread.currentThread().interrupt(); + } - } + } - public void msgChannelData(byte[] msg, int msglen) throws IOException - { - if (msglen <= 9) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); + public void msgChannelData(byte[] msg, int msglen) throws IOException + { + if (msglen <= 9) + throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - Channel c = getChannel(id); + Channel c = getChannel(id); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); - if (len != (msglen - 9)) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " - + len + ")"); + if (len != (msglen - 9)) + throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " + + len + ")"); - log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); + log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); - synchronized (c) - { - if (c.state == Channel.STATE_CLOSED) - return; // ignore + synchronized (c) + { + if (c.state == Channel.STATE_CLOSED) + return; // ignore - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); + if (c.state != Channel.STATE_OPEN) + throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); + if (c.localWindow < len) + throw new IOException("Remote sent too much data, does not fit into window."); - c.localWindow -= len; + c.localWindow -= len; - System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); - c.stdoutWritepos += len; + System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); + c.stdoutWritepos += len; - c.notifyAll(); - } - } + c.notifyAll(); + } + } - public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException - { - if (msglen != 9) - throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); + public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException + { + if (msglen != 9) + throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - Channel c = getChannel(id); + Channel c = getChannel(id); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); - synchronized (c) - { - final long huge = 0xFFFFffffL; /* 2^32 - 1 */ + synchronized (c) + { + final long huge = 0xFFFFffffL; /* 2^32 - 1 */ - c.remoteWindow += (windowChange & huge); /* avoid sign extension */ + c.remoteWindow += (windowChange & huge); /* avoid sign extension */ - /* TODO - is this a good heuristic? */ + /* TODO - is this a good heuristic? */ - if ((c.remoteWindow > huge)) - c.remoteWindow = huge; + if ((c.remoteWindow > huge)) + c.remoteWindow = huge; - c.notifyAll(); - } + c.notifyAll(); + } - log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); - } + log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); + } - public void msgChannelOpen(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); + public void msgChannelOpen(byte[] msg, int msglen) throws IOException + { + TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - String channelType = tr.readString(); - int remoteID = tr.readUINT32(); /* sender channel */ - int remoteWindow = tr.readUINT32(); /* initial window size */ - int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ + tr.readByte(); // skip packet type + String channelType = tr.readString(); + int remoteID = tr.readUINT32(); /* sender channel */ + int remoteWindow = tr.readUINT32(); /* initial window size */ + int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ - if ("x11".equals(channelType)) - { - synchronized (x11_magic_cookies) - { - /* If we did not request X11 forwarding, then simply ignore this bogus request. */ + if ("x11".equals(channelType)) + { + synchronized (x11_magic_cookies) + { + /* If we did not request X11 forwarding, then simply ignore this bogus request. */ - if (x11_magic_cookies.size() == 0) - { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); + if (x11_magic_cookies.size() == 0) + { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); - tm.sendAsynchronousMessage(pcof.getPayload()); + tm.sendAsynchronousMessage(pcof.getPayload()); - log.warning("Unexpected X11 request, denying it!"); + log.warning("Unexpected X11 request, denying it!"); - return; - } - } + return; + } + } - String remoteOriginatorAddress = tr.readString(); - int remoteOriginatorPort = tr.readUINT32(); + String remoteOriginatorAddress = tr.readString(); + int remoteOriginatorPort = tr.readUINT32(); - Channel c = new Channel(this); + Channel c = new Channel(this); - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } - /* - * The open confirmation message will be sent from another thread - */ + /* + * The open confirmation message will be sent from another thread + */ - RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); - rxat.setDaemon(true); - rxat.start(); + RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); + rxat.setDaemon(true); + rxat.start(); - return; - } + return; + } - if ("forwarded-tcpip".equals(channelType)) - { - String remoteConnectedAddress = tr.readString(); /* address that was connected */ - int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ - String remoteOriginatorAddress = tr.readString(); /* originator IP address */ - int remoteOriginatorPort = tr.readUINT32(); /* originator port */ + if ("forwarded-tcpip".equals(channelType)) + { + String remoteConnectedAddress = tr.readString(); /* address that was connected */ + int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ + String remoteOriginatorAddress = tr.readString(); /* originator IP address */ + int remoteOriginatorPort = tr.readUINT32(); /* originator port */ - RemoteForwardingData rfd = null; + RemoteForwardingData rfd = null; - synchronized (remoteForwardings) - { - rfd = remoteForwardings.get(new Integer(remoteConnectedPort)); - } + synchronized (remoteForwardings) + { + rfd = remoteForwardings.get(new Integer(remoteConnectedPort)); + } - if (rfd == null) - { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, - "No thanks, unknown port in forwarded-tcpip request", ""); + if (rfd == null) + { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "No thanks, unknown port in forwarded-tcpip request", ""); - /* Always try to be polite. */ + /* Always try to be polite. */ - tm.sendAsynchronousMessage(pcof.getPayload()); + tm.sendAsynchronousMessage(pcof.getPayload()); - log.debug("Unexpected forwarded-tcpip request, denying it!"); + log.debug("Unexpected forwarded-tcpip request, denying it!"); - return; - } + return; + } - Channel c = new Channel(this); + Channel c = new Channel(this); - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } - /* - * The open confirmation message will be sent from another thread. - */ + /* + * The open confirmation message will be sent from another thread. + */ - RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, - remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); - - rat.setDaemon(true); - rat.start(); - - return; - } - - if ((server_state != null) && ("session".equals(channelType))) - { - ServerConnectionCallback cb = null; - - synchronized (server_state) - { - cb = server_state.cb_conn; - } - - if (cb == null) - { - tm.sendAsynchronousMessage(new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, - "Sessions are currently not enabled", "en").getPayload()); - - return; - } - - final Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - c.state = Channel.STATE_OPEN; - c.ss = new ServerSessionImpl(c); - } - - PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, - c.localWindow, c.localMaxPacketSize); - - tm.sendAsynchronousMessage(pcoc.getPayload()); - - c.ss.sscb = cb.acceptSession(c.ss); + RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, + remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); + + rat.setDaemon(true); + rat.start(); + + return; + } + + if ((server_state != null) && ("session".equals(channelType))) + { + ServerConnectionCallback cb = null; + + synchronized (server_state) + { + cb = server_state.cb_conn; + } + + if (cb == null) + { + tm.sendAsynchronousMessage(new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "Sessions are currently not enabled", "en").getPayload()); + + return; + } + + final Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + c.state = Channel.STATE_OPEN; + c.ss = new ServerSessionImpl(c); + } + + PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, + c.localWindow, c.localMaxPacketSize); + + tm.sendAsynchronousMessage(pcoc.getPayload()); + + c.ss.sscb = cb.acceptSession(c.ss); - return; - } - - /* Tell the server that we have no idea what it is talking about */ - - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, - "Unknown channel type", ""); - - tm.sendAsynchronousMessage(pcof.getPayload()); - - - log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); - } - - /* Starts the given runnable in a foreground (non-daemon) thread */ - private void runAsync(Runnable r) - { - Thread t = new Thread(r); - t.start(); - } - - public void msgChannelRequest(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - int id = tr.readUINT32(); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); - - ServerSessionImpl server_session = null; - - if (server_state != null) - { - synchronized (c) - { - server_session = c.ss; - } - } - - String type = tr.readString("US-ASCII"); - boolean wantReply = tr.readBoolean(); - - log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); - - if (type.equals("exit-status")) - { - if (wantReply != false) - throw new IOException( - "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); - - int exit_status = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) - { - c.exit_status = new Integer(exit_status); - c.notifyAll(); - } - - log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); - - return; - } - - if ((server_state == null) && (type.equals("exit-signal"))) - { - if (wantReply != false) - throw new IOException( - "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); - - String signame = tr.readString("US-ASCII"); - tr.readBoolean(); - tr.readString(); - tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) - { - c.exit_signal = signame; - c.notifyAll(); - } - - log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); - - return; - } - - if ((server_session != null) && (type.equals("pty-req"))) - { - PtySettings pty = new PtySettings(); - - pty.term = tr.readString(); - pty.term_width_characters = tr.readUINT32(); - pty.term_height_characters = tr.readUINT32(); - pty.term_width_pixels = tr.readUINT32(); - pty.term_height_pixels = tr.readUINT32(); - pty.terminal_modes = tr.readByteString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestPtyReq(server_session, pty); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - if ((server_session != null) && (type.equals("shell"))) - { - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestShell(server_session); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - if ((server_session != null) && (type.equals("exec"))) - { - String command = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - Runnable run_after_sending_success = null; - ServerSessionCallback sscb = server_session.getServerSessionCallback(); - - if (sscb != null) - run_after_sending_success = sscb.requestExec(server_session, command); - - if (wantReply) - { - if (run_after_sending_success != null) - { - tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); - } - else - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - } - - if (run_after_sending_success != null) - { - runAsync(run_after_sending_success); - } - - return; - } - - /* We simply ignore unknown channel requests, however, if the server wants a reply, - * then we signal that we have no idea what it is about. - */ - - if (wantReply) - { - tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); - } - - log.debug("Channel request '" + type + "' is not known, ignoring it"); - } - - public void msgChannelEOF(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); - - synchronized (c) - { - c.EOF = true; - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); - } - - public void msgChannelClose(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); - - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("Close requested by remote"); - c.closeMessageRecv = true; - - removeChannel(c.localID); - - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); - } - - public void msgChannelSuccess(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); - - synchronized (c) - { - c.successCounter++; - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); - } - - public void msgChannelFailure(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); - - synchronized (c) - { - c.failedCounter++; - c.notifyAll(); - } + return; + } + + /* Tell the server that we have no idea what it is talking about */ + + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + "Unknown channel type", ""); + + tm.sendAsynchronousMessage(pcof.getPayload()); + + + log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); + } + + /* Starts the given runnable in a foreground (non-daemon) thread */ + private void runAsync(Runnable r) + { + Thread t = new Thread(r); + t.start(); + } + + public void msgChannelRequest(byte[] msg, int msglen) throws IOException + { + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + int id = tr.readUINT32(); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); + + ServerSessionImpl server_session = null; + + if (server_state != null) + { + synchronized (c) + { + server_session = c.ss; + } + } + + String type = tr.readString("US-ASCII"); + boolean wantReply = tr.readBoolean(); + + log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); + + if (type.equals("exit-status")) + { + if (wantReply != false) + throw new IOException( + "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); + + int exit_status = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + synchronized (c) + { + c.exit_status = new Integer(exit_status); + c.notifyAll(); + } + + log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); + + return; + } + + if ((server_state == null) && (type.equals("exit-signal"))) + { + if (wantReply != false) + throw new IOException( + "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); + + String signame = tr.readString("US-ASCII"); + tr.readBoolean(); + tr.readString(); + tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + synchronized (c) + { + c.exit_signal = signame; + c.notifyAll(); + } + + log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); + + return; + } + + if ((server_session != null) && (type.equals("pty-req"))) + { + PtySettings pty = new PtySettings(); + + pty.term = tr.readString(); + pty.term_width_characters = tr.readUINT32(); + pty.term_height_characters = tr.readUINT32(); + pty.term_width_pixels = tr.readUINT32(); + pty.term_height_pixels = tr.readUINT32(); + pty.terminal_modes = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + Runnable run_after_sending_success = null; + + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) + run_after_sending_success = sscb.requestPtyReq(server_session, pty); + + if (wantReply) + { + if (run_after_sending_success != null) + { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else + { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) + { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("subsystem"))) + { + String command = tr.readString(); + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) + run_after_sending_success = sscb.requestSubsystem(server_session, command); + + if (wantReply) + { + if (run_after_sending_success != null) + { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else + { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) + { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("shell"))) + { + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) + run_after_sending_success = sscb.requestShell(server_session); + + if (wantReply) + { + if (run_after_sending_success != null) + { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else + { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) + { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("exec"))) + { + String command = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) + run_after_sending_success = sscb.requestExec(server_session, command); + + if (wantReply) + { + if (run_after_sending_success != null) + { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else + { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) + { + runAsync(run_after_sending_success); + } + + return; + } + + /* We simply ignore unknown channel requests, however, if the server wants a reply, + * then we signal that we have no idea what it is about. + */ + + if (wantReply) + { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + + log.debug("Channel request '" + type + "' is not known, ignoring it"); + } + + public void msgChannelEOF(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); + + synchronized (c) + { + c.EOF = true; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); + } + + public void msgChannelClose(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); + + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("Close requested by remote"); + c.closeMessageRecv = true; + + removeChannel(c.localID); + + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); + } + + public void msgChannelSuccess(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); + + synchronized (c) + { + c.successCounter++; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); + } + + public void msgChannelFailure(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); + + synchronized (c) + { + c.failedCounter++; + c.notifyAll(); + } - log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); - } + log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); + } - public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException - { - PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); + public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException + { + PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); - Channel c = getChannel(sm.recipientChannelID); + Channel c = getChannel(sm.recipientChannelID); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " - + sm.recipientChannelID); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + + sm.recipientChannelID); - synchronized (c) - { - if (c.state != Channel.STATE_OPENING) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " - + sm.recipientChannelID); + synchronized (c) + { + if (c.state != Channel.STATE_OPENING) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + + sm.recipientChannelID); - c.remoteID = sm.senderChannelID; - c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = sm.maxPacketSize; - c.state = Channel.STATE_OPEN; - c.notifyAll(); - } + c.remoteID = sm.senderChannelID; + c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = sm.maxPacketSize; + c.state = Channel.STATE_OPEN; + c.notifyAll(); + } - log.debug("Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " - + sm.senderChannelID + ")"); - } + log.debug("Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + + sm.senderChannelID + ")"); + } - public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException - { - if (msglen < 5) - throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); - - TypesReader tr = new TypesReader(msg, 0, msglen); + public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException + { + if (msglen < 5) + throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); + + TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - int id = tr.readUINT32(); /* sender channel */ - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); - - int reasonCode = tr.readUINT32(); - String description = tr.readString("UTF-8"); - - String reasonCodeSymbolicName = null; - - switch (reasonCode) - { - case 1: - reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; - break; - case 2: - reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; - break; - case 3: - reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; - break; - case 4: - reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; - break; - default: - reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; - } - - StringBuilder descriptionBuffer = new StringBuilder(); - descriptionBuffer.append(description); - - for (int i = 0; i < descriptionBuffer.length(); i++) - { - char cc = descriptionBuffer.charAt(i); - - if ((cc >= 32) && (cc <= 126)) - continue; - descriptionBuffer.setCharAt(i, '\uFFFD'); - } - - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" - + descriptionBuffer.toString() + "')"); - c.notifyAll(); - } - - log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); - } - - public void msgGlobalRequest(byte[] msg, int msglen) throws IOException - { - /* Currently we do not support any kind of global request */ - - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - String requestName = tr.readString(); - boolean wantReply = tr.readBoolean(); - - if (wantReply) - { - byte[] reply_failure = new byte[1]; - reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; - - tm.sendAsynchronousMessage(reply_failure); - } - - /* We do not clean up the requestName String - that is OK for debug */ - - log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); - } - - public void msgGlobalSuccess() throws IOException - { - synchronized (channels) - { - globalSuccessCounter++; - channels.notifyAll(); - } - - log.debug("Got SSH_MSG_REQUEST_SUCCESS"); - } - - public void msgGlobalFailure() throws IOException - { - synchronized (channels) - { - globalFailedCounter++; - channels.notifyAll(); - } - - log.debug("Got SSH_MSG_REQUEST_FAILURE"); - } - - public void handleMessage(byte[] msg, int msglen) throws IOException - { - if (msg == null) - { - - log.debug("HandleMessage: got shutdown"); - - synchronized (listenerThreads) - { - for (IChannelWorkerThread lat : listenerThreads) - { - lat.stopWorking(); - } - listenerThreadsAllowed = false; - } - - synchronized (channels) - { - shutdown = true; - - for (Channel c : channels) - { - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The connection is being shutdown"); - c.closeMessageRecv = true; /* - * You never know, perhaps - * we are waiting for a - * pending close message - * from the server... - */ - c.notifyAll(); - } - } - - channels.clear(); - channels.notifyAll(); /* Notify global response waiters */ - return; - } - } - - switch (msg[0]) - { - case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: - msgChannelOpenConfirmation(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: - msgChannelWindowAdjust(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_DATA: - msgChannelData(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: - msgChannelExtendedData(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_REQUEST: - msgChannelRequest(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_EOF: - msgChannelEOF(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_OPEN: - msgChannelOpen(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_CLOSE: - msgChannelClose(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_SUCCESS: - msgChannelSuccess(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_FAILURE: - msgChannelFailure(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: - msgChannelOpenFailure(msg, msglen); - break; - case Packets.SSH_MSG_GLOBAL_REQUEST: - msgGlobalRequest(msg, msglen); - break; - case Packets.SSH_MSG_REQUEST_SUCCESS: - msgGlobalSuccess(); - break; - case Packets.SSH_MSG_REQUEST_FAILURE: - msgGlobalFailure(); - break; - default: - throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); - } - } + tr.readByte(); // skip packet type + int id = tr.readUINT32(); /* sender channel */ + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); + + int reasonCode = tr.readUINT32(); + String description = tr.readString("UTF-8"); + + String reasonCodeSymbolicName = null; + + switch (reasonCode) + { + case 1: + reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; + break; + case 2: + reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; + break; + case 3: + reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; + break; + case 4: + reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; + break; + default: + reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; + } + + StringBuilder descriptionBuffer = new StringBuilder(); + descriptionBuffer.append(description); + + for (int i = 0; i < descriptionBuffer.length(); i++) + { + char cc = descriptionBuffer.charAt(i); + + if ((cc >= 32) && (cc <= 126)) + continue; + descriptionBuffer.setCharAt(i, '\uFFFD'); + } + + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" + + descriptionBuffer.toString() + "')"); + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); + } + + public void msgGlobalRequest(byte[] msg, int msglen) throws IOException + { + /* Currently we do not support any kind of global request */ + + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + String requestName = tr.readString(); + boolean wantReply = tr.readBoolean(); + + if (wantReply) + { + byte[] reply_failure = new byte[1]; + reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; + + tm.sendAsynchronousMessage(reply_failure); + } + + /* We do not clean up the requestName String - that is OK for debug */ + + log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); + } + + public void msgGlobalSuccess() throws IOException + { + synchronized (channels) + { + globalSuccessCounter++; + channels.notifyAll(); + } + + log.debug("Got SSH_MSG_REQUEST_SUCCESS"); + } + + public void msgGlobalFailure() throws IOException + { + synchronized (channels) + { + globalFailedCounter++; + channels.notifyAll(); + } + + log.debug("Got SSH_MSG_REQUEST_FAILURE"); + } + + public void handleMessage(byte[] msg, int msglen) throws IOException + { + if (msg == null) + { + + log.debug("HandleMessage: got shutdown"); + + synchronized (listenerThreads) + { + for (IChannelWorkerThread lat : listenerThreads) + { + lat.stopWorking(); + } + listenerThreadsAllowed = false; + } + + synchronized (channels) + { + shutdown = true; + + for (Channel c : channels) + { + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("The connection is being shutdown"); + c.closeMessageRecv = true; /* + * You never know, perhaps + * we are waiting for a + * pending close message + * from the server... + */ + c.notifyAll(); + } + } + + channels.clear(); + channels.notifyAll(); /* Notify global response waiters */ + return; + } + } + + switch (msg[0]) + { + case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + msgChannelOpenConfirmation(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: + msgChannelWindowAdjust(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_DATA: + msgChannelData(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: + msgChannelExtendedData(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_REQUEST: + msgChannelRequest(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_EOF: + msgChannelEOF(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_OPEN: + msgChannelOpen(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_CLOSE: + msgChannelClose(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_SUCCESS: + msgChannelSuccess(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_FAILURE: + msgChannelFailure(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: + msgChannelOpenFailure(msg, msglen); + break; + case Packets.SSH_MSG_GLOBAL_REQUEST: + msgGlobalRequest(msg, msglen); + break; + case Packets.SSH_MSG_REQUEST_SUCCESS: + msgGlobalSuccess(); + break; + case Packets.SSH_MSG_REQUEST_FAILURE: + msgGlobalFailure(); + break; + default: + throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); + } + } }