Merge "Fix NPE in FlowProgrammerService in openflow plugin"
authorAndrew Kim <andrekim@cisco.com>
Sat, 23 Nov 2013 01:44:16 +0000 (01:44 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Sat, 23 Nov 2013 01:44:16 +0000 (01:44 +0000)
18 files changed:
opendaylight/containermanager/api/src/main/java/org/opendaylight/controller/containermanager/ContainerFlowConfig.java
opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml
opendaylight/md-sal/forwardingrules-manager/src/main/java/org/opendaylight/controller/forwardingrulesmanager/consumer/impl/FlowConsumerImpl.java
opendaylight/md-sal/model/model-flow-base/src/main/yang/flow-types.yang
opendaylight/md-sal/model/model-flow-statistics/src/main/yang/group-statistics.yang
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfServiceLegacy.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyFuture.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/DummyRpcResult.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java
opendaylight/md-sal/zeromq-routingtable/implementation/src/main/java/org/opendaylight/controller/sal/connector/remoterpc/api/RoutingTable.java
opendaylight/md-sal/zeromq-routingtable/implementation/src/main/java/org/opendaylight/controller/sal/connector/remoterpc/impl/Activator.java
opendaylight/md-sal/zeromq-routingtable/implementation/src/main/java/org/opendaylight/controller/sal/connector/remoterpc/impl/RoutingTableImpl.java
opendaylight/md-sal/zeromq-routingtable/implementation/src/test/java/org/opendaylight/controller/sal/connector/remoterpc/impl/RoutingTableImplTest.java
opendaylight/md-sal/zeromq-routingtable/integrationtest/src/test/java/org/opendaylight/controller/sal/connector/remoterpc/impl/ZeroMQRoutingTableTestIT.java
third-party/ganymed/src/main/java/ch/ethz/ssh2/channel/ChannelManager.java

index baa2c782538255b6da9dfff772d10406e9eabf47..9740a9259833c0ef9d60ad821eaea1e0ee723aef 100644 (file)
@@ -114,6 +114,17 @@ public class ContainerFlowConfig implements Serializable {
         //this.unidirectional = false;
     }
 
+    public ContainerFlowConfig(String name, String dlVlan, String srcIP, String dstIP, String proto, String srcPort,
+            String dstPort) {
+        this.name = name;
+        this.dlVlan = dlVlan;
+        this.nwSrc = srcIP;
+        this.nwDst = dstIP;
+        this.protocol = proto;
+        this.tpSrc = srcPort;
+        this.tpDst = dstPort;
+    }
+
 
     public ContainerFlowConfig(ContainerFlowConfig containerFlowConfig) {
         this.name = containerFlowConfig.name;
index c61b41c06542a24167fa863474b18198a7c32d3a..3ad0c61d4f66ffb8763354e65bc2d196d88929df 100644 (file)
@@ -62,6 +62,8 @@
   <logger name="org.opendaylight.controller.sal.implementation" level="INFO"/>
   <logger name="org.opendaylight.controller.sal.implementation.internal.Inventory" level="INFO"/>
   <logger name="org.opendaylight.controller.sal.implementation.internal.Topology" level="INFO"/>
+     <!-- zeromq router and zeromq routing table -->
+  <logger name="org.opendaylight.controller.sal.connector.remoterpc" level="INFO" />
   <!-- Functional Modules -->
   <logger name="org.opendaylight.controller.arphandler" level="INFO"/>
   <logger name="org.opendaylight.controller.hosttracker" level="INFO"/>
index 82db78e7b9b25f986cce56b38de5ebba2752ed83..9edb690a015dfb0edb98896e50cfc51602ac6b79 100644 (file)
@@ -71,7 +71,7 @@ public class FlowConsumerImpl implements IForwardingRulesManager {
     private IContainer container;
     private static final String NAMEREGEX = "^[a-zA-Z0-9]+$";
     private static ConcurrentMap<Integer, Flow> staticFlows;
-    private static ConcurrentMap<Integer, Integer> staticFlowsOrdinal;
+    private static ConcurrentMap<Integer, Integer> staticFlowsOrdinal = new ConcurrentHashMap<Integer, Integer>();
     /*
      * 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
-            
+
         };
 
     }
index 4b50c0ee720488dfd47ead3754099fd05f3ecb10..67c6933cc7b3158d5435b2929cfae9bfaa99dad5 100644 (file)
@@ -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;
         }
index 5b565365a5894f9912fde799a5a8f1b56855094b..d29ddc0ddd68bcef17b84cfec9ac92eab6d5cc24 100644 (file)
@@ -54,6 +54,10 @@ module opendaylight-group-statistics {
        rpc get-group-statistics {
                input {
             uses inv:node;
+            leaf group-id{
+               type group-types:group-id;
+            }
+           
         }
         output {
             uses group-types:group-statistics-reply;
index c36a79c5d901093ad3bf79d1a4a354ebcf161209..a22ea623975049cd3bd37c8af3103fecce4317e4 100644 (file)
@@ -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<TransactionStatus> 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<TransactionStatus> 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<TransactionStatus> 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<TransactionStatus> updateOperationalData(@PathParam("identifier") String identifier, CompositeNode payload);
+    public Response updateOperationalData(@PathParam("identifier") String identifier, CompositeNode payload);
 
-    
 }
index 6683fd1835942f2af4bd23d42dc16220f3bb6872..242e7f3150d4b775ce56d957ae62ba947a080eba 100644 (file)
@@ -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<TransactionStatus> 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<TransactionStatus> updateConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload);
+    public Response updateConfigurationDataLegacy(@PathParam("identifier") String identifier, CompositeNode payload);
 
 }
index a41a48287df87f887dd37c12b0fd88f4539f7597..d9ac53589fa5415bcba3e59467576fcbb98d069e 100644 (file)
@@ -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) {
index a32a3479bada1b7a5b63d5c8f39e7c86731dc12f..251b212513c80f617740bb6562f1159f43aa6b24 100644 (file)
@@ -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<RpcResult<TransactionStatus>> {
+    
+    private final boolean cancel;
+    private final boolean isCancelled;
+    private final boolean isDone;
+    private final RpcResult<TransactionStatus> 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<TransactionStatus> get() throws InterruptedException, ExecutionException {
-        return null;
+        return result;
     }
 
     @Override
     public RpcResult<TransactionStatus> 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<TransactionStatus> 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<TransactionStatus> result) {
+            this.result = result;
+            return this;
+        }
+        
+        public Future<RpcResult<TransactionStatus>> 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 (file)
index 0000000..5ab4f99
--- /dev/null
@@ -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<TransactionStatus> {
+    
+    private final boolean isSuccessful;
+    private final TransactionStatus result;
+    private final Collection<RpcError> 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<RpcError> getErrors() {
+        return errors;
+    }
+    
+    public static class Builder {
+        private boolean isSuccessful;
+        private TransactionStatus result;
+        private Collection<RpcError> 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<RpcError> errors) {
+            this.errors = errors;
+            return this;
+        }
+        
+        public RpcResult<TransactionStatus> build() {
+            return new DummyRpcResult(this);
+        }
+        
+    }
+
+}
index 3d06e4a759985f1eb938576b7bac10e9be9b8aae..1d8d7495f9dfabae21bdddb737401032af34724b 100644 (file)
@@ -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<TransactionStatus> rpcResult = DummyRpcResult.builder().result(TransactionStatus.COMMITED).build();
+        Future<RpcResult<TransactionStatus>> 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);
index baf226712ffbb255550948c3c5f36160d4c30262..7b63c5fd9422e5bb3abd0060be1ba57dc9643389 100644 (file)
@@ -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<Module> 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<Module> 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<RpcResult<TransactionStatus>>() {
-            @Override
-            public boolean cancel(boolean mayInterruptIfRunning) {
-                return false;
-            }
-            @Override
-            public boolean isCancelled() {
-                return false;
-            }
-            @Override
-            public boolean isDone() {
-                return false;
-            }
-            @Override
-            public RpcResult<TransactionStatus> get() throws InterruptedException, ExecutionException {
-                return null;
-            }
-            @Override
-            public RpcResult<TransactionStatus> 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("<SimpleNode/>", 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("<SimpleNode>", 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("<SimpleNode/>", 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("<SimpleNode>", 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<String> entity = Entity.entity(xml, MEDIA_TYPE);
+        RpcResult<TransactionStatus> rpcResult = DummyRpcResult.builder().result(TransactionStatus.COMMITED).build();
+        Future<RpcResult<TransactionStatus>> 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<String> entity = Entity.entity(xml, MEDIA_TYPE);
+        RpcResult<TransactionStatus> rpcResult = DummyRpcResult.builder().result(TransactionStatus.FAILED).build();
+        Future<RpcResult<TransactionStatus>> 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
index 97d2a6d02a40a054135b7bf390279fb669208f87..6ec4c2ce01f7ccb675873890a3ff80bfa6102963 100644 (file)
@@ -19,10 +19,12 @@ public interface RoutingTable<I,R> {
    *
    * @param routeId route identifier
    * @param route network address
+   * @throws RoutingTableException for any logical exception
+   * @throws SystemException
    */
-  public void addRoute(I routeId, R route) throws SystemException,  RoutingTableException;
+  public void addRoute(I routeId, R route) throws  RoutingTableException,SystemException;
 
-  /**
+    /**
    * Adds a network address for the route. If the route already exists,
    * it throws <code>DuplicateRouteException</code>.
    * This method would be used when registering a global service.
@@ -31,6 +33,7 @@ public interface RoutingTable<I,R> {
    * @param routeId route identifier
    * @param route network address
    * @throws DuplicateRouteException
+   * @throws RoutingTableException
    */
   public void addGlobalRoute(I routeId, R route) throws  RoutingTableException, SystemException;
 
@@ -50,8 +53,10 @@ public interface RoutingTable<I,R> {
      * Remove the route.
      * This method would be used when registering a global service.
      * @param routeId
+     * @throws RoutingTableException
+     * @throws SystemException
      */
-    public void removeGlobalRoute(I routeId);
+    public void removeGlobalRoute(I routeId) throws RoutingTableException, SystemException;
 
   /**
    * Returns a set of network addresses associated with this route
@@ -69,6 +74,13 @@ public interface RoutingTable<I,R> {
    */
   public R getARoute(I routeId);
 
+    /**
+     *
+     * This will be removed after listeners
+     * have made change on their end to use whiteboard pattern
+     * @deprecated
+     */
+
   public void registerRouteChangeListener(RouteChangeListener listener);
 
   public class DuplicateRouteException extends RoutingTableException {
index 45414437ccb3d3afe6279fd1d428a8dc930d60fc..6e2d280a89f5158358c3d6757c7c1e4e2f72e64e 100644 (file)
@@ -11,6 +11,7 @@ package org.opendaylight.controller.sal.connector.remoterpc.impl;
 import org.apache.felix.dm.Component;
 import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
 import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.sal.connector.remoterpc.api.RouteChangeListener;
 import org.opendaylight.controller.sal.connector.remoterpc.api.RoutingTable;
 import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
 import org.slf4j.Logger;
@@ -72,6 +73,15 @@ public class Activator extends ComponentActivatorAbstractBase {
             c.setInterface(new String[] { RoutingTable.class.getName(),ICacheUpdateAware.class.getName()  }, props);
             logger.debug("configureGlobalInstance adding dependency:", IClusterGlobalServices.class);
 
+
+            // RouteChangeListener services will be none or many so the
+            // dependency is optional
+            c.add(createServiceDependency()
+                    .setService(RouteChangeListener.class)
+                    .setCallbacks("setRouteChangeListener", "unsetRouteChangeListener")
+                    .setRequired(false));
+
+            //dependency is required as it provides us the caching support
             c.add(createServiceDependency().setService(
                     IClusterGlobalServices.class).setCallbacks(
                     "setClusterGlobalServices",
index 558c8a80d32b59aaf092a4505e680c9eabb14190..4e1dfb00588a3e2ae566a744ea1f05eb51ca4bf1 100644 (file)
@@ -10,12 +10,16 @@ package org.opendaylight.controller.sal.connector.remoterpc.impl;
 
 import com.google.common.base.Preconditions;
 import org.apache.felix.dm.Component;
-import org.opendaylight.controller.clustering.services.*;
+import org.opendaylight.controller.clustering.services.CacheConfigException;
+import org.opendaylight.controller.clustering.services.CacheExistException;
+import org.opendaylight.controller.clustering.services.CacheListenerAddException;
+import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
+import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
+import org.opendaylight.controller.clustering.services.IClusterServices;
 import org.opendaylight.controller.sal.connector.remoterpc.api.RouteChangeListener;
 import org.opendaylight.controller.sal.connector.remoterpc.api.RoutingTable;
 import org.opendaylight.controller.sal.connector.remoterpc.api.RoutingTableException;
 import org.opendaylight.controller.sal.connector.remoterpc.api.SystemException;
-import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -23,243 +27,298 @@ import javax.transaction.HeuristicMixedException;
 import javax.transaction.HeuristicRollbackException;
 import javax.transaction.NotSupportedException;
 import javax.transaction.RollbackException;
-import java.util.*;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 
 /**
  * @author: syedbahm
  */
-public class RoutingTableImpl<I, R> implements RoutingTable<I, R>,ICacheUpdateAware<I,R> {
+public class RoutingTableImpl<I, R> implements RoutingTable<I, R>, ICacheUpdateAware<I, R> {
     public static final String ROUTING_TABLE_GLOBAL_CACHE = "routing_table_global_cache";
 
-  private Logger log = LoggerFactory
-            .getLogger(RoutingTableImpl.class);
-
-  private IClusterGlobalServices clusterGlobalServices = null;
-  private RoutingTableImpl routingTableInstance = null;
-  private ConcurrentMap routingTableCache = null;
-  private List<RouteChangeListener>  routeChangeListeners = new ArrayList<RouteChangeListener>();
-  private ServiceRegistration cacheAwareRegistration = null;
-                                                      
- public RoutingTableImpl() {
-  }
-
-  @Override
-  public void addRoute(I routeId, R route) throws RoutingTableException {
-       throw new UnsupportedOperationException(" Not implemented yet!");
-  }
-
-  @Override
-  public void addGlobalRoute(I routeId, R route) throws RoutingTableException, SystemException {
-    Preconditions.checkNotNull(routeId, "addGlobalRoute: routeId cannot be null!");
-    Preconditions.checkNotNull(route, "addGlobalRoute: route cannot be null!");
-    try {
-
-      Set<R> existingRoute = null;
-      // ok does the global route is already registered ?
-      if ((existingRoute = getRoutes(routeId)) == null) {
-
-          if(log.isDebugEnabled()){
-              log.debug("addGlobalRoute: adding  a new route with id"+ routeId + " and value = "+route);
-          }
-        // lets start a transaction
-        clusterGlobalServices.tbegin();
-        Set<R> routes  = new HashSet<R>();
-        routes.add(route);
-        routingTableCache.put(routeId, routes);
-        clusterGlobalServices.tcommit();
-      } else {
-        throw new DuplicateRouteException(" There is already existing route " + existingRoute);
-      }
-
-    } catch (NotSupportedException e) {
-      throw new RoutingTableException("Transaction error - while trying to create route id=" + routeId + "with route" + route, e);
-    } catch (HeuristicRollbackException e) {
-      throw new RoutingTableException("Transaction error - while trying to create route id=" + routeId + "with route" + route, e);
-    } catch (RollbackException e) {
-      throw new RoutingTableException("Transaction error - while trying to create route id=" + routeId + "with route" + route, e);
-    } catch (HeuristicMixedException e) {
-      throw new RoutingTableException("Transaction error - while trying to create route id=" + routeId + "with route" + route, e);
-    } catch (javax.transaction.SystemException e){
-        throw new SystemException ( "System error occurred - while trying to create with value",e);
+    private Logger log = LoggerFactory.getLogger(RoutingTableImpl.class);
+
+    private IClusterGlobalServices clusterGlobalServices = null;
+    private RoutingTableImpl routingTableInstance = null;
+    private ConcurrentMap routingTableCache = null;
+    private Set<RouteChangeListener> routeChangeListeners = Collections
+            .synchronizedSet(new HashSet<RouteChangeListener>());
+
+    public RoutingTableImpl() {
     }
 
-  }
+    @Override
+    public void addRoute(I routeId, R route) throws RoutingTableException {
+        throw new UnsupportedOperationException(" Not implemented yet!");
+    }
 
-  @Override
-  public void removeRoute(I routeId, R route) {
-         throw new UnsupportedOperationException("Not implemented yet!");
-  }
     @Override
-    public void removeGlobalRoute(I routeId) {
-        routingTableCache.remove(routeId);
+    public void addGlobalRoute(I routeId, R route) throws RoutingTableException, SystemException {
+        Preconditions.checkNotNull(routeId, "addGlobalRoute: routeId cannot be null!");
+        Preconditions.checkNotNull(route, "addGlobalRoute: route cannot be null!");
+        try {
+
+            Set<R> existingRoute = null;
+            // ok does the global route is already registered ?
+            if ((existingRoute = getRoutes(routeId)) == null) {
+
+                if (log.isDebugEnabled()) {
+                    log.debug("addGlobalRoute: adding  a new route with id" + routeId + " and value = "
+                            + route);
+                }
+                // lets start a transaction
+                clusterGlobalServices.tbegin();
+                Set<R> routes = new HashSet<R>();
+                routes.add(route);
+                routingTableCache.put(routeId, routes);
+                clusterGlobalServices.tcommit();
+            } else {
+                throw new DuplicateRouteException(" There is already existing route " + existingRoute);
+            }
+
+        } catch (NotSupportedException e) {
+            throw new RoutingTableException("Transaction error - while trying to create route id="
+                    + routeId + "with route" + route, e);
+        } catch (HeuristicRollbackException e) {
+            throw new RoutingTableException("Transaction error - while trying to create route id="
+                    + routeId + "with route" + route, e);
+        } catch (RollbackException e) {
+            throw new RoutingTableException("Transaction error - while trying to create route id="
+                    + routeId + "with route" + route, e);
+        } catch (HeuristicMixedException e) {
+            throw new RoutingTableException("Transaction error - while trying to create route id="
+                    + routeId + "with route" + route, e);
+        } catch (javax.transaction.SystemException e) {
+            throw new SystemException("System error occurred - while trying to create with value", e);
+        }
+
     }
 
-  @Override
-  public Set<R> getRoutes(I routeId) {
+    @Override
+    public void removeRoute(I routeId, R route) {
+        throw new UnsupportedOperationException("Not implemented yet!");
+    }
 
-      //Note: currently works for global routes only wherein there is just single route
-      Preconditions.checkNotNull(routeId, "getARoute: routeId cannot be null!");
-      return (Set<R>) routingTableCache.get(routeId);
-  }
+    @Override
+    public void removeGlobalRoute(I routeId) throws RoutingTableException, SystemException {
+        Preconditions.checkNotNull(routeId, "removeGlobalRoute: routeId cannot be null!");
+        try {
+            if (log.isDebugEnabled()) {
+                log.debug("removeGlobalRoute: removing  a new route with id" + routeId);
+            }
+            // lets start a transaction
+            clusterGlobalServices.tbegin();
+
+            routingTableCache.remove(routeId);
+            clusterGlobalServices.tcommit();
+
+        } catch (NotSupportedException e) {
+            throw new RoutingTableException("Transaction error - while trying to remove route id="
+                    + routeId, e);
+        } catch (HeuristicRollbackException e) {
+            throw new RoutingTableException("Transaction error - while trying to remove route id="
+                    + routeId, e);
+        } catch (RollbackException e) {
+            throw new RoutingTableException("Transaction error - while trying to remove route id="
+                    + routeId, e);
+        } catch (HeuristicMixedException e) {
+            throw new RoutingTableException("Transaction error - while trying to remove route id="
+                    + routeId, e);
+        } catch (javax.transaction.SystemException e) {
+            throw new SystemException("System error occurred - while trying to remove with value", e);
+        }
+    }
 
-  @Override
-  public R getARoute(I routeId) {
-       throw new UnsupportedOperationException("Not implemented yet!");
-  }
+    @Override
+    public Set<R> getRoutes(I routeId) {
 
-  /**
-   * Registers listener for sending any change notification
-   * 
-   * @param listener
-   */
-  @Override
-  public void registerRouteChangeListener(RouteChangeListener listener) {
-      routeChangeListeners.add(listener);
-  }
+        // Note: currently works for global routes only wherein there is just single
+        // route
+        Preconditions.checkNotNull(routeId, "getARoute: routeId cannot be null!");
+        return (Set<R>) routingTableCache.get(routeId);
+    }
 
+    @Override
+    public R getARoute(I routeId) {
+        throw new UnsupportedOperationException("Not implemented yet!");
+    }
 
     /**
-     * Returning the list of route change listeners for Unit testing
-     * Note: the package scope is default
-     * @return   List of registered RouteChangeListener<I,R> listeners
+     * @deprecated doesn't do anything will be removed once listeners used
+     *             whiteboard pattern Registers listener for sending any change
+     *             notification
+     * @param listener
      */
-  List<RouteChangeListener> getRegisteredRouteChangeListeners(){
-      return routeChangeListeners;
-  }
+    @Override
+    public void registerRouteChangeListener(RouteChangeListener listener) {
 
-  public void setClusterGlobalServices(IClusterGlobalServices clusterGlobalServices) {
-    this.clusterGlobalServices = clusterGlobalServices;
-  }
+    }
 
-  public void unsetClusterGlobalServices(IClusterGlobalServices clusterGlobalServices) {
-    if(cacheAwareRegistration != null) {
-        cacheAwareRegistration.unregister();
+    public void setRouteChangeListener(RouteChangeListener rcl) {
+        if(rcl != null){
+            routeChangeListeners.add(rcl);
+        }else{
+            log.warn("setRouteChangeListener called with null listener");
+        }
+    }
+
+    public void unSetRouteChangeListener(RouteChangeListener rcl) {
+        if(rcl != null){
+         routeChangeListeners.remove(rcl);
+        }else{
+            log.warn("unSetRouteChangeListener called with null listener");
+        }
     }
-    this.clusterGlobalServices = null;
-  }
 
     /**
-     * Creates the Routing Table clustered global services cache
-     * @throws CacheExistException  -- cluster global services exception when cache exist
-     * @throws CacheConfigException -- cluster global services exception during cache config
-     * @throws CacheListenerAddException  -- cluster global services exception during adding of listener
+     * Returning the set of route change listeners for Unit testing Note: the
+     * package scope is default
+     *
+     * @return List of registered RouteChangeListener<I,R> listeners
      */
+    Set<RouteChangeListener> getRegisteredRouteChangeListeners() {
+        return routeChangeListeners;
+    }
 
-  void createRoutingTableCache() throws CacheExistException, CacheConfigException, CacheListenerAddException {
-    // TBD: HOW DO WE DECIDE ON PROPERTIES OF THE CACHE i.e. what duration it
-    // should be caching?
-
-    // let us check here if the cache already exists -- if so don't create
-    if (!clusterGlobalServices.existCache(
-        ROUTING_TABLE_GLOBAL_CACHE)) {
+    public void setClusterGlobalServices(IClusterGlobalServices clusterGlobalServices) {
+        this.clusterGlobalServices = clusterGlobalServices;
+    }
 
-        if(log.isDebugEnabled()){
-            log.debug("createRoutingTableCache: creating a new routing table cache "+ROUTING_TABLE_GLOBAL_CACHE );
-        }
-      routingTableCache = clusterGlobalServices.createCache(
-          ROUTING_TABLE_GLOBAL_CACHE, EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
-    } else {
-        if(log.isDebugEnabled()){
-            log.debug("createRoutingTableCache: found existing routing table cache "+ROUTING_TABLE_GLOBAL_CACHE );
+    public void unsetClusterGlobalServices(IClusterGlobalServices clusterGlobalServices) {
+        if((clusterGlobalServices != null ) &&  (this.clusterGlobalServices.equals(clusterGlobalServices))){
+            this.clusterGlobalServices = null;
         }
-      routingTableCache = clusterGlobalServices.getCache(
-          ROUTING_TABLE_GLOBAL_CACHE);
     }
 
-  }
-
-  /**
-   * Function called by the dependency manager when all the required
-   * dependencies are satisfied
-   * 
-   */
-  void init(Component c) {
-    try {
-
-      createRoutingTableCache();
-    } catch (CacheExistException e) {
-      throw new IllegalStateException("could not construct routing table cache");
-    } catch (CacheConfigException e) {
-      throw new IllegalStateException("could not construct routing table cache");
-    } catch (CacheListenerAddException e) {
-        throw new IllegalStateException("could not construct routing table cache");
+    /**
+     * Creates the Routing Table clustered global services cache
+     *
+     * @throws CacheExistException
+     *           -- cluster global services exception when cache exist
+     * @throws CacheConfigException
+     *           -- cluster global services exception during cache config
+     * @throws CacheListenerAddException
+     *           -- cluster global services exception during adding of listener
+     */
+
+    void createRoutingTableCache() throws CacheExistException, CacheConfigException,
+            CacheListenerAddException {
+        // TBD: HOW DO WE DECIDE ON PROPERTIES OF THE CACHE i.e. what duration it
+        // should be caching?
+
+        // let us check here if the cache already exists -- if so don't create
+        if (!clusterGlobalServices.existCache(ROUTING_TABLE_GLOBAL_CACHE)) {
+
+            if (log.isDebugEnabled()) {
+                log.debug("createRoutingTableCache: creating a new routing table cache "
+                        + ROUTING_TABLE_GLOBAL_CACHE);
+            }
+            routingTableCache = clusterGlobalServices.createCache(ROUTING_TABLE_GLOBAL_CACHE,
+                    EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("createRoutingTableCache: found existing routing table cache "
+                        + ROUTING_TABLE_GLOBAL_CACHE);
+            }
+            routingTableCache = clusterGlobalServices.getCache(ROUTING_TABLE_GLOBAL_CACHE);
+        }
+
     }
-  }
 
+    /**
+     * Function called by the dependency manager when all the required
+     * dependencies are satisfied
+     *
+     */
+    void init(Component c) {
+        try {
+
+            createRoutingTableCache();
+        } catch (CacheExistException e) {
+            throw new IllegalStateException("could not construct routing table cache");
+        } catch (CacheConfigException e) {
+            throw new IllegalStateException("could not construct routing table cache");
+        } catch (CacheListenerAddException e) {
+            throw new IllegalStateException("could not construct routing table cache");
+        }
+    }
 
     /**
-     * Get routing table method is useful for unit testing
-     * <note>It has package scope</note>
+     * Get routing table method is useful for unit testing <note>It has package
+     * scope</note>
      */
-    ConcurrentMap getRoutingTableCache(){
+    ConcurrentMap getRoutingTableCache() {
         return this.routingTableCache;
     }
 
+    /**
+     * Invoked when a new entry is available in the cache, the key is only
+     * provided, the value will come as an entryUpdate invocation
+     *
+     * @param key
+     *          Key for the entry just created
+     * @param cacheName
+     *          name of the cache for which update has been received
+     * @param originLocal
+     *          true if the event is generated from this node
+     */
+    @Override
+    public void entryCreated(I key, String cacheName, boolean originLocal) {
+        // TBD: do we require this.
+        if (log.isDebugEnabled()) {
+            log.debug("RoutingTableUpdates: entryCreated  routeId = " + key + " cacheName=" + cacheName);
+        }
+    }
 
-      /**
-       * Invoked when a new entry is available in the cache, the key is
-       * only provided, the value will come as an entryUpdate invocation
-       *
-       * @param key         Key for the entry just created
-       * @param cacheName   name of the cache for which update has been
-       *                    received
-       * @param originLocal true if the event is generated from this
-       *                    node
-       */
-      @Override
-      public void entryCreated(I key, String cacheName, boolean originLocal) {
-          //TBD: do we require this.
-          if(log.isDebugEnabled()){
-              log.debug("RoutingTableUpdates: entryCreated  routeId = "+key
-                      + " cacheName="+cacheName
-                      );
-          }
-      }
-
-      /**
-       * Called anytime a given entry is updated
-       *
-       * @param key         Key for the entry modified
-       * @param new_value   the new value the key will have
-       * @param cacheName   name of the cache for which update has been
-       *                    received
-       * @param originLocal true if the event is generated from this
-       *                    node
-       */
-      @Override
-      public void entryUpdated(I key, R new_value, String cacheName, boolean originLocal) {
-          if(log.isDebugEnabled()){
-              log.debug("RoutingTableUpdates: entryUpdated  routeId = "+key
-                      + ",value = "+ new_value
-                      + " ,cacheName="+cacheName
-                      );
-          }
-          for(RouteChangeListener rcl:routeChangeListeners){
-              rcl.onRouteUpdated(key, new_value);
-          }
-      }
-
-      /**
-       * Called anytime a given key is removed from the
-       * ConcurrentHashMap we are listening to.
-       *
-       * @param key         Key of the entry removed
-       * @param cacheName   name of the cache for which update has been
-       *                    received
-       * @param originLocal true if the event is generated from this
-       *                    node
-       */
-      @Override
-      public void entryDeleted(I key, String cacheName, boolean originLocal) {
-          if(log.isDebugEnabled()){
-              log.debug("RoutingTableUpdates: entryUpdated  routeId = "+key
-                      + " local = "+ originLocal
-                      + " cacheName="+cacheName
-                       );
-          }
-          for(RouteChangeListener rcl:routeChangeListeners){
-              rcl.onRouteDeleted(key);
-          }
-      }
-  }
\ No newline at end of file
+    /**
+     * Called anytime a given entry is updated
+     *
+     * @param key
+     *          Key for the entry modified
+     * @param new_value
+     *          the new value the key will have
+     * @param cacheName
+     *          name of the cache for which update has been received
+     * @param originLocal
+     *          true if the event is generated from this node
+     */
+    @Override
+    public void entryUpdated(I key, R new_value, String cacheName, boolean originLocal) {
+        if (log.isDebugEnabled()) {
+            log.debug("RoutingTableUpdates: entryUpdated  routeId = " + key + ",value = " + new_value
+                    + " ,cacheName=" + cacheName + " originLocal="+originLocal);
+        }
+        if (!originLocal) {
+            for (RouteChangeListener rcl : routeChangeListeners) {
+                rcl.onRouteUpdated(key, new_value);
+            }
+        }
+    }
+
+    /**
+     * Called anytime a given key is removed from the ConcurrentHashMap we are
+     * listening to.
+     *
+     * @param key
+     *          Key of the entry removed
+     * @param cacheName
+     *          name of the cache for which update has been received
+     * @param originLocal
+     *          true if the event is generated from this node
+     */
+    @Override
+    public void entryDeleted(I key, String cacheName, boolean originLocal) {
+        if (log.isDebugEnabled()) {
+            log.debug("RoutingTableUpdates: entryUpdated  routeId = " + key + " local = " + originLocal
+                    + " cacheName=" + cacheName + " originLocal="+originLocal);
+        }
+        if (!originLocal) {
+            for (RouteChangeListener rcl : routeChangeListeners) {
+                rcl.onRouteDeleted(key);
+            }
+        }
+    }
+}
\ No newline at end of file
index 75cc6f5da811a5c073071a71bb0fecf1e256455d..2ef251d9a1f0a642020b2b5d9299970bdf429189 100644 (file)
@@ -103,7 +103,12 @@ public class RoutingTableImplTest {
         Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),0);
         rti.registerRouteChangeListener(new RouteChangeListenerImpl());
 
-        Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),1);
+        Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),0); //old should not work
+        //what about the new approach - using whiteboard pattern
+        rti.setRouteChangeListener(new RouteChangeListenerImpl());
+
+        Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),1); //should not work
+
 
     }
     @Test
index 3b6d398511304f1fc2e3cf23361d977c83d40ebf..a7929e82fce2bce0b9895e2db666afbd26d0f16d 100644 (file)
@@ -21,7 +21,6 @@ import java.io.Serializable;
 import java.net.URI;
 import java.util.Set;
 
-
 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
 import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
 import static org.ops4j.pax.exam.CoreOptions.options;
@@ -41,6 +40,7 @@ public class
     public static final String YANG = "org.opendaylight.yangtools";
     public static final String CONTROLLER = "org.opendaylight.controller";
     public static final String YANGTOOLS = "org.opendaylight.yangtools";
+    RoutingIdentifierImpl rii  = new RoutingIdentifierImpl();
     // get the OSGI bundle context
     @Inject
     private BundleContext bc;
@@ -171,9 +171,6 @@ public class
 
                 mavenBundle(YANGTOOLS + ".thirdparty", "antlr4-runtime-osgi-nohead").versionAsInProject(), //
 
-                mavenBundle(YANG, "concepts").versionAsInProject(),
-                mavenBundle(YANG, "yang-binding").versionAsInProject(), //
-                mavenBundle(YANG, "yang-common").versionAsInProject(), //
                 mavenBundle(YANG+".thirdparty", "xtend-lib-osgi").versionAsInProject(),
                 mavenBundle("com.google.guava", "guava").versionAsInProject(), //
                 mavenBundle("org.javassist", "javassist").versionAsInProject(),
@@ -248,7 +245,7 @@ public class
 
     @Test
   public  void testAddGlobalRoute () throws Exception{
-       RoutingIdentifierImpl rii  = new RoutingIdentifierImpl();
+
        routingTable.addGlobalRoute(rii,"172.27.12.1:5000");
 
        Set<String> routes = routingTable.getRoutes(rii);
@@ -261,6 +258,20 @@ public class
     }
 
 
+    @Test
+    public  void testDeleteGlobalRoute () throws Exception{
+
+        routingTable.removeGlobalRoute(rii);
+
+        Set<String> routes = routingTable.getRoutes(rii);
+
+        Assert.assertNull(routes);
+
+
+    }
+
+
+
    class RoutingIdentifierImpl implements RpcRouter.RouteIdentifier,Serializable {
 
        private final URI namespace = URI.create("http://cisco.com/example");
@@ -281,6 +292,28 @@ public class
        public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier getRoute() {
            return InstanceIdentifier.of(instance);
        }
+
+       @Override
+       public boolean equals(Object o) {
+           if (this == o) return true;
+           if (o == null || getClass() != o.getClass()) return false;
+
+           RoutingIdentifierImpl that = (RoutingIdentifierImpl) o;
+
+           if (QNAME != null ? !QNAME.equals(that.QNAME) : that.QNAME != null) return false;
+           if (instance != null ? !instance.equals(that.instance) : that.instance != null) return false;
+           if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false;
+
+           return true;
+       }
+
+       @Override
+       public int hashCode() {
+           int result = namespace != null ? namespace.hashCode() : 0;
+           result = 31 * result + (QNAME != null ? QNAME.hashCode() : 0);
+           result = 31 * result + (instance != null ? instance.hashCode() : 0);
+           return result;
+       }
    }
 
 
index a8290ce57f1a46ee6ab1e106b5a8e9f889db4cc6..8fec9a0f54de240ddcce18b587f8fb163a8abf02 100644 (file)
@@ -44,1770 +44,1802 @@ import ch.ethz.ssh2.transport.TransportManager;
  */
 public class ChannelManager implements MessageHandler
 {
-       private static final Logger log = Logger.getLogger(ChannelManager.class);
-
-       private final ServerConnectionState server_state;
-       private final TransportManager tm;
-
-       private final HashMap<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>();
-
-       private final List<Channel> channels = new Vector<Channel>();
-       private int nextLocalChannel = 100;
-       private boolean shutdown = false;
-       private int globalSuccessCounter = 0;
-       private int globalFailedCounter = 0;
-
-       private final HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>();
-
-       private final List<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>();
-
-       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> channel_copy = new Vector<Channel>();
-
-               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> channel_copy = new Vector<Channel>();
-
-               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<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>();
+
+    private final List<Channel> channels = new Vector<Channel>();
+    private int nextLocalChannel = 100;
+    private boolean shutdown = false;
+    private int globalSuccessCounter = 0;
+    private int globalFailedCounter = 0;
+
+    private final HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>();
+
+    private final List<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>();
+
+    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> channel_copy = new Vector<Channel>();
+
+        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> channel_copy = new Vector<Channel>();
+
+        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));
+        }
+    }
 }