From 430fa32ddda707d29e0fb5e6cf859a31c932ee4b Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Wed, 9 Aug 2017 23:47:21 +0300 Subject: [PATCH 01/55] Add maven build script for jar file Added script to add lib files to local maven library (must be launched before fdt build) Added maven build script to include dependencies (fat-jar) --- add-lib-to-local-maven.sh | 35 +++++ pom.xml | 279 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100755 add-lib-to-local-maven.sh create mode 100644 pom.xml diff --git a/add-lib-to-local-maven.sh b/add-lib-to-local-maven.sh new file mode 100755 index 0000000..b526aa5 --- /dev/null +++ b/add-lib-to-local-maven.sh @@ -0,0 +1,35 @@ +# Adding lib directory contents to a local maven repository then later we could use them in fdt building +# Maven should be installed already on machine (i.e. use sudo apt-get install maven) + +#Globus dependencies +mvn install:install-file -Dfile="lib/globus/gss-2.2.0.jar" -DgroupId=org.globus.gsi.gssapi -DartifactId=gss -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/io-2.2.0.jar" -DgroupId=org.globus.io -DartifactId=io -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/jsse-2.2.0.jar" -DgroupId=org.globus.jsse -DartifactId=jsse -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/myproxy-2.2.0.jar" -DgroupId=org.globus.myproxy -DartifactId=myproxy -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/ssl-proxies-2.2.0.jar" -DgroupId=org.globus -DartifactId=ssl-proxies -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/gridftp-2.2.0.jar" -DgroupId=org.globus -DartifactId=gridftp -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/gram-2.2.0.jar" -DgroupId=org.globus -DartifactId=gram -Dversion=2.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/globus/axisg-2.2.0.jar" -DgroupId=org.globus -DartifactId=axisg -Dversion=2.2.0 -Dpackaging=jar +# END of Globus dependencies + +#SSH Tools dependencies +mvn install:install-file -Dfile="lib/gsi-sshterm/TransferAPIClient.jar" -DgroupId=org.globusonline -DartifactId=TransferAPIClient -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/SSHVnc.jar" -DgroupId=com.sshtools.sshvnc -DartifactId=SSHVnc -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/SSHTerm-1.0.0.jar" -DgroupId=com.sshtools.sshterm -DartifactId=SSHTerm -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/ShiFT.jar" -DgroupId=com.sshtools.shift -DartifactId=ShiFT -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/putty-pk-1.1.0.jar" -DgroupId=com.sshtools.ext -DartifactId=putty-pk -Dversion=1.1.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/SecureTunneling.jar" -DgroupId=com.sshtools.tunnel -DartifactId=SecureTunneling -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/not-yet-commons-ssl-0.3.11.jar" -DgroupId=org.apache.commons -DartifactId=not-yet-commons-ssl -Dversion=0.3.11 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/ncsa-lcrypto-146.jar" -DgroupId=edu.illinois.ncsa -DartifactId=ncsa-lcrypto -Dversion=1.4.6 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/libbrowser.jar" -DgroupId=uk.ac.rl.esc.browser -DartifactId=libbrowser -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/jlirc-unix-soc.jar" -DgroupId=org.lirc.socket -DartifactId=jlirc-unix-soc -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/j2ssh-core-0.2.7.jar" -DgroupId=com.sshtools.core -DartifactId=j2ssh-core -Dversion=0.2.7 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/j2ssh-common-0.2.7.jar" -DgroupId=com.sshtools.common -DartifactId=j2ssh-common -Dversion=0.2.7 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/filedrop.jar" -DgroupId=net.iharder.dnd -DartifactId=filedrop -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/commons-compress-1.2.jar" -DgroupId=org.apache.commons -DartifactId=commons-compress -Dversion=1.2.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/bcprov-jdk15on-1.50.jar" -DgroupId=org.bouncycastle -DartifactId=bcprov -Dversion=1.50.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/BCGSS.jar" -DgroupId=edu.illinois.ncsa -DartifactId=BCGSS -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/gsi-sshterm/swing-layout-1.0.3.jar" -DgroupId=org.jdesktop -DartifactId=swing-layout -Dversion=1.0.3 -Dpackaging=jar +#mvn install:install-file -Dfile="bbbb" -DgroupId=aaaaa -DartifactId=BCGSS -Dversion=1.0.0 -Dpackaging=jar + +echo "FINISHED INSTALLING LOCAL DEPENDENCIES" \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..37a7a9d --- /dev/null +++ b/pom.xml @@ -0,0 +1,279 @@ + + 4.0.0 + fast-data-transfer + fdt + 0.26.0-SNAPSHOT + Fast data transfer + jar + + + 1.7 + + + + edu.illinois.ncsa + BCGSS + 1.0.0 + + + org.bouncycastle + bcprov + 1.50.0 + + + org.apache.commons + commons-compress + 1.2.0 + + + net.iharder.dnd + filedrop + 1.0.0 + + + com.sshtools.common + j2ssh-common + 0.2.7 + + + com.sshtools.core + j2ssh-core + 0.2.7 + + + org.lirc.socket + jlirc-unix-soc + 1.0.0 + + + uk.ac.rl.esc.browser + libbrowser + 1.0.0 + + + log4j + log4j + 1.2.17 + + + edu.illinois.ncsa + ncsa-lcrypto + 1.4.6 + + + org.apache.commons + not-yet-commons-ssl + 0.3.11 + + + com.sshtools.ext + putty-pk + 1.1.0 + + + com.sshtools.tunnel + SecureTunneling + 1.0.0 + + + com.sshtools.shift + ShiFT + 1.0.0 + + + com.sshtools.sshterm + SSHTerm + 1.0.0 + + + com.sshtools.sshvnc + SSHVnc + 1.0.0 + + + org.jdesktop + swing-layout + 1.0.3 + + + org.globusonline + TransferAPIClient + 1.0.0 + + + org.italiangrid + voms-api-java + 3.0.1 + + + org.globus + axisg + 2.2.0 + + + commons-logging + commons-logging + 1.2 + + + org.json + json + 20170516 + + + org.globus + gram + 2.2.0 + + + org.globus + gridftp + 2.2.0 + + + org.globus.gsi.gssapi + gss + 2.2.0 + + + org.globus.io + io + 2.2.0 + + + org.globus.jsse + jsse + 2.2.0 + + + org.globus.myproxy + myproxy + 2.2.0 + + + org.globus + ssl-proxies + 2.2.0 + + + + src + + + src + + **/*.java + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + false + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + ${jdk.version} + ${jdk.version} + + + + org.apache.maven.plugins + maven-jar-plugin + + + + **/log4j.properties + + + + + lia.util.net.copy.FDTMain + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + + lia.util.net.copy.FDTMain + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + + + + \ No newline at end of file From 7235869eba173746f9400311f1626c0633a7910c Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Wed, 9 Aug 2017 23:47:21 +0300 Subject: [PATCH 02/55] Add maven build script for jar file Added script to add lib files to local maven library (must be launched before fdt build) Added maven build script to include dependencies (fat-jar) --- pom.xml | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/pom.xml b/pom.xml index 37a7a9d..5b8f1d1 100644 --- a/pom.xml +++ b/pom.xml @@ -202,50 +202,6 @@ - - org.apache.maven.plugins maven-assembly-plugin From f40a7755a2715185e324995e1153cda3510cb71f Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Mon, 14 Aug 2017 18:44:24 +0300 Subject: [PATCH 03/55] Added RPM build phase --- pom.xml | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5b8f1d1..3ec43e1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,6 +9,7 @@ 1.7 + UTF-8 @@ -215,6 +216,7 @@ jar-with-dependencies + ${project.artifactId}-${project.version} @@ -226,10 +228,68 @@ + + maven-antrun-plugin + 1.8 + + + package + + + + java + -jar fdt.jar "$@" + + + 755 + + + run + + + + + + org.codehaus.mojo + rpm-maven-plugin + 2.1.5 + true + + + + attached-rpm + + package + + + + + + /usr/bin/ + + + target + + fdt.jar + fdt + + + + + + ${rpm.classifier} + Caltech + Linux + Applications/Internet + ${user.name} + CHANGELOG + 500 + 755 + + - - - \ No newline at end of file From a0390514d866d24202b3ceada1bfea106595e122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 2 Sep 2017 00:00:24 +0300 Subject: [PATCH 04/55] Fix images paths for documentation --- docs/index.md | 23 +++++++++++++ docs/perf-disk-to-disk.md | 61 +++++++++++++++++++++++++++++++++++ docs/perf-memory-to-memory.md | 55 +++++++++++++++++++++++++++++++ docs/perf-sc06.md | 10 ++++++ docs/perf-sc08.md | 14 ++++++++ docs/perf-sc09.md | 8 +++++ 6 files changed, 171 insertions(+) create mode 100644 docs/index.md create mode 100644 docs/perf-disk-to-disk.md create mode 100644 docs/perf-memory-to-memory.md create mode 100644 docs/perf-sc06.md create mode 100644 docs/perf-sc08.md create mode 100644 docs/perf-sc09.md diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..cee573f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,23 @@ +[[Home](index.md)] [[Documentation](doc-fdt-ddcopy.md)] [[Performance Tests](perf-disk-to-disk.md)] + +### Fast Data Transfer - FDT + +FDT is an Application for Efficient Data Transfers which is capable of +reading and writing at disk speed over wide area networks (with standard TCP). +It is written in Java, runs an all major platforms and it is easy to use. + +FDT is based on an asynchronous, flexible multithreaded system and is using +the capabilities of the Java NIO libraries. Its main features are: + +* Streams a dataset (list of files) continuously, using a managed poolof buffers through one or more TCP sockets. +* Uses independent threads to read and write on each physical device +* Transfers data in parallel on multiple TCP streams, when necessary +* Uses appropriate-sized buffers for disk I/O and for the network +* Restores the files from buffers asynchronously +* Resumes a file transfer session without loss, when needed + +FDT can be used to stream a large set of files across the network, so that +a large dataset composed of thousands of files can be sent or received at +full speed, without the network transfer restarting between files. + +![Fast Data Transfer Diagram](docs/img/FDT_diagram.png) diff --git a/docs/perf-disk-to-disk.md b/docs/perf-disk-to-disk.md new file mode 100644 index 0000000..7deb41a --- /dev/null +++ b/docs/perf-disk-to-disk.md @@ -0,0 +1,61 @@ +[[Home](index.md)] [[Documentation](doc-fdt-ddcopy.md)] [Performance Tests] + +[Disk to Disk] [[Memmory to Memmory](perf-memory-to-memory.md)] [[SC06](perf-sc06.md)] [[SC08](perf-sc08.md)] [[SC09](perf-sc09.md)] + +### FDT Disk To Disk I/O Performance over WAN + + +**1. Disk Servers with hardware RAID controllers** +This performance test was done using two disk servers between CERN and Caltech (RTT ~ 170 ms). Each system used a 10Gb/s network card (The system at Caltech has a Myricom card and the system at CERN has a Neterion card) +The connection between the two systems used the USLHCNET for the transatlantic part and Internet2 in US. + +The disk servers used: +4U - 2 CPUs Dual Core Intel Woodcrest @ 3.00 GHz, 6 GB RAM, 2 ARECA RAID controllers and 24 SATA HDs. +The system at CERN runs Linux (Ubuntu 7.04) kernel 2.6.21.1 +The system at Caltech run Linux (Centos 4.4) kernel 2.6.18 +For both systems we used the default TCP congestion control (CUBIC) +MonALISA was used for all the monitoring. +The total transfer rate for data files from CERN to Caltech using the two disk controllers in parallel on both servers is shown in Figure 1. +Total network traffic for disk to disk transfer between CERN and Caltech using two RAID controllers per server + +![Figure 1. Total network traffic for disk to disk transfer between CERN and Caltech using two RAID controllers per server.](img/figure1.png) + +Figure 1. Total network traffic for disk to disk transfer between CERN and Caltech using two RAID controllers per server. + +The total Disk IO on the receiving server (Caltech) is shown in Figure 2. + +![Figure 2. Total disk IO for the receiving server (the writer). The mean value is ~ 545 MB/s.](img/figure2.png) + +Figure 2. Total disk IO for the receiving server (the writer). The mean value is ~ 545 MB/s. + +The CPU utilization for the receiving server (the writer) is presented in Figure 3. The CPU system is ~ 50% and the used for soft interrupts is ~ 15%. + +![Figure 3. The CPU utilization for the receiving server](img/figure3.png) + +Figure 3. The CPU utilization for the receiving server + +**The mean disk to disk transfer rate between the two servers was 545MB/s, which means 1.96 TB per hour.** + +If we used only one RAID controller in the data transfer on each server the total transfer rate in shown in Figure 4. In this case the mean total throughput is ~ 2.6 Gb/s or 325 MB/s. + +![Figure 4. The total network throughput for a Disk to disk transfer between CERN - Caltech, when only +one RAID controller was used on both servers](img/figure4.png) + +Figure 4. The total network throughput for a Disk to disk transfer between CERN - Caltech, when only +one RAID controller was used on both servers + +##### 2. Simple Servers + +This performance test was done using two 1U servers between CERN and MANLAN (New York) (RTT ~ 93 ms). Each system used a 10Gb/s network card (Netrion ) and we used the USLHCNET. + +The server used: + +2 CPUs Dual Core Intel Woodcrest @ 3.00 GHz, 4 GB RAM, 4 x 320 GB SATA HDs. + +FDT was used to read and write on parallel on all four SATA HDs on both servers. The total disk IO for the sender server is presented in Figure 5. + +![Figure 5. The total disk IO traffic for a data transfer between CERN and MANLAN using in parallel 4 SATA HDs on both servers.](img/figure5.png) + +Figure 5. The total disk IO traffic for a data transfer between CERN and MANLAN using in parallel 4 SATA HDs on both servers. + +The mean transfer rate was ~ 210MB/s or 0.75 TB per hour. diff --git a/docs/perf-memory-to-memory.md b/docs/perf-memory-to-memory.md new file mode 100644 index 0000000..5d9d028 --- /dev/null +++ b/docs/perf-memory-to-memory.md @@ -0,0 +1,55 @@ +[[Home](index.md)] [[Documentation](doc-fdt-ddcopy.md)] [Performance Tests] + +[[Disk to Disk](perf-disk-to-disk.md)] [Memmory to Memmory] [[SC06](perf-sc06.md)] [[SC08](perf-sc08.md)] [[SC09](perf-sc09.md)] + +### FDT Tests for Memory to Memory Transfers +##### Setup and Topology + +These tests were done on USLHCNET network using the segment between CERN and New York (RTT 93ms). +The systems (Cx-NY and Cx-GVA, x=1,2) used for these tests are: +2 CPUs Dual Core Intel Xenon @ 3.00 GHz, 4 GB RAM, 4 x 320 GB SATA Disks +Connected with 10Gb/s Myricom cards in the routers at CERN and MANLAN. The topology is presented in Figure 1. +We used FDT version 0.5 +We used 2.6.18 and 2.6.19 Linux kernels. For the TCP congestion control we used Scalable and Cubic. For both we get very similar results. + +![Topology of the test environment in using the CERN - MANLAN link](img/figure1-m2m.png) + +Figure 1. Topology of the test environment in using the CERN - MANLAN link + +##### Transfers in one direction using a pair of servers +One pair (C1-NY and C1-GVA) was used to test the maximum throughput we can get from one system. +We used 2MB for the TCP buffer size and 35 streams. +The throughput in each direction was very stable at ~ 9.2 GB/s (Figure 2). The CPU utilization for the sender and receiver is shown in Figure 3. + +![The throughput between C1-NY sender C1-GVA receiver. The TCP buffer size was set to 2MB and we used 35 steams. The RTT is 93ms.](img/figure2-m2m.png) + +Figure 2. The throughput between C1-NY sender C1-GVA receiver. The TCP buffer size was set to 2MB and we used 35 steams. The RTT is 93ms. + + ![CPU utilization for the sender and receiver](img/figure3-m2m.png) + +Figure 3. CPU utilization for the sender and receiver. + +##### Transfers in both directions with a pair of servers + +We used one pair (C1-NY and C1-GVA) to concurrently send and receive data on the same 10Gb/s interface. The throughput in each direction was ~ 6 Gb/s (Figure 4). +Perhaps the limitation is due to the PCI express bus access to the memory. Testing the throughput on a .localhost. is very close to the aggregated traffic obtained in this test. + + ![The throughput in both directions between C1-NY C1-GVA. The TCP buffer size was set to 2MB and we used 20 steams for each transfer. The RTT is 93ms](img/figure4-m2m.png) + +Figure 4. The throughput in both directions between C1-NY C1-GVA. The TCP buffer size was set to 2MB and we used 20 steams for each transfer. The RTT is 93ms. + +##### Transfers in both directions with two pairs of servers + +One pair of servers (C1-NY and C1-GVA) was used to send data from MANLAN to CERN and the other one to send data from CERN to MANLAN. The measured traffic in the MANLAN router is shown in Figure 5. The total throughput in each direction was quite stable. The Traffic from CERN to MANLAN was ~ 9.2Gb/s and the traffic from MANLAN to CERN was ~ 9Gb/s. + + ![The throughput in both directions between two pairs of servers. The TCP buffer size was set to 2MB and we used 35 steams for each transfer. The RTT is 93ms](img/figure5-m2m.png) + +Figure 5. The throughput in both directions between two pairs of servers. The TCP buffer size was set to 2MB and we used 35 steams for each transfer. The RTT is 93ms + +##### Results for Memory to Memory transfers in LAN + +The data transfer between the two systems at CERN (C1-GVA to C2-GVA) or MANLAN (C1-NY to C2-NY) runs very close to the theoretical limit of 10Gb/s (Figure 6 ) and is stable. We used 3 streams with 2MB TCP buffer size. + + ![The throughput in LAN between two servers](img/figure6-m2m.png) + +Figure 6. The throughput in LAN between two servers. diff --git a/docs/perf-sc06.md b/docs/perf-sc06.md new file mode 100644 index 0000000..dd1b682 --- /dev/null +++ b/docs/perf-sc06.md @@ -0,0 +1,10 @@ +[[Home](index.md)] [[Documentation](doc-fdt-ddcopy.md)] [Performance Tests] + +[[Disk to Disk](perf-disk-to-disk.md)] [[Memmory to Memmory](perf-memory-to-memory.md)] [SC06] [[SC08](perf-sc08.md)] [[SC09](perf-sc09.md)] + +### Fast Data Transfers at SuperComputing 2006 +FDT was used at the Supercomputing 2006, by the Caltech team for the Bandwidth Challenge. + +Following the rules set for the SC06 Bandwidth Challenge, the team used a single 10-Gbps link that carried data in both directions. FDT provided sustained total throughput of ~17 Gbps for disk to disk transfer using 10 pairs of small servers (each having 4 SATA HD configured in software raid0 and 1Gb/s network interface) in both directions. + +![SC06](img/SC06.png) diff --git a/docs/perf-sc08.md b/docs/perf-sc08.md new file mode 100644 index 0000000..4236592 --- /dev/null +++ b/docs/perf-sc08.md @@ -0,0 +1,14 @@ +[[Home](index.md)] [[Documentation](doc-fdt-ddcopy.md)] [Performance Tests] + +[[Disk to Disk](perf-disk-to-disk.md)] [[Memmory to Memmory](perf-memory-to-memory.md)] [[SC06](perf-sc06.md)] [SC08] [[SC09](perf-sc09.md)] + +### Fast Data Transfers at SuperComputing 2008 +The record-setting demonstration was made possible through the use of twelve 10 Gbps links to SC08 provided by SCInet, CENIC, National Lambda Rail, Pacific Wave and Internet2, together with two fully populated Cisco 6509E switches, 10 gigabit Ethernet network interfaces provided by Myricom and Intel, two fiber channel S2A9900 storage platforms provided Data Direct Networks equipped with 8 Gbps host bus adapters from QLogic along with five X4500 and X4540 disk servers from Sun Microsystems. The server equipment consisted of 32 twin motherboards Supermicro systems using dual quad-core Intel Xeon processors. + +![FDT @ SC08 Image](img/results08_1.jpg) + +### 100G test with Ciena + +Second major milestone was achieved by the HEP team working together with Ciena, who had just completed its first OTU-4 (112 Gbps) standard link carrying a 100 Gbps payload (or 200 Gbps bidirectional) with forward error correction. The Caltech and Ciena teams used an optical fiber cable with ten fiber-pairs linking their neighboring booths, Ciena’s system to multiplex and demultiplex ten 10 Gbps links onto the single OTU-4 wavelength running on an 80 km fiber loop, and some of Caltech’s nodes used in setting the wide area network records together with FDT, to achieve full throughput over the new link. Thanks to FDT’s high throughput capabilities, and the error free links between the booths, the teams were able to achieve a maximum of 199.90 Gbps bi-directionally (memory-to-memory) within minutes of the start of the test, and an average of 191 Gbps during a 12 hour period that logged the transmission of 1.02 Petabytes overnight. + +![FDT @ SC08 Image](img/ciena_sc08_1.jpg) diff --git a/docs/perf-sc09.md b/docs/perf-sc09.md new file mode 100644 index 0000000..b984036 --- /dev/null +++ b/docs/perf-sc09.md @@ -0,0 +1,8 @@ +[[Home](index.md)] [[Documentation](doc-fdt-ddcopy.md)] [Performance Tests] + +[[Disk to Disk](perf-disk-to-disk.md)] [[Memmory to Memmory](perf-memory-to-memory.md)] [[SC06](perf-sc06.md)] [[SC08](perf-sc08.md)] [SC09] + +### Fast Data Transfers at SuperComputing 2009 +The focus of the exhibit was the HEP team's record-breaking demonstration of storage-to-storage data transfer using FDT, over wide area networks from two racks of servers and a network switch-router on the exhibit floor. The high-energy physics team's demonstration "Moving Towards Terabit/sec Transfers of Scientific Datasets: The LHC Challenge" achieved a bi-directional peak throughput of 119 gigabits per second (Gbps) and a data flow of more than 110 Gbps that could be sustained indefinitely among clusters of servers on the show floor and at Caltech, Michigan, San Diego, Florida, Fermilab, Brookhaven, CERN, Brazil, Korea, and Estonia. FDT was used at the Supercomputing 2006, by the Caltech team for the Bandwidth Challenge. + +![FDT @ SC09 Image](img/results09_2.jpg) From d4b1e27d326f38ee78f6b1862cb0dc70edfa6728 Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Tue, 15 Aug 2017 20:19:44 +0300 Subject: [PATCH 05/55] [Prototype] 3rd party copy (cherry picked from commit 203a844) --- MANIFEST.MF | 31 +- .../dCacheFileChannelProviderFactory.java | 40 +- .../util/net/common/AbstractFDTCloseable.java | 13 +- src/lia/util/net/common/Config.java | 102 ++- .../common/FileChannelProviderFactory.java | 2 + src/lia/util/net/common/Utils.java | 127 +++- src/lia/util/net/copy/FDT.java | 639 +++++++++--------- src/lia/util/net/copy/FDTReaderSession.java | 32 +- src/lia/util/net/copy/FDTSession.java | 108 +-- src/lia/util/net/copy/FDTSessionManager.java | 32 +- src/lia/util/net/copy/FDTWriterSession.java | 12 +- .../PosixFSFileChannelProviderFactory.java | 48 +- .../net/copy/gui/ClientSessionManager.java | 20 +- src/lia/util/net/copy/gui/FolderFrame.java | 17 +- .../copy/monitoring/ConsoleReportingTask.java | 14 +- .../net/copy/transport/ControlChannel.java | 129 ++-- src/lia/util/net/copy/transport/CtrlMsg.java | 7 +- .../copy/transport/FDTSessionConfigMsg.java | 12 + .../util/net/copy/transport/SocketTask.java | 2 - .../net/copy/transport/TCPSessionWriter.java | 14 +- .../copy/transport/TCPTransportProvider.java | 4 +- 21 files changed, 849 insertions(+), 556 deletions(-) diff --git a/MANIFEST.MF b/MANIFEST.MF index b1503d6..b89d72e 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,12 +1,23 @@ Manifest-Version: 1.0 -Ant-Version: Apache Ant 1.9.6 -Created-By: 1.8.0_66-b17 (Oracle Corporation) +Class-Path: io-2.2.0.jar bcprov-jdk15on-157.jar gss-2.2.0.jar gram-2.2.0.jar jsse-2.2.0.jar + axisg-2.2.0.jar gridftp-2.2.0.jar myproxy-2.2.0.jar commons-io-2.4.jar + commons-logging.jar commons-codec-1.7.jar commons-lang3-3.1.jar ssl- + proxies-2.2.0.jar io-2.2.0.jar gss-2.2.0.jar gram-2.2.0.jar jsse-2.2. + 0.jar axisg-2.2.0.jar gridftp-2.2.0.jar myproxy-2.2.0.jar commons-io- + 2.4.jar commons-logging.jar commons-codec-1.7.jar commons-lang3-3.1.j + ar ssl-proxies-2.2.0.jar BCGSS.jar ShiFT.jar SSHVnc.jar filedrop.jar + libbrowser.jar log4j-1.2.6.jar SSHTerm-1.0.0.jar jlirc-unix-soc.jar p + utty-pk-1.1.0.jar SecureTunneling.jar bcprov-jdk16-146.jar j2ssh-core + -0.2.7.jar ncsa-lcrypto-146.jar TransferAPIClient.jar j2ssh-common-0. + 2.7.jar swing-layout-1.0.3.jar voms-api-java-2.0.9.jar commons-compre + ss-1.2.jar not-yet-commons-ssl-0.3.11.jar BCGSS.jar ShiFT.jar SSHVnc. + jar filedrop.jar libbrowser.jar log4j-1.2.6.jar SSHTerm-1.0.0.jar jli + rc-unix-soc.jar putty-pk-1.1.0.jar SecureTunneling.jar j2ssh-core-0.2 + .7.jar ncsa-lcrypto-146.jar TransferAPIClient.jar j2ssh-common-0.2.7. + jar swing-layout-1.0.3.jar voms-api-java-2.0.9.jar commons-compress-1 + .2.jar not-yet-commons-ssl-0.3.11.jar io-2.2.0.jar gss-2.2.0.jar gram + -2.2.0.jar jsse-2.2.0.jar axisg-2.2.0.jar gridftp-2.2.0.jar myproxy-2 + .2.0.jar commons-io-2.4.jar commons-logging.jar commons-codec-1.7.jar + commons-lang3-3.1.jar ssl-proxies-2.2.0.jar bcprov-jdk15on-157.jar Main-Class: lia.util.net.copy.FDTMain -Built-Jdk: 1.8.0_66-b17 -Built-By: jbalcas -Built-Arch: amd64 -Built-Os: Linux 4.3.0-040300-generic -Release-Date: 2017-04-20 -Release-Version: 0.25.0 -Implementation-Version: 0.25.1-201704201041 -Class-Path: \ No newline at end of file + diff --git a/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java b/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java index c84df70..af2d33c 100644 --- a/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java +++ b/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java @@ -11,6 +11,7 @@ import lia.util.net.common.FDTCloseable; import lia.util.net.common.FileChannelProvider; import lia.util.net.common.FileChannelProviderFactory; +//import lia.util.net.copy.FDTCoordinatorSession; import lia.util.net.copy.FDTReaderSession; import lia.util.net.copy.FDTWriterSession; @@ -25,11 +26,13 @@ public class dCacheFileChannelProviderFactory implements FileChannelProviderFact //keep them static; ignore the session private final FileChannelProvider readerFileChannelProvider; private final FileChannelProvider writerFileChannelProvider; + private final FileChannelProvider coordinatorChannelProvider; public dCacheFileChannelProviderFactory() throws Exception { dCapLayer.initialize(); - readerFileChannelProvider = new dCacheReaderFileChannelProvider(); + this.readerFileChannelProvider = new dCacheReaderFileChannelProvider(); this.writerFileChannelProvider = new dCacheWriterFileChannelProvider(); + this.coordinatorChannelProvider = new dCacheCoordinatorChannelProvider(); } public FileChannelProvider newReaderFileChannelProvider(FDTReaderSession readerSession) { @@ -40,6 +43,11 @@ public FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerS return writerFileChannelProvider; } +// @Override +// public FileChannelProvider newCoordinatorChannelProvider(FDTCoordinatorSession coordinatorSession) { +// return coordinatorChannelProvider; +// } + public boolean close(String downMessage, Throwable downCause) { try { dCapLayer.close(); @@ -113,4 +121,34 @@ public FileChannel getFileChannel(File dCacheFile, String openMode) throws IOExc } + private static final class dCacheCoordinatorChannelProvider implements FileChannelProvider { + + public File getFile(String fName) throws IOException { + try { + return new edu.caltech.hep.dcapj.dCacheFile(fName, edu.caltech.hep.dcapj.dCacheFile.Mode.WRITE_ONLY); + }catch(InvalidConfigurationException ice) { + throw new IOException(ice); + } + } + + public int getPartitionID(File dCacheFile) throws IOException { + if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { + return ((edu.caltech.hep.dcapj.dCacheFile)dCacheFile).getPoolID(); + } + throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); + } + + public FileChannel getFileChannel(File dCacheFile, String openMode) throws IOException { + if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile ) { + try { + return new edu.caltech.hep.dcapj.io.dCacheFileOutputStream((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getChannel(); + }catch(Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); + } + + } + } diff --git a/src/lia/util/net/common/AbstractFDTCloseable.java b/src/lia/util/net/common/AbstractFDTCloseable.java index 09c8386..da2a02f 100644 --- a/src/lia/util/net/common/AbstractFDTCloseable.java +++ b/src/lia/util/net/common/AbstractFDTCloseable.java @@ -28,6 +28,7 @@ public abstract class AbstractFDTCloseable implements FDTCloseable { * internalClose is called with this lock taken */ protected final Object closeLock = new Object(); + protected volatile boolean closed; private volatile String downMessage; private volatile Throwable downCause; @@ -63,7 +64,7 @@ private static final class AsynchronousCloseThread extends Thread { BlockingQueue workingQueue; private AsynchronousCloseThread() { - workingQueue = new LinkedBlockingQueue(); + workingQueue = new LinkedBlockingQueue<>(); this.setDaemon(true); this.setName(" AsyncCloseThread [ " + workingQueue.size() + " ]"); } @@ -105,9 +106,9 @@ public AbstractFDTCloseable() { public boolean close(final String downMessage, final Throwable downCause) { synchronized (closeLock) { - if (!closed) { + if (!isClosed()) { - closed = true; + setClosed(true); this.downMessage = downMessage; this.downCause = downCause; @@ -117,7 +118,7 @@ public boolean close(final String downMessage, final Throwable downCause) { return true; } - }//end sync + } return false; } @@ -126,6 +127,10 @@ public boolean isClosed() { return closed; } + public void setClosed(boolean closed) { + this.closed = closed; + } + public String downMessage() { return downMessage; } diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 2a0b2b3..609bbd7 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -20,7 +20,7 @@ /** * Configuration params for FDT - * + * * @author ramiro * @author Lucian Musat */ @@ -33,8 +33,8 @@ public class Config { public static final int NETWORK_BUFF_LEN_SIZE; static { - int defaultMSSSize = 1460; - int minMTU = 1500; + int defaultMSSSize; + int minMTU; try { minMTU = getMinMTU(); defaultMSSSize = minMTU - 40; @@ -80,13 +80,15 @@ public class Config { public static final int HEADER_SIZE = 56; public static final int HEADER_SIZE_v2 = 56; public static final boolean TRACK_ALLOCATIONS = true; + public static final int KILO = 1024; // 1 MByte - public static final int DEFAULT_BUFFER_SIZE = 1 * 1024 * 1024; // 1MB + public static final int DEFAULT_BUFFER_SIZE = KILO * KILO; // 1MB private int byteBufferSize = DEFAULT_BUFFER_SIZE; // default will be false private final boolean isNagleEnabled; // shall I get the data from server? - used only by the client private boolean isPullMode = false; + private boolean isCoordinatorMode; // this should be used for syncronizations at application level () public static final Object BIG_FDTAPP_LOCK = new Object(); // default is 4 @@ -105,13 +107,15 @@ public class Config { private String hostname; private String lisaHost; private int lisaPort; - private final int portNo; - private final int portNoGSI; - private final int portNoSSH; + private int portNo; + private int portNoGSI; + private int portNoSSH; private final boolean isStandAlone; private String[] fileList; private String[] remappedFileList; private String destDir; + private String sIP; + private String dIP; private final String sshKeyPath; private final String apMonHosts; private boolean bComputeMD5 = false; @@ -141,11 +145,12 @@ public class Config { private final String preFilters; private final String postFilters; private final String monID; + private String logLevel; private String massStorageConfig = null; private String massStorageType = null; private MassStorage storageParams = null; private Level statsLevel = null; - private final Map configMap; + private Map configMap; private boolean isNoTmpFlagSet = false; private boolean isNoLockFlagSet = false; private long consoleReportingTaskDelay = 5; @@ -300,6 +305,8 @@ private Config(final Map configMap) throws InvalidFDTParameterEx configMap.put("-ka", "" + TimeUnit.NANOSECONDS.toSeconds(this.keepAliveDelayNanos)); portNo = Utils.getIntValue(configMap, "-p", DEFAULT_PORT_NO); + List transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_PORT_NO); + isCoordinatorMode = Boolean.getBoolean("coordinator"); portNoGSI = Utils.getIntValue(configMap, "-gsip", DEFAULT_PORT_NO_GSI); portNoSSH = Utils.getIntValue(configMap, "-sshp", DEFAULT_PORT_NO_SSH); IORetryFactor = Utils.getIntValue(configMap, "-iof", 1); @@ -331,6 +338,8 @@ private Config(final Map configMap) throws InvalidFDTParameterEx isLisaRestartEnabled = (configMap.get("-enableLisaRestart") != null); bCheckUpdate = (configMap.get("-u") != null); destDir = Utils.getStringValue(configMap, "-d", null); + sIP = Utils.getStringValue(configMap, "-sIP", null); + dIP = Utils.getStringValue(configMap, "-dIP", null); bComputeMD5 = (configMap.get("-md5") != null); sshKeyPath = Utils.getStringValue(configMap, "-sshKey", null); @@ -503,8 +512,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx fileList = (String[]) files; } - logger.log(Level.INFO, "FDT started in {0} mode", ((hostname == null) - && (configMap.get("SCPSyntaxUsed") == null) ? "server" : "client")); + logger.log(Level.INFO, "FDT started in {0} mode", getFDTMode(configMap)); if (hostname != null) {// client mode if (logger.isLoggable(Level.FINE)) { StringBuilder sb = new StringBuilder(); @@ -527,7 +535,14 @@ private Config(final Map configMap) throws InvalidFDTParameterEx } } - public int getBulkSockConnect() { + private String getFDTMode(Map configMap) { + if (configMap.get("-coord") != null) { + return "coordinator"; + } + return (hostname == null) && (configMap.get("SCPSyntaxUsed") == null) ? "server" : "client"; + } + + public static int getBulkSockConnect() { return 30; } @@ -535,7 +550,7 @@ public long getKeepAliveDelay(TimeUnit unit) { return unit.convert(keepAliveDelayNanos, TimeUnit.NANOSECONDS); } - public long getBulkSockConnectWait() { + public static long getBulkSockConnectWait() { return 1500; } @@ -543,11 +558,15 @@ public Map getConfigMap() { return configMap; } + public void setConfigMap(Map configMap) { + this.configMap = configMap; + } + public static final String getUsage() { return Utils.getUsage(); } - public int getMaxTakePollIter() { + public static int getMaxTakePollIter() { return 1000; } @@ -671,6 +690,21 @@ public int getSSHPort() { return portNoSSH; } + public void setPortNo(int port) + { + this.portNo = port; + } + + public void setGSIPort(int port) + { + this.portNoGSI = port; + } + + public void setSSHPort(int port) + { + this.portNoSSH = port; + } + public boolean isStandAlone() { return isStandAlone; } @@ -703,6 +737,28 @@ public String getDestinationDir() { return destDir; } + public String getSourceIP() { + return sIP; + } + + public String getDestinationIP() { + return dIP; + } + + public void setDestinationIP(String dIP) { + this.dIP = dIP; + } + + public void setFileList(String[] fileList) { + this.fileList = fileList; + } + + public void setLisaPort(int lisaPort) { + this.lisaPort = lisaPort; + } + + + // TODO - As param ... public int getNumberOfSelectors() { return selectorsNo; @@ -719,7 +775,19 @@ public void setPullMode(boolean pullMode) { } else { this.configMap.remove("-pull"); } + } + + public void setCoordinatorMode(boolean coordinatorMode) { + this.isCoordinatorMode = coordinatorMode; + if (coordinatorMode) { + this.configMap.put("-coord", true); + } else { + this.configMap.remove("-coord"); + } + } + public boolean isCoordinatorMode() { + return isCoordinatorMode; } public boolean isPullMode() { @@ -872,6 +940,14 @@ public boolean isGenTest() { return isGenTest; } + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public String getLogLevel() { + return logLevel; + } + public FileChannelProviderFactory getFileChannelProviderFactory() { return this.fileChannelProviderFactory; } diff --git a/src/lia/util/net/common/FileChannelProviderFactory.java b/src/lia/util/net/common/FileChannelProviderFactory.java index b10625d..040ac42 100644 --- a/src/lia/util/net/common/FileChannelProviderFactory.java +++ b/src/lia/util/net/common/FileChannelProviderFactory.java @@ -4,6 +4,7 @@ */ package lia.util.net.common; +//import lia.util.net.copy.FDTCoordinatorSession; import lia.util.net.copy.FDTReaderSession; import lia.util.net.copy.FDTWriterSession; @@ -15,4 +16,5 @@ public interface FileChannelProviderFactory { FileChannelProvider newReaderFileChannelProvider(FDTReaderSession readerSession); FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerSession); +// FileChannelProvider newCoordinatorChannelProvider(FDTCoordinatorSession coordinatorSession); } diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index a2a3969..b6eda5a 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1,6 +1,7 @@ package lia.util.net.common; import apmon.ApMon; +import com.sun.istack.internal.NotNull; import lia.util.net.copy.FDT; import lia.util.net.copy.FileBlock; import lia.util.net.copy.transport.internal.FDTSelectionKey; @@ -23,6 +24,7 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.Logger; /** @@ -923,8 +925,8 @@ private static boolean updateTotalCounter(final long total, final String propert rstContor++; } - updateProperties.put(property, lastContor); - updateProperties.put(property + "_rst", rstContor); + updateProperties.put(property, String.valueOf(lastContor)); + updateProperties.put(property + "_rst", String.valueOf(rstContor)); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, " [ Utils ] [ updateTotalContor ] store new properties: {0}", @@ -1526,7 +1528,7 @@ public static void getRecursiveFiles(String fileName, String remappedFileName, L * * @param closeable to be closed */ - public static void closeIgnoringExceptions(FDTCloseable closeable, String downMessage, Throwable downCause) { + public static void closeIgnoringExceptions(FDTCloseable closeable, @NotNull String downMessage, Throwable downCause) { if (closeable != null) { try { closeable.close(downMessage, downCause); @@ -1735,4 +1737,123 @@ static InetAddress getLoopbackAddress() { return localhost; } + + public static List getTransportPortsValue(Map configMap, String key, int defaultPortNo) { + List transportPorts = new ArrayList(); + int i=0; + Object obj = configMap.get(key); + if (obj == null || obj.toString().isEmpty()) + { + transportPorts.add(defaultPortNo); + return transportPorts; + } + String tp = obj.toString(); + if (tp.isEmpty() || tp.replace(",","").isEmpty()) + { + transportPorts.add(defaultPortNo); + return transportPorts; + } + else + { + + + + + int n[]=new int[10];//for integer array of numbers + + StringTokenizer stk=new StringTokenizer(tp,","); + String s[]=new String[10];//for String array of numbers + while(stk.hasMoreTokens()) + { + s[i]=stk.nextToken(); + transportPorts.add(Integer.parseInt(s[i]));//Converting into Integer + i++; + } + } + for(i=0;i 0) { + if (level.contains("FINER")) { + logger.info(" LocalProperties loaded: " + localProps); + } + } else { + logger.info("No local properties defined"); + } + } + } + + public static void initLogger(String level, File logFile, Properties localProps) { + initLocalProps(level, localProps); + Properties loggingProps = new Properties(); + loggingProps.putAll(localProps); + + try { + if (!loggingProps.containsKey("handlers")) { + loggingProps.put("handlers", "java.util.logging.ConsoleHandler"); + loggingProps.put("java.util.logging.ConsoleHandler.level", "FINEST"); + loggingProps.put("java.util.logging.ConsoleHandler.formatter", "java.util.logging.SimpleFormatter"); + } + + if (logFile != null) { + if (loggingProps.contains("handlers")) { + loggingProps.remove("handlers"); + } + + loggingProps.put("handlers", "java.util.logging.FileHandler"); + loggingProps.put("java.util.logging.FileHandler.level", "FINEST"); + loggingProps.put("java.util.logging.FileHandler.formatter", "java.util.logging.SimpleFormatter"); + loggingProps.put("java.util.logging.FileHandler.pattern", "" + logFile); + loggingProps.put("java.util.logging.FileHandler.append", "true"); + + System.setProperty("CustomLog", "true"); + } + + if (!loggingProps.containsKey(".level")) { + loggingProps.put(".level", level); + } + + if (level.contains("FINER")) { + logger.info("\n Logging props: " + loggingProps); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + loggingProps.store(baos, null); + LogManager.getLogManager().reset(); + LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(baos.toByteArray())); + + } catch (Throwable t) { + System.err.println(" Got exception setting the logging level "); + t.printStackTrace(); + } + } } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index cf98bc8..8c57560 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -6,24 +6,30 @@ import lia.util.net.copy.monitoring.ConsoleReportingTask; import lia.util.net.copy.monitoring.FDTInternalMonitoringTask; import lia.util.net.copy.monitoring.lisa.LISAReportingTask; +import lia.util.net.copy.transport.ControlChannel; +import lia.util.net.copy.transport.CtrlMsg; import lia.util.net.copy.transport.FDTProcolException; +import lia.util.net.copy.transport.FDTSessionConfigMsg; import lia.util.net.copy.transport.internal.SelectionManager; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.logging.LogManager; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; import java.util.logging.Logger; /** * The "main" class ... Everything will start from here, more or less - * + *

* Due to Java checks the app entry point is {@link FDTMain} - * + * * @author ramiro */ public class FDT { + public static final String MONALISA2_CERN_CH = "monalisa2.cern.ch:28884"; private static final String name = "FDT"; private static final Logger logger = Logger.getLogger(FDT.class.getName()); @@ -34,7 +40,9 @@ public class FDT { public static final String FDT_FULL_VERSION = "0.26.0-201708081850"; - /** two weeks between checking for updates */ + /** + * two weeks between checking for updates + */ public static final long UPDATE_PERIOD = 2 * 24 * 3600 * 1000; private static Config config; @@ -48,92 +56,9 @@ private final static class GracefulStopper extends AbstractFDTCloseable { private boolean internalClosed = false; - protected void internalClose() throws Exception { - synchronized (this) { - this.internalClosed = true; - this.notifyAll(); - } - } - } - - private static void initLocalProps(String level) { - - FileInputStream fis = null; - File confFile = null; - try { - confFile = new File( - System.getProperty("user.home") + File.separator + ".fdt" + File.separator + "fdt.properties"); - if (level.contains("FINE")) { - logger.info("Using local properties file: " + confFile); - } - if (confFile.exists() && confFile.canRead()) { - fis = new FileInputStream(confFile); - localProps.load(fis); - } - } catch (Throwable t) { - if (confFile != null) { - if (level.contains("FINE")) { - System.err.println("Unable to read local configuration file " + confFile); - t.printStackTrace(); - } - } - } finally { - Utils.closeIgnoringExceptions(fis); - } - - if (level.contains("FINE")) { - if (localProps.size() > 0) { - if (level.contains("FINER")) { - logger.info(" LocalProperties loaded: " + localProps); - } - } else { - logger.info("No local properties defined"); - } - } - } - - private static void initLogger(String level, File logFile) { - initLocalProps(level); - Properties loggingProps = new Properties(); - loggingProps.putAll(localProps); - - try { - if (!loggingProps.containsKey("handlers")) { - loggingProps.put("handlers", "java.util.logging.ConsoleHandler"); - loggingProps.put("java.util.logging.ConsoleHandler.level", "FINEST"); - loggingProps.put("java.util.logging.ConsoleHandler.formatter", "java.util.logging.SimpleFormatter"); - } - - if (logFile != null) { - if (loggingProps.contains("handlers")) { - loggingProps.remove("handlers"); - } - - loggingProps.put("handlers", "java.util.logging.FileHandler"); - loggingProps.put("java.util.logging.FileHandler.level", "FINEST"); - loggingProps.put("java.util.logging.FileHandler.formatter", "java.util.logging.SimpleFormatter"); - loggingProps.put("java.util.logging.FileHandler.pattern", "" + logFile); - loggingProps.put("java.util.logging.FileHandler.append", "true"); - - System.setProperty("CustomLog", "true"); - } - - if (!loggingProps.containsKey(".level")) { - loggingProps.put(".level", level); - } - - if (level.contains("FINER")) { - logger.info("\n Logging props: " + loggingProps); - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - loggingProps.store(baos, null); - LogManager.getLogManager().reset(); - LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(baos.toByteArray())); - - } catch (Throwable t) { - System.err.println(" Got exception setting the logging level "); - t.printStackTrace(); + protected synchronized void internalClose() throws Exception { + this.internalClosed = true; + this.notifyAll(); } } @@ -143,10 +68,10 @@ private static void initLogger(String level, File logFile) { final String configApMonHosts = config.getApMonHosts(); if (configApMonHosts != null) { long lStart = System.currentTimeMillis(); + ApMon apmon = null; - String mlDestinations = "monalisa2.cern.ch:28884"; - final String apMonHosts = (configApMonHosts.length() > 0) ? configApMonHosts : mlDestinations; + final String apMonHosts = (configApMonHosts.length() > 0) ? configApMonHosts : MONALISA2_CERN_CH; logger.info("Trying to instantiate apMon to: " + apMonHosts); try { @@ -154,11 +79,11 @@ private static void initLogger(String level, File logFile) { Vector vPorts = new Vector<>(); final String[] apMonDstTks = apMonHosts.split(","); - if (apMonDstTks == null || apMonDstTks.length == 0) { - System.err.println("\n\nApMon enabled but no hosts defined! Cannot send apmon statistics\n\n"); + if (apMonDstTks.length == 0) { + logger.log(Level.WARNING, "\n\nApMon enabled but no hosts defined! Cannot send apmon statistics\n\n"); } else { for (String host_port : apMonDstTks) { - int index = -1; + int index; String host; int port; if ((index = host_port.indexOf(':')) != -1) { @@ -182,8 +107,8 @@ private static void initLogger(String level, File logFile) { apmon.setGenMonitoring(true, 40); // apmon.setJobMonitoring(, ) // apmon.setMaxMsgRate(50); - String cluster_name = ""; - String node_name = ""; + String cluster_name; + String node_name; if (config.getHostName() != null) {// client cluster_name = "Clients"; node_name = config.getHostName(); @@ -203,8 +128,7 @@ private static void initLogger(String level, File logFile) { } } catch (Throwable ex) { - System.err.println("Error initializing ApMon engine."); - ex.printStackTrace(); + logger.log(Level.WARNING, "Error initializing ApMon engine.", ex); } finally { Utils.initApMonInstance(apmon); } @@ -215,11 +139,10 @@ private static void initLogger(String level, File logFile) { Utils.getMonitoringExecService().scheduleWithFixedDelay(apmrt, 1, config.getApMonReportingInterval(), TimeUnit.SECONDS); } else { - System.err.println("Cannot start ApMonReportingTask because apMon is null!"); + logger.log(Level.WARNING, "Cannot start ApMonReportingTask because apMon is null!"); } } catch (Throwable t) { - System.err.println("Cannot start ApMonReportingTask because got Exception:"); - t.printStackTrace(); + logger.log(Level.WARNING, "Cannot start ApMonReportingTask because got Exception.", t); } long lEnd = System.currentTimeMillis(); @@ -234,23 +157,30 @@ private static void initLogger(String level, File logFile) { reportingTaskDelay, TimeUnit.SECONDS); } - if (config.getHostName() != null) { // role == client - // the session manager will check the "pull/push" mode and start the FDTSession - FDTSessionManager.getInstance().addFDTClientSession(); - } else { // is server - if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), config.getMaxTakePollIter())) { - // this is really wrong ... I cannot be already initialized - throw new FDTProcolException("The buffer pool cannot be alredy initialized"); - } + if (config.isCoordinatorMode()) { + ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); + UUID sessionID = cc.sendCoordinatorMessage(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, new FDTSessionConfigMsg(config))); + // wait for remote config + logger.log(Level.INFO,"Message sent to: " + config.getHostName() +":"+ config.getPort() + " Remote Job Session ID: " + sessionID); + System.exit(0); + } else { + if (config.getHostName() != null) { // role == client + // the session manager will check the "pull/push" mode and start the FDTSession + FDTSessionManager.getInstance().addFDTClientSession(); + } else { // is server + if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { + // this is really wrong ... I cannot be already initialized + throw new FDTProcolException("The buffer pool cannot be alredy initialized"); + } - final FDTServer theServer = new FDTServer(); // ( because it's the only one ) - theServer.doWork(); + final FDTServer theServer = new FDTServer(); // ( because it's the only one ) + theServer.doWork(); + } } - } private static void printHelp() { - System.err.println(Config.getUsage()); + logger.log(Level.INFO, Config.getUsage()); } private static void printVersion() { @@ -260,10 +190,11 @@ private static void printVersion() { private static int doWork() { + Exception e = null; FDTSessionManager fdtSessionManager = FDTSessionManager.getInstance(); try { - for (;;) { + for (; ; ) { try { Thread.sleep(1000); if (config.getHostName() != null && fdtSessionManager.isInited()) { @@ -286,27 +217,29 @@ private static int doWork() { } } } catch (Throwable t) { - System.err.println("FDT Got exception in main loop"); - t.printStackTrace(); + logger.log(Level.WARNING, "FDT Got exception in main loop", t); } } + } + catch (Exception ex) { + e = ex; } finally { try { logger.info(" [ " + new Date().toString() + " ] - GracefulStopper hook started ... Waiting for the cleanup to finish"); - GracefulStopper stopper = new GracefulStopper(); + AtomicReference stopper = new AtomicReference<>(new GracefulStopper()); // it will be the last in the queue ;) - stopper.close(null, null); + stopper.get().close(null, e); - while (!stopper.internalClosed) { - synchronized (stopper) { - if (stopper.internalClosed) { + while (!stopper.get().internalClosed) { + synchronized (stopper.get()) { + if (stopper.get().internalClosed) { break; } try { - stopper.wait(); + stopper.get().wait(); } catch (Throwable t) { t.printStackTrace(); } @@ -314,21 +247,20 @@ private static int doWork() { } logger.info(" [ " + new Date().toString() + " ] - GracefulStopper hook finished!"); } catch (Throwable gExc) { - System.err.println(" [GracefulStopper] Got exception stopper"); - gExc.printStackTrace(); + logger.log(Level.WARNING, " [GracefulStopper] Got exception stopper", gExc); } } final Throwable tExit = fdtSessionManager.getLasDownCause(); final String mExit = fdtSessionManager.getLasDownMessage(); if (tExit != null || mExit != null) { - System.err.println("\n [ " + new Date().toString() + " ] FDT Session finished with errors: "); + logger.log(Level.WARNING, "\n [ " + new Date().toString() + " ] FDT Session finished with errors: "); if (mExit != null) { - System.err.println(mExit + '\n'); + logger.log(Level.WARNING, mExit + '\n'); } if (tExit != null) { - System.err.println(Utils.getStackTrace(tExit) + '\n'); + logger.log(Level.WARNING, Utils.getStackTrace(tExit) + '\n'); } return 1; @@ -341,131 +273,165 @@ private static int doWork() { private static void processSCPSyntax(String[] args) throws Exception { int iTransferConfiguration = config.getSSHConfig(); if (iTransferConfiguration > 0) { - ControlStream sshConn; - String localAddresses; - StringBuilder remoteCmd; - String[] clients; - final int sshPort = config.getSSHPort(); switch (iTransferConfiguration) { + case Config.SSH_REMOTE_SERVER_LOCAL_CLIENT_PUSH: + sshRemoteServerLocalClientPush(sshPort); + break; + case Config.SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL: + sshRemoteServerLocalClientPull(sshPort); + break; + case Config.SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH: + sshRemoteServerAndClientPush(args, sshPort); + break; + default: + break; + } + } + } - case Config.SSH_REMOTE_SERVER_LOCAL_CLIENT_PUSH: - System.err.println("[SSH Mode] SSH_REMOTE_SERVER_LOCAL_CLIENT_PUSH. Remote ssh port: " + sshPort); - try {// here we can have some class-not-found exceptions if GSI libraries are not loaded - sshConn = config.isGSISSHModeEnabled() ? // - new lia.util.net.common.GSISSHControlStream(config.getHostName(), - config.getDestinationUser(), sshPort) - : // - new SSHControlStream(config.getHostName(), config.getDestinationUser(), sshPort); - } catch (NoClassDefFoundError t) { - throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); - } - sshConn.connect(); - localAddresses = config.getLocalAddresses(); - // append the required options to the configurable java command - remoteCmd = new StringBuilder(config.getRemoteCommand() + " -p " + config.getPort() + " -noupdates -silent -S -f " - + localAddresses); - System.err.println(" [ CONFIG ] Starting FDT server over SSH using [ " + remoteCmd + " ]"); - sshConn.startProgram(remoteCmd.toString()); - sshConn.waitForControlMessage("READY"); - System.err.println(" [ CONFIG ] FDT server successfully started on [ " + config.getHostName() + " ]"); - break; - - case Config.SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL: - System.err.println("[SSH Mode] SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL. Remote ssh port: " + sshPort); - // the host running the FDT server is the source in this case - String remoteServerHost = config.getSourceHosts()[0]; - String remoteServerUsername; - clients = config.getSourceUsers(); - if (clients != null && clients.length > 0 && clients[0] != null) { - remoteServerUsername = clients[0]; - } else { - remoteServerUsername = System.getProperty("user.name", "root"); - } - // update the local client parameters - config.setPullMode(true); - config.setHostName(remoteServerHost); - - try {// here we can have some class-not-found exceptions if GSI libraries are not loaded - sshConn = config.isGSISSHModeEnabled() - ? new lia.util.net.common.GSISSHControlStream(remoteServerHost, remoteServerUsername, - sshPort) - : new SSHControlStream(remoteServerHost, remoteServerUsername, sshPort); - } catch (NoClassDefFoundError t) { - throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); - } - sshConn.connect(); - localAddresses = config.getLocalAddresses(); - // append the required options to the configurable java command - remoteCmd = new StringBuilder(config.getRemoteCommand() + " -p " + config.getPort() + " -noupdates -silent -S -f " - + localAddresses); - System.err.println(" [ CONFIG ] Starting FDT server over SSH using [ " + remoteCmd + " ]"); - sshConn.startProgram(remoteCmd.toString()); - sshConn.waitForControlMessage("READY"); - System.err.println(" [ CONFIG ] FDT server successfully started on [ " + remoteServerHost + " ]"); - break; - - case Config.SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH: - System.err.println("[SSH Mode] SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH. Remote ssh port: " + sshPort); - // the host starting the fdt client - final String clientHost = config.getSourceHosts()[0]; - // start FDT Server - try {// here we can have some class-not-found exceptions if GSI libraries are not loaded - sshConn = config.isGSISSHModeEnabled() - ? new lia.util.net.common.GSISSHControlStream(config.getHostName(), - config.getDestinationUser(), sshPort) - : new SSHControlStream(config.getHostName(), config.getDestinationUser(), sshPort); - } catch (NoClassDefFoundError t) { - throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); - } - // append the required options to the configurable java command - remoteCmd = new StringBuilder(config.getRemoteCommand() + " -p " + config.getPort() + " -noupdates -silent -S -f " - + clientHost); - System.err.println(" [ CONFIG ] Starting remote FDT server over SSH using [ " + remoteCmd + " ]"); - sshConn.startProgram(remoteCmd.toString()); - sshConn.waitForControlMessage("READY"); - System.err.println(" [ CONFIG ] FDT server successfully started on [ " + config.getHostName() + " ]"); - // server ok - - // start FDT client - String clientUser; - clients = config.getSourceUsers(); - if (clients != null && clients.length > 0 && clients[0] != null) { - clientUser = clients[0]; - } else { - clientUser = System.getProperty("user.name", "root"); - } + private static void sshRemoteServerLocalClientPush(int sshPort) throws Exception { + ControlStream sshConn; + String localAddresses; + StringBuilder remoteCmd; + if (logger.isLoggable(Level.FINE)) { + logger.fine("[SSH Mode] SSH_REMOTE_SERVER_LOCAL_CLIENT_PUSH. Remote ssh port: " + sshPort); + } + try {// here we can have some class-not-found exceptions if GSI libraries are not loaded + sshConn = config.isGSISSHModeEnabled() ? // + new GSISSHControlStream(config.getHostName(), + config.getDestinationUser(), sshPort) + : // + new SSHControlStream(config.getHostName(), config.getDestinationUser(), sshPort); + } catch (NoClassDefFoundError t) { + throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); + } + sshConn.connect(); + localAddresses = config.getLocalAddresses(); + // append the required options to the configurable java command + remoteCmd = new StringBuilder(config.getRemoteCommand() + " -p " + config.getPort() + " -noupdates -silent -S -f " + + localAddresses); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] Starting FDT server over SSH using [ " + remoteCmd + " ]"); + } + sshConn.startProgram(remoteCmd.toString()); + sshConn.waitForControlMessage("READY"); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] FDT server successfully started on [ " + config.getHostName() + " ]"); + } + } - try {// here we can have some class-not-found exceptions if GSI libraries are not loaded - sshConn = config.isGSISSHModeEnabled() - ? new lia.util.net.common.GSISSHControlStream(clientHost, clientUser, sshPort) - : new SSHControlStream(clientHost, clientUser, sshPort); - } catch (NoClassDefFoundError t) { - throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); - } - remoteCmd = new StringBuilder(config.getRemoteCommand()); - for (String arg : args) { - if (arg.indexOf(':') < 0) { - remoteCmd.append(' ').append(arg); - } - } - remoteCmd.append(" -c ").append(config.getHostName()); - remoteCmd.append(" -d ").append(config.getDestinationDir()); - String[] files = (String[]) config.getConfigMap().get("Files"); - remoteCmd.append(' ').append(files[0]); - System.err.println(" [ CONFIG ] Starting FDT client over SSH using [ " + remoteCmd + " ]"); - sshConn.startProgram(remoteCmd.toString()); - // wait for client termination or forced exit - sshConn.waitForControlMessage("DONE", true); - // after the remote client finished, our 'proxy' program should also exit - // maybe we should change this 'exit' with some method return code - System.exit(0); - break; - default: - break; + private static void sshRemoteServerLocalClientPull(int sshPort) throws Exception { + String[] clients; + ControlStream sshConn; + String localAddresses; + StringBuilder remoteCmd; + if (logger.isLoggable(Level.FINE)) { + logger.fine("[SSH Mode] SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL. Remote ssh port: " + sshPort); + } + // the host running the FDT server is the source in this case + String remoteServerHost = config.getSourceHosts()[0]; + String remoteServerUsername; + clients = config.getSourceUsers(); + if (clients != null && clients.length > 0 && clients[0] != null) { + remoteServerUsername = clients[0]; + } else { + remoteServerUsername = System.getProperty("user.name", "root"); + } + // update the local client parameters + config.setPullMode(true); + config.setHostName(remoteServerHost); + + try {// here we can have some class-not-found exceptions if GSI libraries are not loaded + sshConn = config.isGSISSHModeEnabled() + ? new GSISSHControlStream(remoteServerHost, remoteServerUsername, + sshPort) + : new SSHControlStream(remoteServerHost, remoteServerUsername, sshPort); + } catch (NoClassDefFoundError t) { + throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); + } + sshConn.connect(); + localAddresses = config.getLocalAddresses(); + // append the required options to the configurable java command + remoteCmd = new StringBuilder(config.getRemoteCommand() + " -p " + config.getPort() + " -noupdates -silent -S -f " + + localAddresses); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] Starting FDT server over SSH using [ " + remoteCmd + " ]"); + } + sshConn.startProgram(remoteCmd.toString()); + sshConn.waitForControlMessage("READY"); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] FDT server successfully started on [ " + remoteServerHost + " ]"); + } + } + + private static void sshRemoteServerAndClientPush(String[] args, int sshPort) throws Exception { + ControlStream sshConn; + StringBuilder remoteCmd; + String[] clients; + if (logger.isLoggable(Level.FINE)) { + logger.fine("[SSH Mode] SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH. Remote ssh port: " + sshPort); + } + // the host starting the fdt client + final String clientHost = config.getSourceHosts()[0]; + // start FDT Server + try {// here we can have some class-not-found exceptions if GSI libraries are not loaded + sshConn = config.isGSISSHModeEnabled() + ? new GSISSHControlStream(config.getHostName(), + config.getDestinationUser(), sshPort) + : new SSHControlStream(config.getHostName(), config.getDestinationUser(), sshPort); + } catch (NoClassDefFoundError t) { + throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); + } + // append the required options to the configurable java command + remoteCmd = new StringBuilder(config.getRemoteCommand() + " -p " + config.getPort() + " -noupdates -silent -S -f " + + clientHost); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] Starting remote FDT server over SSH using [ " + remoteCmd + " ]"); + } + sshConn.startProgram(remoteCmd.toString()); + sshConn.waitForControlMessage("READY"); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] FDT server successfully started on [ " + config.getHostName() + " ]"); + } + // server ok + + // start FDT client + String clientUser; + clients = config.getSourceUsers(); + if (clients != null && clients.length > 0 && clients[0] != null) { + clientUser = clients[0]; + } else { + clientUser = System.getProperty("user.name", "root"); + } + + try {// here we can have some class-not-found exceptions if GSI libraries are not loaded + sshConn = config.isGSISSHModeEnabled() + ? new GSISSHControlStream(clientHost, clientUser, sshPort) + : new SSHControlStream(clientHost, clientUser, sshPort); + } catch (NoClassDefFoundError t) { + throw new Exception("GSI libraries not loaded. You should set CLASSPATH accordingly!"); + } + remoteCmd = new StringBuilder(config.getRemoteCommand()); + for (String arg : args) { + if (arg.indexOf(':') < 0) { + remoteCmd.append(' ').append(arg); } } + remoteCmd.append(" -c ").append(config.getHostName()); + remoteCmd.append(" -d ").append(config.getDestinationDir()); + String[] files = (String[]) config.getConfigMap().get("Files"); + remoteCmd.append(' ').append(files[0]); + if (logger.isLoggable(Level.FINE)) { + logger.fine(" [ CONFIG ] Starting FDT client over SSH using [ " + remoteCmd + " ]"); + } + sshConn.startProgram(remoteCmd.toString()); + // wait for client termination or forced exit + sshConn.waitForControlMessage("DONE", true); + // after the remote client finished, our 'proxy' program should also exit + // maybe we should change this 'exit' with some method return code + System.exit(0); } private static void initManagement() throws Exception { @@ -474,30 +440,62 @@ private static void initManagement() throws Exception { // the one and only entry point public static void main(String[] args) throws Exception { - // Init the logging - - // If the ${HOME}/.fdt/fdt.properties exists - String logLevel = null; - File logFile = null; - initLogging(args, logLevel, logFile); + String logLevel = initLogging(args); Map argsMap = Utils.parseArguments(args, Config.SINGLE_CMDLINE_ARGS); - if (argsMap.get("-c") != null) { - if (argsMap.get("-d") == null && argsMap.get("-nettest") == null) { - throw new IllegalArgumentException("No destination specified"); - } + checkMainParams(argsMap); - @SuppressWarnings("unchecked") - final List lParams = (List) argsMap.get("LastParams"); + final boolean noLock = checkAdditionalParams(argsMap); - if (argsMap.get("-nettest") == null && argsMap.get("-fl") == null - && (lParams == null || lParams.size() == 0) && argsMap.get("Files") == null) { - throw new IllegalArgumentException("No source specified"); + updateOrSkip(logLevel, argsMap, noLock); + + logger.info("\n\n" + name + " [ " + FDT_FULL_VERSION + " ] STARTED ... \n\n"); + + initConfig(argsMap, logLevel); + + if (!config.isCoordinatorMode()) { + logger.info("FDT uses" + ((!config.isBlocking()) ? " *non-" : " *") + "blocking* I/O mode."); + } + + processSCPSyntax(args); + + HeaderBufferPool.initInstance(); + + if (!config.isLisaDisabled()) { + LISAReportingTask lrt = LISAReportingTask.initInstance(config.getLisaHost(), config.getLisaPort()); + Utils.getMonitoringExecService().scheduleWithFixedDelay(lrt, 1, config.getLisaReportingInterval(), + TimeUnit.SECONDS); + } + + try { + new FDT(); + initManagement(); + } catch (Throwable t) { + logger.log(Level.WARNING, "Failed to instantiate FDT", t); + System.exit(1); + } + + final int exitCode = FDT.doWork(); + + Utils.getMonitoringExecService().shutdownNow(); + try { + if (config.massStorageType() != null && config.massStorageType().equals("dcache")) { + final FileChannelProviderFactory fcpf = config.getFileChannelProviderFactory(); + if (fcpf instanceof FDTCloseable) { + ((FDTCloseable) fcpf).close(null, null); + } } + } catch (Throwable t) { + logger.log(Level.WARNING, "FDT got exception trying to close the dCapLayer. Cause:", t); + System.exit(2502); } + System.exit(exitCode); + } + + private static boolean checkAdditionalParams(Map argsMap) throws Exception { final boolean noLock = argsMap.get("-nolock") != null || argsMap.get("-nolocks") != null; if (argsMap.get("-h") != null || argsMap.get("-H") != null || argsMap.get("-help") != null || argsMap.get("--help") != null) { @@ -508,26 +506,26 @@ public static void main(String[] args) throws Exception { System.exit(0); } else if (argsMap.get("-u") != null || argsMap.get("-U") != null || argsMap.get("-update") != null || argsMap.get("--update") != null) { - final Object urlS = argsMap.get("-U"); - String updateURL = UPDATE_URL; - - if (urlS != null && urlS instanceof String) { - updateURL = (String) urlS; - if (updateURL.length() == 0) { - updateURL = UPDATE_URL; - } - } + updateIfAvailable(argsMap, noLock); + } + return noLock; + } - if (Utils.updateFDT(FDT_FULL_VERSION, updateURL, true, noLock)) { - // Just print the current version ... - logger.info("\nThe update finished successfully\n"); - System.exit(0); - } else { - logger.info("\nNo updates available\n"); - System.exit(100); - } + private static void initConfig(Map argsMap, String logLevel) { + try { + Config.initInstance(argsMap); + } catch (InvalidFDTParameterException e) { + logger.log(Level.WARNING,"Invalid parameters supplied: " + e.getMessage(), e); + System.exit(1); + } catch (Throwable t1) { + logger.log(Level.WARNING,"got exception parsing command args", t1); + System.exit(1); } + config = Config.getInstance(); + config.setLogLevel(logLevel); + } + private static void updateOrSkip(String logLevel, Map argsMap, boolean noLock) { if (argsMap.get("-noupdates") == null) { final Object urlS = argsMap.get("-U"); String updateURL = UPDATE_URL; @@ -565,70 +563,47 @@ public static void main(String[] args) throws Exception { } } } + } - logger.info("\n\n" + name + " [ " + FDT_FULL_VERSION + " ] STARTED ... \n\n"); + private static void updateIfAvailable(Map argsMap, boolean noLock) throws Exception { + final Object urlS = argsMap.get("-U"); + String updateURL = UPDATE_URL; - try { - Config.initInstance(argsMap); - } catch (InvalidFDTParameterException e) { - System.err.println("Invalid parameters supplied: " + e.getMessage()); - e.printStackTrace(); - System.err.flush(); - System.exit(1); - } catch (Throwable t1) { - System.err.println("got exception parsing command args"); - t1.printStackTrace(); - System.err.flush(); - System.exit(1); + if (urlS != null && urlS instanceof String) { + updateURL = (String) urlS; + if (updateURL.length() == 0) { + updateURL = UPDATE_URL; + } } - config = Config.getInstance(); - logger.info("FDT uses" + ((!config.isBlocking()) ? " *non-" : " *") + "blocking* I/O mode."); - - processSCPSyntax(args); - - HeaderBufferPool.initInstance(); - - FDT jnc = null; - - if (!config.isLisaDisabled()) { - LISAReportingTask lrt = LISAReportingTask.initInstance(config.getLisaHost(), config.getLisaPort()); - Utils.getMonitoringExecService().scheduleWithFixedDelay(lrt, 1, config.getLisaReportingInterval(), - TimeUnit.SECONDS); + if (Utils.updateFDT(FDT_FULL_VERSION, updateURL, true, noLock)) { + // Just print the current version ... + logger.info("\nThe update finished successfully\n"); + System.exit(0); + } else { + logger.info("\nNo updates available\n"); + System.exit(100); } + } - try { - jnc = new FDT(); - initManagement(); - } catch (Throwable t) { - t.printStackTrace(); - System.out.flush(); - System.err.flush(); - System.exit(1); - } + private static void checkMainParams(Map argsMap) { + if (argsMap.get("-c") != null) { + if (argsMap.get("-d") == null && argsMap.get("-nettest") == null) { + throw new IllegalArgumentException("No destination specified"); + } - final int exitCode = FDT.doWork(); + @SuppressWarnings("unchecked") final List lParams = (List) argsMap.get("LastParams"); - Utils.getMonitoringExecService().shutdownNow(); - try { - if (config.massStorageType() != null && config.massStorageType().equals("dcache")) { - final FileChannelProviderFactory fcpf = config.getFileChannelProviderFactory(); - if (fcpf instanceof FDTCloseable) { - ((FDTCloseable) fcpf).close(null, null); - } + if (argsMap.get("-nettest") == null && argsMap.get("-fl") == null + && (lParams == null || lParams.size() == 0) && argsMap.get("Files") == null) { + throw new IllegalArgumentException("No source specified"); } - } catch (Throwable t) { - System.err.println("FDT got exception trying to close the dCapLayer. Cause:"); - t.printStackTrace(); - System.out.flush(); - System.err.flush(); - System.exit(2502); } - - System.exit(exitCode); } - private static String initLogging(String[] args, String logLevel, File logFile) throws IOException { + private static String initLogging(String[] args) throws IOException { + String logLevel = null; + File logFile = null; for (int i = 0; i < args.length; i++) { if (logLevel == null) { if (args[i].equals("-v")) { @@ -697,7 +672,7 @@ private static String initLogging(String[] args, String logLevel, File logFile) if (logLevel.startsWith("FIN")) { logger.info(" LogLevel: " + logLevel); } - initLogger(logLevel, logFile); + Utils.initLogger(logLevel, logFile, localProps); return logLevel; } } diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 5f2210a..f4e2368 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -6,17 +6,8 @@ import java.io.File; import java.io.IOException; import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; @@ -43,7 +34,7 @@ /** * The "reader" session; it will send data over the wire - * + * * @author ramiro */ public class FDTReaderSession extends FDTSession implements FileBlockProducer { @@ -73,7 +64,7 @@ public class FDTReaderSession extends FDTSession implements FileBlockProducer { private int readersCount = 1; - private static final int MAX_TAKE_POLL_ITER = config.getMaxTakePollIter(); + private static final int MAX_TAKE_POLL_ITER = Config.getMaxTakePollIter(); private final AtomicBoolean finalCleaupExecuted = new AtomicBoolean(false); @@ -83,11 +74,12 @@ public class FDTReaderSession extends FDTSession implements FileBlockProducer { /** * LOCAL SESSION - look in the Config - * + * * @throws Exception */ public FDTReaderSession() throws Exception { super(FDTSession.CLIENT); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "CLIENT-" + sessionID + ".log"), new Properties()); final int rMul = Integer.getInteger("fdt.rQueueM", 2).intValue(); final int avProcProp = Integer.getInteger("fdt.avProc", 1).intValue(); final int avProcMax = Math.max(avProcProp, Utils.availableProcessors()); @@ -113,12 +105,13 @@ public FDTReaderSession() throws Exception { /** * REMOTE SESSION - wait for init() - * + * * @param ctrlChannel * @throws Exception */ public FDTReaderSession(ControlChannel ctrlChannel) throws Exception { super(ctrlChannel, FDTSession.SERVER); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "SERVER-" + sessionID + ".log"), new Properties()); fileBlockQueue = new ArrayBlockingQueue(Utils.availableProcessors() * 2); readersMap = new TreeMap>(); @@ -637,7 +630,7 @@ private void finalCleanup() { } nlrec.setType("RETR"); - System.out.println(nlrec.toULMString()); + logger.info(nlrec.toULMString()); // log final statistics try { @@ -676,11 +669,8 @@ private void finalCleanup() { sb.append("\n Exit Status: ") .append(((downCause() == null) && (downMessage() == null)) ? "OK" : "Not OK"); sb.append("\n"); - if (customLog) { - logger.info(sb.toString()); - } else { - System.out.println(sb.toString()); - } + logger.info(sb.toString()); + System.out.println(sb.toString()); } catch (Throwable t) { logger.log(Level.WARNING, "[ FDTReaderSession ] [ finalCleanup ] [ HANDLED ] Exception getting final statistics. Smth went dreadfully wrong!", @@ -868,7 +858,7 @@ public void handleEndFDTSession(CtrlMsg ctrlMsg) throws Exception { } /** - * @param ctrlMsg + * @param ctrlMsg */ @Override public void handleStartFDTSession(CtrlMsg ctrlMsg) throws Exception { diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index ec7c784..3890661 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -3,14 +3,9 @@ */ package lia.util.net.copy; +import java.io.File; import java.net.InetAddress; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -24,15 +19,11 @@ import lia.util.net.common.Utils; import lia.util.net.copy.monitoring.FDTSessionMonitoringTask; import lia.util.net.copy.monitoring.lisa.LisaCtrlNotifier; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.ControlChannelNotifier; -import lia.util.net.copy.transport.CtrlMsg; -import lia.util.net.copy.transport.FDTProcolException; -import lia.util.net.copy.transport.TCPTransportProvider; +import lia.util.net.copy.transport.*; /** * Base class for both FDT Reader/Writer sessions - * + * * @author ramiro */ public abstract class FDTSession extends IOSession implements ControlChannelNotifier, Comparable, @@ -49,6 +40,8 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public static final short CLIENT = 1; + public static final short COORDINATOR = 2; + public static final int UNINITIALIZED = 0; // I think only OOM can do this public static final int STARTED = 1 << 0; @@ -71,6 +64,8 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public static final int END_RCV = 1 << 8; + public static final int COORDINATOR_MSG_RCVD = 1 << 9; + protected AtomicLong totalProcessedBytes; protected AtomicLong totalUtilBytes; @@ -148,6 +143,8 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public FDTSession(short role) throws Exception { super(); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+ (role == CLIENT ? "CLIENT" : "SERVER")+ "-" + sessionID + ".log"), new Properties()); + customLog = Utils.isCustomLog(); currentStatus = 0; @@ -156,7 +153,7 @@ public FDTSession(short role) throws Exception { setCurrentState(STARTED); this.role = role; - if (this.role == CLIENT) { + if (this.role == CLIENT || this.role == COORDINATOR) { this.controlChannel = new ControlChannel(config.getHostName(), config.getPort(), sessionID(), this); } @@ -385,32 +382,37 @@ public final void notifyCtrlMsg(ControlChannel controlChannel, Object o) throws synchronized (protocolLock) { switch (ctrlMsg.tag) { - case CtrlMsg.INIT_FDTSESSION_CONF: { - setCurrentState(INIT_CONF_RCV); - handleInitFDTSessionConf(ctrlMsg); - break; - } - case CtrlMsg.FINAL_FDTSESSION_CONF: { - setCurrentState(FINAL_CONF_RCV); - handleFinalFDTSessionConf(ctrlMsg); - break; - } - case CtrlMsg.START_SESSION: { - setCurrentState(START_RCV); - handleStartFDTSession(ctrlMsg); - break; - } - case CtrlMsg.END_SESSION: { - setCurrentState(END_RCV); - handleEndFDTSession(ctrlMsg); - break; - } - default: { - FDTProcolException fpe = new FDTProcolException("Illegal CtrlMsg tag [ " + ctrlMsg.tag + " ]"); - fpe.fillInStackTrace(); - close("FileProtocolException", fpe); - throw fpe; - } + case CtrlMsg.INIT_FDTSESSION_CONF: { + setCurrentState(INIT_CONF_RCV); + handleInitFDTSessionConf(ctrlMsg); + break; + } + case CtrlMsg.FINAL_FDTSESSION_CONF: { + setCurrentState(FINAL_CONF_RCV); + handleFinalFDTSessionConf(ctrlMsg); + break; + } + case CtrlMsg.START_SESSION: { + setCurrentState(START_RCV); + handleStartFDTSession(ctrlMsg); + break; + } + case CtrlMsg.END_SESSION: { + setCurrentState(END_RCV); + handleEndFDTSession(ctrlMsg); + break; + } + case CtrlMsg.THIRD_PARTY_COPY: { + setCurrentState(COORDINATOR_MSG_RCVD); + handleCoordinatorMessage(ctrlMsg); + break; + } + default: { + FDTProcolException fpe = new FDTProcolException("Illegal CtrlMsg tag [ " + ctrlMsg.tag + " ]"); + fpe.fillInStackTrace(); + close("FileProtocolException", fpe); + throw fpe; + } } } } else { @@ -421,6 +423,32 @@ public final void notifyCtrlMsg(ControlChannel controlChannel, Object o) throws } } + private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { + + logger.log(Level.INFO, "[ FDTSession ] [ handleCoordinatorMessage ( " + ctrlMsg.message.toString() + " )"); + Map oldConfig = config.getConfigMap(); + try { + FDTSessionConfigMsg sessionConfig = (FDTSessionConfigMsg) ctrlMsg.message; + config.setDestinationDir(sessionConfig.destinationDir); + config.setCoordinatorMode(false); + config.setDestinationIP(sessionConfig.destinationIP); + config.setFileList(sessionConfig.fileLists); + config.setPortNo(54321); + final ControlChannel ctrlChann = this.controlChannel; + FDTSession session = FDTSessionManager.getInstance().addFDTClientSession(); + ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, session.controlChannel.fdtSessionID())); + } + catch (Exception ex) + { + logger.log(Level.WARNING, "Exception while handling coordinator message", ex); + } + finally { + //Restore old config. + this.setClosed(true); + config.setConfigMap(oldConfig); + } + } + protected void buildPartitionMap() { if (logger.isLoggable(Level.FINEST)) { diff --git a/src/lia/util/net/copy/FDTSessionManager.java b/src/lia/util/net/copy/FDTSessionManager.java index e96c4b6..6cf1690 100644 --- a/src/lia/util/net/copy/FDTSessionManager.java +++ b/src/lia/util/net/copy/FDTSessionManager.java @@ -3,6 +3,11 @@ */ package lia.util.net.copy; +import lia.util.net.common.AbstractFDTCloseable; +import lia.util.net.common.Config; +import lia.util.net.common.Utils; +import lia.util.net.copy.transport.*; + import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.Map; @@ -16,15 +21,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.AbstractFDTCloseable; -import lia.util.net.common.Config; -import lia.util.net.common.Utils; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.ControlChannelNotifier; -import lia.util.net.copy.transport.FDTProcolException; - /** - * * This class is the placeholder for all the alve FDTSessions instantiated * in the entire FDT app * @@ -50,14 +47,14 @@ public class FDTSessionManager extends AbstractFDTCloseable implements ControlCh private volatile String lastDownMsg; private volatile Throwable lastDownCause; - public static final FDTSessionManager getInstance() { + public static FDTSessionManager getInstance() { return _thisInstanceManager; } private FDTSessionManager() { lock = new ReentrantLock(); isSessionMapEmpty = lock.newCondition(); - fdtSessionMap = new ConcurrentHashMap(); + fdtSessionMap = new ConcurrentHashMap<>(); inited = new AtomicBoolean(false); } @@ -103,14 +100,13 @@ public FDTSession addFDTClientSession() throws Exception { FDTSession fdtSession = null; try { - - if (config.isPullMode()) { - //-> Start a writer and connect to the server - fdtSession = new FDTWriterSession(); - } else { - //-> Start a reader and connect to the server - fdtSession = new FDTReaderSession(); - } + if (config.isPullMode()) { + //-> Start a writer and connect to the server + fdtSession = new FDTWriterSession(); + } else { + //-> Start a reader and connect to the server + fdtSession = new FDTReaderSession(); + } fdtSessionMap.put(fdtSession.sessionID(), fdtSession); inited.set(true); diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index a3f552a..55603c8 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -5,13 +5,7 @@ import java.io.File; import java.net.InetAddress; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -62,6 +56,7 @@ public class FDTWriterSession extends FDTSession implements FileBlockConsumer { public FDTWriterSession() throws Exception { super(FDTSession.CLIENT); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "CLIENT" + "-" + sessionID + ".log"), new Properties()); dwm.addSession(this); sendInitConf(); this.monID = config.getMonID(); @@ -75,6 +70,7 @@ public FDTWriterSession() throws Exception { */ public FDTWriterSession(ControlChannel cc) throws Exception { super(cc, FDTSession.SERVER); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "SERVER" + "-" + sessionID + ".log"), new Properties()); dwm.addSession(this); this.monID = (String) cc.remoteConf.get("-monID"); } @@ -163,7 +159,7 @@ private void finalCleanup() { } nlrec.setType("STOR"); - System.out.println(nlrec.toULMString()); + logger.info(nlrec.toULMString()); try { notifySessionFinished(); diff --git a/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java b/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java index 49d9894..b6330fa 100644 --- a/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java +++ b/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java @@ -4,11 +4,7 @@ */ package lia.util.net.copy; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.RandomAccessFile; +import java.io.*; import java.nio.channels.FileChannel; import lia.util.net.common.FileChannelProvider; @@ -22,11 +18,13 @@ public class PosixFSFileChannelProviderFactory implements FileChannelProviderFac private final FileChannelProvider readerFileChannelProvider; private final FileChannelProvider writerFileChannelProvider; + private final FileChannelProvider coordinatorChannelProvider; public PosixFSFileChannelProviderFactory() { this.readerFileChannelProvider = new PosixFSReaderFileChannelProvider(); this.writerFileChannelProvider = new PosixFSWriterFileChannelProvider(); + this.coordinatorChannelProvider = new PosixFSCoordinatorChannelProvider(); } /** @@ -43,7 +41,14 @@ public FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerS return writerFileChannelProvider; } - private static final class PosixFSReaderFileChannelProvider implements FileChannelProvider { +// /** +// * @param coordinatorSession +// */ +// public FileChannelProvider newCoordinatorChannelProvider(FDTCoordinatorSession coordinatorSession) { +// return coordinatorChannelProvider; +// } + + private static final class PosixFSReaderFileChannelProvider implements FileChannelProvider, Serializable { /** * @throws IOException @@ -101,4 +106,35 @@ public FileChannel getFileChannel(File file, final String openMode) throws IOExc } + private static final class PosixFSCoordinatorChannelProvider implements FileChannelProvider { + + /** + * @throws IOException + */ + public File getFile(String fileName) throws IOException { + return new File(fileName); + } + + /** + * @throws IOException + */ + public int getPartitionID(File file) throws IOException { + if(file.exists()) { + return PartitionMap.getPartitionFromCache(file); + } + + return PartitionMap.getPartitionFromCache(file.getParentFile()); + } + + @SuppressWarnings("resource") + public FileChannel getFileChannel(File file, final String openMode) throws IOException { + if(openMode != null) { + return new RandomAccessFile(file, openMode).getChannel(); + } + + return new FileOutputStream(file).getChannel(); + } + + } + } diff --git a/src/lia/util/net/copy/gui/ClientSessionManager.java b/src/lia/util/net/copy/gui/ClientSessionManager.java index 33754d6..e0d4fad 100644 --- a/src/lia/util/net/copy/gui/ClientSessionManager.java +++ b/src/lia/util/net/copy/gui/ClientSessionManager.java @@ -73,14 +73,12 @@ public FDTSession currentSession() { public void cancelTransfer() { if (currentSession == null) return; -// currentSession.finishFileSession(currentSession.sessionID(), new Exception("User pressed cancel")); currentSession.close("User pressed cancel", new Exception("User pressed cancel")); currentSession = null; fdtSessionMTask = null; } public void end() { -// System.out.println("Ended called "+Utils.getMonitoringExecService().getQueue().size()); if (fdtInternalMonitoringTask != null) { Utils.getMonitoringExecService().remove(fdtInternalMonitoringTask); fdtInternalMonitoringTask = null; @@ -104,7 +102,6 @@ public double transferProgress() { TCPTransportProvider tcpTransportProvider = currentSession.getTransportProvider(); if (tcpTransportProvider == null) { -// logger.warning("NULL Transport is closed"); return 0.0; } @@ -117,16 +114,12 @@ public double transferProgress() { final long tcpSize = tcpTransportProvider.getUtilBytes(); final double cSize = (tcpSize <= 0L) ? 0D : tcpSize; -// final double cSize = currentSession.getTotalBytes(); double percent = 100.0; try { percent = Math.min((cSize*100.0)/(double)tSize, 100.0); } catch (Exception e) { } if (!Double.isNaN(percent) && !Double.isInfinite(percent) && percent >= 100.0) { try { -// Method m = FDTSession.class.getDeclaredMethod("currentState", new Class[0]); -// m.setAccessible(true); -// int state = (Integer)m.invoke(currentSession, new Object[0]); int state = currentSession.currentState(); boolean endRcv = ((state & FDTSession.END_RCV) == FDTSession.END_RCV); boolean endSnt = ((state & FDTSession.END_SENT) == FDTSession.END_SENT); @@ -188,11 +181,6 @@ private final void constructConfig(final String host, final int port, final bool final String[] fileList, final String destDir, final FDTPropsDialog d, final boolean isRecursive) { // first set the initialized flag on false.... Class c = Config.class; -// try { -// Field f = c.getDeclaredField("initialized"); -// f.setAccessible(true); -// f.set(null, false); -// } catch (Throwable t) { } // construct the hashmap try { Config.initInstance(new HashMap()); @@ -203,9 +191,6 @@ private final void constructConfig(final String host, final int port, final bool // shall I get the data from server? - used only by the client try { conf.setHostName(host); -// Field f = c.getDeclaredField("hostname"); -// f.setAccessible(true); -// f.set(conf, host); } catch (Throwable t) { } try { Field f = c.getDeclaredField("portNo"); @@ -214,10 +199,7 @@ private final void constructConfig(final String host, final int port, final bool } catch (Throwable t) { } try { conf.setPullMode(isPullMode); -// Field f = c.getDeclaredField("isPullMode"); -// f.setAccessible(true); -// f.set(conf, isPullMode); - } catch (Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } try { diff --git a/src/lia/util/net/copy/gui/FolderFrame.java b/src/lia/util/net/copy/gui/FolderFrame.java index 693dd67..77e779a 100644 --- a/src/lia/util/net/copy/gui/FolderFrame.java +++ b/src/lia/util/net/copy/gui/FolderFrame.java @@ -79,21 +79,8 @@ public void actionPerformed(ActionEvent e) { this.manager = new RemoteSessionManager(props, panel); connect = new ConnectDialog(this); -// connect.setVisible(true); -// connect.toFront(); -// if (connect.bDialogOK) { -// String host = connect.sHost; -// int port = 54321; -// try { -// port = Integer.valueOf(connect.sPort); -// } catch (Throwable t) { } -// try { -// manager.connect(host, port); -// } catch (Throwable t) { } -// } else { - manager.initiated = true; -// } - + manager.initiated = true; + panel.setOpaque(false); panel.setLayout(new BorderLayout()); JPanel pp = new JPanel(); diff --git a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java index c9f229c..f67e9d7 100644 --- a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java +++ b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java @@ -22,7 +22,7 @@ /** * This class is the only class which should report to the stdout - * + * * @author ramiro */ public class ConsoleReportingTask extends AbstractAccountableMonitoringTask { @@ -57,7 +57,7 @@ public static final ConsoleReportingTask getInstance() { } private final boolean reportStatus(final Set currentSessionSet, final Set oldSessionSet, - final String tag, final StringBuilder sb) { + final String tag, final StringBuilder sb) { boolean shouldReport = false; if (oldSessionSet.size() > 0) { @@ -79,7 +79,7 @@ private final boolean reportStatus(final Set currentSessionSet, fina logger.log(Level.WARNING, " [ ConsoleReportingTask ] The session: " + fdtSession .sessionID() + " is no longer " - + "available, but canot remove trasport provider from monitoring queue. It's probably a BUG in FDT"); + + "available, but canot remove trasport provider from monitoring queue. It's probably a BUG in FDT"); continue; } if (logger.isLoggable(Level.FINE)) { @@ -161,12 +161,10 @@ private void reportStatus() { || reportStatus(diskReaderManager.getSessions(), oldReaderSessions, "Net Out: ", sb)); if (shouldReport) { - if (customLog) { - logger.info(sb.toString()); - } else { - System.out.println(sb.toString()); - } + logger.info(sb.toString()); + System.out.println(sb.toString()); } + } @Override diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index bdb6c9a..1a558d5 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -3,41 +3,29 @@ */ package lia.util.net.copy.transport; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import lia.gsi.GSIServer; +import lia.gsi.net.GSIGssSocketFactory; +import lia.util.net.common.*; + +import javax.security.auth.Subject; +import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import java.util.UUID; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import javax.security.auth.Subject; - -import lia.gsi.GSIServer; -import lia.gsi.net.GSIGssSocketFactory; -import lia.util.net.common.AbstractFDTCloseable; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.FDTVersion; -import lia.util.net.common.Utils; - /** * Encapsulates the control socket ( channel ) between two peer FDTSessios When the constructor returns the * communication can begin ... - * + * * @author ramiro */ public class ControlChannel extends AbstractFDTCloseable implements Runnable { @@ -53,6 +41,8 @@ public class ControlChannel extends AbstractFDTCloseable implements Runnable { public static final int SOCKET_TIMEOUT = 60 * 1000; + public static final int MAX_RETRIES = 3; + private final Socket controlSocket; private final ConcurrentLinkedQueue qToSend = new ConcurrentLinkedQueue(); @@ -115,7 +105,7 @@ public void run() { /** * Try to connect to a remote FDT instance - * + * * @param address * @param port * @param sessionID @@ -128,7 +118,7 @@ public ControlChannel(String address, int port, UUID sessionID, ControlChannelNo /** * Try to connect to a remote FDT instance - * + * * @param inetAddress * @param port * @param fdtSessionID @@ -156,9 +146,9 @@ public ControlChannel(InetAddress inetAddress, int port, UUID fdtSessionID, Cont controlSocket.setTcpNoDelay(true); - // only the first octet will be interpreted by the AcceptTask at the other end + // only the first socket will be interpreted by the AcceptTask at the other end if (!config.isGSIModeEnabled()) { - controlSocket.getOutputStream().write(new byte[] { 0 }); + controlSocket.getOutputStream().write(new byte[]{0}); } // from now on only CtrlMsg will be sent @@ -178,11 +168,9 @@ public boolean isSocketClosed() { /** * A remote peer connected to FDT - * - * @param s - * - the socket - * @throws Exception - * - if anything goes wrong in intialization + * + * @param s - the socket + * @throws Exception - if anything goes wrong in intialization */ public ControlChannel(Socket s, ControlChannelNotifier notifier) throws Exception { try { @@ -204,8 +192,7 @@ public ControlChannel(Socket s, ControlChannelNotifier notifier) throws Exceptio } /** - * @param parent - * + * @param parent */ public ControlChannel(GSIServer parent, Socket s, Subject peerSubject, ControlChannelNotifier notifier) throws Exception { @@ -247,16 +234,12 @@ private void initStreams() throws Exception { try { BufferedInputStream bis = new BufferedInputStream(controlSocket.getInputStream()); - if (bis.available() == 1) - { + if (bis.available() == 1) { throw new IllegalStateException("Could not initialise stream to server, client did not use GSI"); - } - else { + } else { ois = new ObjectInputStream(new BufferedInputStream(controlSocket.getInputStream())); } - } - catch (IOException ex) - { + } catch (IOException ex) { logger.log(Level.WARNING, "Could not initialise stream to server, check if server is running or certificates present" + ex); throw ex; } @@ -284,7 +267,7 @@ private void initStreams() throws Exception { try { if (DirectByteBufferPool.initInstance(Integer.parseInt((String) remoteConf.get("-bs")), - config.getMaxTakePollIter())) { + Config.getMaxTakePollIter())) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "The buffer pool has been initialized"); } @@ -311,6 +294,7 @@ private void initStreams() throws Exception { sendMsgImpl(new CtrlMsg(CtrlMsg.SESSION_ID, fdtSessionID)); } + Utils.initLogger(config.getLogLevel(), null, new Properties()); myName = " ControlThread for ( " + fdtSessionID + " ) " + controlSocket.getInetAddress() + ":" + controlSocket.getPort(); logger.log(Level.INFO, "NEW CONTROL stream for " + fdtSessionID + " initialized "); @@ -363,12 +347,11 @@ private final void cleanup() { } } - public void sendCtrlMessage(final Object ctrlMsg) { + public void sendCtrlMessage(final CtrlMsg ctrlMsg) { if (ctrlMsg == null) { throw new NullPointerException("Control message cannot be null over the ControlChannel"); } - if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); if (logger.isLoggable(Level.FINEST)) { @@ -376,13 +359,71 @@ public void sendCtrlMessage(final Object ctrlMsg) { Thread.dumpStack(); } } - qToSend.add(ctrlMsg); + } + + public void sendSessionIDToCoordinator(CtrlMsg ctrlMsg) { + logger.log(Level.INFO, "[ ControlChannel ] [ sendSessionIDToCoordinator ( " + ctrlMsg.message.toString() + " )"); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); + } + } + try { + sendMsgImpl(ctrlMsg); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public UUID sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { + + logger.log(Level.INFO, "[ ControlChannel ] [ sendCoordinatorMessage ]"); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); + } + } + + try { + sendMsgImpl(ctrlMsg); + + CtrlMsg newCtrlMsg = getRemoteJobSessionID(); + logger.info("Remote job session ID: " + newCtrlMsg.message.toString()); + return (UUID) newCtrlMsg.message; + + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to retrieve response from server", e); + } + cleanup(); + return null; + } + + private CtrlMsg getRemoteJobSessionID() throws Exception { + Exception t = null; + CtrlMsg newCtrlMsg = null; + for (int i = 1; i <= MAX_RETRIES; i++) { + try { + newCtrlMsg = (CtrlMsg) ois.readObject(); + return newCtrlMsg; + } catch (Exception e) { + t = e; + Thread.sleep(i * CONNECT_TIMEOUT/2); + } finally { + if (newCtrlMsg == null && i == MAX_RETRIES) { + throw t; + } + } + } + return null; } private void sendAllMsgs() throws Exception { - for (;;) { + for (; ; ) { final Object ctrlMsg = qToSend.poll(); if (ctrlMsg == null) { break; diff --git a/src/lia/util/net/copy/transport/CtrlMsg.java b/src/lia/util/net/copy/transport/CtrlMsg.java index c74cab5..3027ae6 100644 --- a/src/lia/util/net/copy/transport/CtrlMsg.java +++ b/src/lia/util/net/copy/transport/CtrlMsg.java @@ -54,7 +54,7 @@ public class CtrlMsg implements Serializable { /** message types designated to GUI */ public static final int GUI_MSG = 11; - + /** * * sent, eventually, by the FDTWriter session to notify ONLY @@ -62,11 +62,14 @@ public class CtrlMsg implements Serializable { * **/ public static final int END_SESSION_FIN2 = 12; + + /** message types designated for third party copy feature */ + public static final int THIRD_PARTY_COPY = 13; private static final String[] CTRL_MSG_TAGS = new String[] { "KEEP_ALIVE_MSG", "PROTOCOL_VERSION", "SESSION_ID", "SESSION_TYPE", "INIT_FDT_CONF", "PING_SESSION", "INIT_FDTSESSION_CONF", "FINAL_FDTSESSION_CONF", "FINISHED_FILE_SESSIONS", "START_SESSION", "END_SESSION", - "GUI_MSG" + "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY" }; /** diff --git a/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java b/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java index 0059dee..6dfb7c3 100644 --- a/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java +++ b/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy.transport; +import lia.util.net.common.Config; + import java.io.Serializable; import java.util.Arrays; import java.util.UUID; @@ -17,10 +19,13 @@ public class FDTSessionConfigMsg implements Serializable { public String destinationDir; + public String destinationIP; public boolean recursive; //future? use public String dirOffset; + + public String sourceIP; public UUID[] fileIDs; public String[] fileLists; @@ -32,6 +37,13 @@ public FDTSessionConfigMsg() { } + public FDTSessionConfigMsg(Config config) { + this.destinationIP = config.getDestinationIP(); + this.destinationDir = config.getDestinationDir(); + this.sourceIP = config.getSourceIP(); + this.fileLists = config.getFileList(); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/lia/util/net/copy/transport/SocketTask.java b/src/lia/util/net/copy/transport/SocketTask.java index a92a3ca..8a32678 100644 --- a/src/lia/util/net/copy/transport/SocketTask.java +++ b/src/lia/util/net/copy/transport/SocketTask.java @@ -17,8 +17,6 @@ * */ public abstract class SocketTask extends AbstractFDTIOEntity implements Runnable { -// protected final static DirectByteBufferPool payloadPool = DirectByteBufferPool.getInstance(); -// protected final static HeaderBufferPool headersPool = HeaderBufferPool.getInstance(); protected static final boolean isBlocking = Config.getInstance().isBlocking(); protected final BlockingQueue readyChannelsQueue; diff --git a/src/lia/util/net/copy/transport/TCPSessionWriter.java b/src/lia/util/net/copy/transport/TCPSessionWriter.java index 72495a9..b61bfc3 100644 --- a/src/lia/util/net/copy/transport/TCPSessionWriter.java +++ b/src/lia/util/net/copy/transport/TCPSessionWriter.java @@ -33,13 +33,13 @@ public class TCPSessionWriter extends TCPTransportProvider { public TCPSessionWriter(FDTReaderSession fdtSession) throws Exception { super(fdtSession, new PriorityBlockingQueue(10, new FDTWriterKeyAttachementComparator())); } - + public TCPSessionWriter(FDTReaderSession fdtSession, InetAddress endPointAddress, int port, int numberOfStreams) throws Exception { super(fdtSession, endPointAddress, port, numberOfStreams, new PriorityBlockingQueue(10, new FDTWriterKeyAttachementComparator())); } - + public void notifyAvailableBytes(final long available) { speedLimitLock.lock(); try { @@ -152,12 +152,10 @@ public void startTransport(final boolean sendCookie) throws Exception { public void workerDown(FDTSelectionKey fdtSelectionKey, Throwable downCause) { //smth gone wrong ... or maybe the session finished already //I do not know if it should take other action ... for the moment the session will go down - -// if(downCause != null) { -// logger.log(Level.WARNING, " [ TCPSessionReader ] for fdtSession [ " + fdtSession + " ] got an error on a worker", downCause); -// } -// - + if(downCause != null) { + logger.log(Level.WARNING, " [ TCPSessionReader ] for fdtSession [ " + fdtSession + " ] got an error on a worker", downCause); + } + close("Worker down", downCause); if(fdtSession != null) { diff --git a/src/lia/util/net/copy/transport/TCPTransportProvider.java b/src/lia/util/net/copy/transport/TCPTransportProvider.java index 9041df6..d9b6404 100644 --- a/src/lia/util/net/copy/transport/TCPTransportProvider.java +++ b/src/lia/util/net/copy/transport/TCPTransportProvider.java @@ -153,8 +153,8 @@ private static final List tryToConnect(InetSocketAddress addr, in tmpSelector = Selector.open(); - final int bSockConn = config.getBulkSockConnect(); - final long bSockConnWait = config.getBulkSockConnectWait(); + final int bSockConn = Config.getBulkSockConnect(); + final long bSockConnWait = Config.getBulkSockConnectWait(); logger.log(Level.FINER, " bSockConn: " + bSockConn + " bSockConnWait: " + bSockConnWait); int cCounter = 0; From eae6cbf4c8689b68c54dbac4037e2128540c09f5 Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Sat, 19 Aug 2017 20:44:31 +0300 Subject: [PATCH 06/55] [Prototype] 3rd party copy [part 2] [retrieve session log file] To retrieve log file for any transfer session please use following sample. Usage: -c -d -sID (cherry picked from commit 28b3759) --- src/lia/util/net/common/Config.java | 42 +++++++++++++++++---- src/lia/util/net/copy/FDT.java | 6 +-- src/lia/util/net/copy/FDTReaderSession.java | 4 +- src/lia/util/net/copy/FDTSession.java | 2 +- src/lia/util/net/copy/FDTWriterSession.java | 4 +- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 609bbd7..85750d7 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -89,6 +89,7 @@ public class Config { // shall I get the data from server? - used only by the client private boolean isPullMode = false; private boolean isCoordinatorMode; + private boolean isRetrievingLogFile; // this should be used for syncronizations at application level () public static final Object BIG_FDTAPP_LOCK = new Object(); // default is 4 @@ -228,14 +229,15 @@ private Config(final Map configMap) throws InvalidFDTParameterEx } isStandAlone = (configMap.get("-S") == null); + setRetrievingLogFile(configMap.get("-sID")); byteBufferSize = Utils.getIntValue(configMap, "-bs", DEFAULT_BUFFER_SIZE); - configMap.put("-bs", "" + byteBufferSize); + configMap.put("-bs", String.valueOf(byteBufferSize)); sockNum = Utils.getIntValue(configMap, "-P", DEFAULT_SOCKET_NO); - configMap.put("-P", "" + sockNum); + configMap.put("-P", String.valueOf(sockNum)); sockBufSize = Utils.getIntValue(configMap, "-ss", -1); - configMap.put("-ss", "" + sockBufSize); + configMap.put("-ss", String.valueOf(sockBufSize)); rateLimit = Utils.getLongValue(configMap, "-limit", -1); if ((rateLimit > 0) && (rateLimit < NETWORK_BUFF_LEN_SIZE)) { @@ -243,9 +245,9 @@ private Config(final Map configMap) throws InvalidFDTParameterEx logger.log(Level.WARNING, " The rate limit (-limit) is too small. It will be set to " + rateLimit + " Bytes/s"); } - configMap.put("-limit", "" + rateLimit); + configMap.put("-limit", String.valueOf(rateLimit)); rateLimitDelayMillis = Utils.getLongValue(configMap, "-limitDelay", 300L); - configMap.put("-limitDelay", "" + rateLimitDelayMillis); + configMap.put("-limitDelay", String.valueOf(rateLimitDelayMillis)); preFilters = Utils.getStringValue(configMap, "-preFilters", null); postFilters = Utils.getStringValue(configMap, "-postFilters", null); @@ -297,12 +299,13 @@ private Config(final Map configMap) throws InvalidFDTParameterEx if ((hostname != null) && (hostname.length() == 0)) { hostname = null; } else { - isPullMode = (configMap.get("-pull") != null); + isPullMode = (configMap.get("-pull") != null) || (configMap.get("-sID") != null); + configMap.put("-pull", ""); } final long ka = Utils.getLongValue(configMap, "-ka", TimeUnit.NANOSECONDS.toSeconds(DEFAULT_KEEP_ALIVE_NANOS)); this.keepAliveDelayNanos = (ka < 0) ? DEFAULT_KEEP_ALIVE_NANOS : TimeUnit.SECONDS.toNanos(ka); - configMap.put("-ka", "" + TimeUnit.NANOSECONDS.toSeconds(this.keepAliveDelayNanos)); + configMap.put("-ka", String.valueOf(TimeUnit.NANOSECONDS.toSeconds(this.keepAliveDelayNanos))); portNo = Utils.getIntValue(configMap, "-p", DEFAULT_PORT_NO); List transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_PORT_NO); @@ -500,6 +503,13 @@ private Config(final Map configMap) throws InvalidFDTParameterEx Utils.closeIgnoringExceptions(br); } } + else if (configMap.get("-sID") != null) + { + String sessionID = (String)configMap.get("-sID"); + String[] logFiles = getLogFiles(sessionID); + remappedFileList = logFiles; + fileList = logFiles; + } } else { configMap.remove("-fl"); fileList = lastParams.toArray(new String[lastParams.size()]); @@ -535,6 +545,10 @@ private Config(final Map configMap) throws InvalidFDTParameterEx } } + private String[] getLogFiles(String sessionID) { + return new String[] {"/tmp/"+sessionID+".log"}; + } + private String getFDTMode(Map configMap) { if (configMap.get("-coord") != null) { return "coordinator"; @@ -786,10 +800,24 @@ public void setCoordinatorMode(boolean coordinatorMode) { } } + public void setRetrievingLogFile(Object sessionID) { + this.isRetrievingLogFile = sessionID != null; + if (isRetrievingLogFile) { + this.configMap.put("-sID", (String)sessionID); + } else { + this.configMap.remove("-sID"); + } + } + public boolean isCoordinatorMode() { return isCoordinatorMode; } + public boolean isRetrievingLogFile() { + return isRetrievingLogFile || configMap.containsKey("-sID"); + } + + public boolean isPullMode() { return isPullMode; } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 8c57560..43257fc 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -455,7 +455,7 @@ public static void main(String[] args) throws Exception { initConfig(argsMap, logLevel); - if (!config.isCoordinatorMode()) { + if (!config.isCoordinatorMode() || !config.isRetrievingLogFile()) { logger.info("FDT uses" + ((!config.isBlocking()) ? " *non-" : " *") + "blocking* I/O mode."); } @@ -594,8 +594,8 @@ private static void checkMainParams(Map argsMap) { @SuppressWarnings("unchecked") final List lParams = (List) argsMap.get("LastParams"); - if (argsMap.get("-nettest") == null && argsMap.get("-fl") == null - && (lParams == null || lParams.size() == 0) && argsMap.get("Files") == null) { + if ((argsMap.get("-nettest") == null && argsMap.get("-fl") == null + && (lParams == null || lParams.size() == 0) && argsMap.get("Files") == null) && argsMap.get("-sID") == null) { throw new IllegalArgumentException("No source specified"); } } diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index f4e2368..d10ae42 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -79,7 +79,7 @@ public class FDTReaderSession extends FDTSession implements FileBlockProducer { */ public FDTReaderSession() throws Exception { super(FDTSession.CLIENT); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "CLIENT-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); final int rMul = Integer.getInteger("fdt.rQueueM", 2).intValue(); final int avProcProp = Integer.getInteger("fdt.avProc", 1).intValue(); final int avProcMax = Math.max(avProcProp, Utils.availableProcessors()); @@ -111,7 +111,7 @@ public FDTReaderSession() throws Exception { */ public FDTReaderSession(ControlChannel ctrlChannel) throws Exception { super(ctrlChannel, FDTSession.SERVER); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "SERVER-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); fileBlockQueue = new ArrayBlockingQueue(Utils.availableProcessors() * 2); readersMap = new TreeMap>(); diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 3890661..ba2d01a 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -143,7 +143,7 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public FDTSession(short role) throws Exception { super(); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+ (role == CLIENT ? "CLIENT" : "SERVER")+ "-" + sessionID + ".log"), new Properties()); +// Utils.initLogger(config.getLogLevel(), new File("/tmp/"+ (role == CLIENT ? "CLIENT" : "SERVER")+ "-" + sessionID + ".log"), new Properties()); customLog = Utils.isCustomLog(); diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 55603c8..5d2a964 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -56,7 +56,7 @@ public class FDTWriterSession extends FDTSession implements FileBlockConsumer { public FDTWriterSession() throws Exception { super(FDTSession.CLIENT); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "CLIENT" + "-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); dwm.addSession(this); sendInitConf(); this.monID = config.getMonID(); @@ -70,7 +70,7 @@ public FDTWriterSession() throws Exception { */ public FDTWriterSession(ControlChannel cc) throws Exception { super(cc, FDTSession.SERVER); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "SERVER" + "-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); dwm.addSession(this); this.monID = (String) cc.remoteConf.get("-monID"); } From aa899f0220a4e9a665108036a114d5741e35f16a Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Sun, 20 Aug 2017 07:55:00 +0300 Subject: [PATCH 07/55] [Prototype] 3rd party copy [part 3] [retrieve list of files] To retrieve list of files on custom path please use following sample. Usage: -c -ls (cherry picked from commit 0a3e2a6) --- src/lia/util/net/common/Config.java | 23 ++++- src/lia/util/net/copy/FDT.java | 38 +++++--- src/lia/util/net/copy/FDTSession.java | 94 ++++++++++++++++--- .../net/copy/transport/ControlChannel.java | 37 ++++++-- src/lia/util/net/copy/transport/CtrlMsg.java | 5 +- .../net/copy/transport/FDTListFilesMsg.java | 31 ++++++ 6 files changed, 190 insertions(+), 38 deletions(-) create mode 100644 src/lia/util/net/copy/transport/FDTListFilesMsg.java diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 85750d7..8c9bdcd 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -3,6 +3,8 @@ */ package lia.util.net.common; +import lia.util.net.copy.PosixFSFileChannelProviderFactory; + import java.io.BufferedReader; import java.io.FileReader; import java.net.NetworkInterface; @@ -16,8 +18,6 @@ import java.util.logging.Logger; import java.util.regex.Pattern; -import lia.util.net.copy.PosixFSFileChannelProviderFactory; - /** * Configuration params for FDT * @@ -115,6 +115,7 @@ public class Config { private String[] fileList; private String[] remappedFileList; private String destDir; + private String listFilesFrom; private String sIP; private String dIP; private final String sshKeyPath; @@ -343,6 +344,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx destDir = Utils.getStringValue(configMap, "-d", null); sIP = Utils.getStringValue(configMap, "-sIP", null); dIP = Utils.getStringValue(configMap, "-dIP", null); + listFilesFrom = Utils.getStringValue(configMap, "-ls", null); bComputeMD5 = (configMap.get("-md5") != null); sshKeyPath = Utils.getStringValue(configMap, "-sshKey", null); @@ -353,7 +355,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx lastParams.add("/dev/zero"); } - if ((hostname != null) && ((destDir == null) || (destDir.length() == 0))) { + if ((hostname != null) && (((destDir == null) || (destDir.length() == 0)) && listFilesFrom == null)) { throw new IllegalArgumentException("No destination specified"); } @@ -549,10 +551,21 @@ private String[] getLogFiles(String sessionID) { return new String[] {"/tmp/"+sessionID+".log"}; } + public String getListFilesFrom() { + return listFilesFrom; + } + + public void setListFilesFrom(String listFilesFrom) { + this.listFilesFrom = listFilesFrom; + } + private String getFDTMode(Map configMap) { if (configMap.get("-coord") != null) { return "coordinator"; } + else if (configMap.get("-ls") != null) { + return "list files"; + } return (hostname == null) && (configMap.get("SCPSyntaxUsed") == null) ? "server" : "client"; } @@ -813,6 +826,10 @@ public boolean isCoordinatorMode() { return isCoordinatorMode; } + public boolean isListFilesMode() { + return listFilesFrom != null && configMap.get("-ls") != null; + } + public boolean isRetrievingLogFile() { return isRetrievingLogFile || configMap.containsKey("-sID"); } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 43257fc..85972db 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -6,10 +6,7 @@ import lia.util.net.copy.monitoring.ConsoleReportingTask; import lia.util.net.copy.monitoring.FDTInternalMonitoringTask; import lia.util.net.copy.monitoring.lisa.LISAReportingTask; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.CtrlMsg; -import lia.util.net.copy.transport.FDTProcolException; -import lia.util.net.copy.transport.FDTSessionConfigMsg; +import lia.util.net.copy.transport.*; import lia.util.net.copy.transport.internal.SelectionManager; import java.io.File; @@ -161,8 +158,16 @@ protected synchronized void internalClose() throws Exception { ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); UUID sessionID = cc.sendCoordinatorMessage(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, new FDTSessionConfigMsg(config))); // wait for remote config - logger.log(Level.INFO,"Message sent to: " + config.getHostName() +":"+ config.getPort() + " Remote Job Session ID: " + sessionID); + logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " Remote Job Session ID: " + sessionID); System.exit(0); + } else if (config.isListFilesMode()) { + ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); + List filesInDir = cc.sendListFilesMessage(new CtrlMsg(CtrlMsg.LIST_FILES, new FDTListFilesMsg(config.getListFilesFrom()))); + // wait for remote config + logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort()); + printOutResults(filesInDir); + System.exit(0); + } else { if (config.getHostName() != null) { // role == client // the session manager will check the "pull/push" mode and start the FDTSession @@ -179,6 +184,17 @@ protected synchronized void internalClose() throws Exception { } } + private static void printOutResults(List filesInDir) { + StringBuilder sb = new StringBuilder(); + sb.append("\r\n"); + for (String entry : filesInDir) { + sb.append(entry); + sb.append("\r\n"); + } + String files = sb.toString(); + logger.info(files); + } + private static void printHelp() { logger.log(Level.INFO, Config.getUsage()); } @@ -220,8 +236,7 @@ private static int doWork() { logger.log(Level.WARNING, "FDT Got exception in main loop", t); } } - } - catch (Exception ex) { + } catch (Exception ex) { e = ex; } finally { try { @@ -515,10 +530,10 @@ private static void initConfig(Map argsMap, String logLevel) { try { Config.initInstance(argsMap); } catch (InvalidFDTParameterException e) { - logger.log(Level.WARNING,"Invalid parameters supplied: " + e.getMessage(), e); + logger.log(Level.WARNING, "Invalid parameters supplied: " + e.getMessage(), e); System.exit(1); } catch (Throwable t1) { - logger.log(Level.WARNING,"got exception parsing command args", t1); + logger.log(Level.WARNING, "got exception parsing command args", t1); System.exit(1); } config = Config.getInstance(); @@ -588,14 +603,15 @@ private static void updateIfAvailable(Map argsMap, boolean noLoc private static void checkMainParams(Map argsMap) { if (argsMap.get("-c") != null) { - if (argsMap.get("-d") == null && argsMap.get("-nettest") == null) { + if (argsMap.get("-d") == null && argsMap.get("-nettest") == null && argsMap.get("-ls") == null) { throw new IllegalArgumentException("No destination specified"); } @SuppressWarnings("unchecked") final List lParams = (List) argsMap.get("LastParams"); if ((argsMap.get("-nettest") == null && argsMap.get("-fl") == null - && (lParams == null || lParams.size() == 0) && argsMap.get("Files") == null) && argsMap.get("-sID") == null) { + && (lParams == null || lParams.size() == 0) && argsMap.get("Files") == null) + && argsMap.get("-sID") == null && argsMap.get("-ls") == null) { throw new IllegalArgumentException("No source specified"); } } diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index ba2d01a..dd29e96 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -3,8 +3,18 @@ */ package lia.util.net.copy; +import lia.util.net.common.Config; +import lia.util.net.common.Utils; +import lia.util.net.copy.monitoring.FDTSessionMonitoringTask; +import lia.util.net.copy.monitoring.lisa.LisaCtrlNotifier; +import lia.util.net.copy.transport.*; + import java.io.File; +import java.io.IOException; import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.attribute.PosixFileAttributes; import java.util.*; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -15,12 +25,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.Utils; -import lia.util.net.copy.monitoring.FDTSessionMonitoringTask; -import lia.util.net.copy.monitoring.lisa.LisaCtrlNotifier; -import lia.util.net.copy.transport.*; - /** * Base class for both FDT Reader/Writer sessions * @@ -29,7 +33,9 @@ public abstract class FDTSession extends IOSession implements ControlChannelNotifier, Comparable, Accountable, LisaCtrlNotifier { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final Logger logger = Logger.getLogger(FDTSession.class.getName()); private static final String LISA_RATE_LIMIT_CMD = "limit"; @@ -66,6 +72,8 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public static final int COORDINATOR_MSG_RCVD = 1 << 9; + public static final int LIST_FILES_MSG_RCVD = 1 << 10; + protected AtomicLong totalProcessedBytes; protected AtomicLong totalUtilBytes; @@ -75,9 +83,9 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti // should be 0 in case everything works fine and !=0 in case of an error protected short currentStatus; - protected static final String[] FDT_SESION_STATES = { "UNINITIALIZED", "STARTED", "INIT_CONF_SENT", + protected static final String[] FDT_SESION_STATES = {"UNINITIALIZED", "STARTED", "INIT_CONF_SENT", "INIT_CONF_RCV", "FINAL_CONF_SENT", "FINAL_CONF_RCV", "START_SENT", "START_RCV", "TRANSFERING", "END_SENT", - "END_RCV" }; + "END_RCV"}; protected Map> partitionsMap; @@ -407,6 +415,11 @@ public final void notifyCtrlMsg(ControlChannel controlChannel, Object o) throws handleCoordinatorMessage(ctrlMsg); break; } + case CtrlMsg.LIST_FILES: { + setCurrentState(LIST_FILES_MSG_RCVD); + handleListFilesMessage(ctrlMsg); + break; + } default: { FDTProcolException fpe = new FDTProcolException("Illegal CtrlMsg tag [ " + ctrlMsg.tag + " ]"); fpe.fillInStackTrace(); @@ -437,18 +450,31 @@ private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { final ControlChannel ctrlChann = this.controlChannel; FDTSession session = FDTSessionManager.getInstance().addFDTClientSession(); ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, session.controlChannel.fdtSessionID())); - } - catch (Exception ex) - { + } catch (Exception ex) { logger.log(Level.WARNING, "Exception while handling coordinator message", ex); - } - finally { + } finally { //Restore old config. this.setClosed(true); config.setConfigMap(oldConfig); } } + private void handleListFilesMessage(CtrlMsg ctrlMsg) { + + logger.log(Level.INFO, "[ FDTSession ] [ handleListFilesMessage ( " + ctrlMsg.message.toString() + " )"); + try { + FDTListFilesMsg lsMsg = (FDTListFilesMsg) ctrlMsg.message; + config.setListFilesFrom(lsMsg.listFilesFrom); + final ControlChannel ctrlChann = this.controlChannel; + lsMsg.filesInDir = getListOfFiles(); + ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.LIST_FILES, lsMsg)); + } catch (Exception ex) { + logger.log(Level.WARNING, "Exception while handling 'list files in dir' message", ex); + } finally { + this.setClosed(true); + } + } + protected void buildPartitionMap() { if (logger.isLoggable(Level.FINEST)) { @@ -639,4 +665,44 @@ public boolean isNetTest() { return isNetTest; } + public static List getListOfFiles() { + File[] filesList = new File(config.getListFilesFrom()).listFiles(); + List listOfFiles = new ArrayList<>(); + + if (filesList != null) { + for (File fileInDir : filesList) { + if (fileInDir.canRead()) { + listOfFiles.add(getFileListEntry(fileInDir)); + } + } + } + return listOfFiles; + } + + private static String getFileListEntry(File fileInDir) { + + StringBuilder sb = new StringBuilder(); + try { + PosixFileAttributes fa = Files.readAttributes(fileInDir.toPath(), PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + sb.append(fa.isDirectory() ? "d" : fa.isSymbolicLink() ? "l" : fa.isRegularFile() ? "f" : "-"); + sb.append(fileInDir.canRead() ? "r" : "-"); + sb.append(fileInDir.canWrite() ? "w" : "-"); + sb.append(fileInDir.canExecute() ? "x" : "-"); + sb.append("\t"); + sb.append(fa.owner()); + sb.append(fa.owner().getName().length() < 4 ? "\t\t" : "\t"); + sb.append(fa.group()); + sb.append(fa.group().getName().length() < 4 ? "\t\t" : "\t"); + sb.append(fa.size()); + sb.append(String.valueOf(fa.size()).length() < 4 ? "\t\t" : "\t"); + sb.append(fa.lastModifiedTime().toString()); + sb.append("\t"); + sb.append(fa.isDirectory() ? fileInDir.getName() + "/" : fileInDir.getName()); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to get file attributes", e); + } + logger.info(sb.toString()); + return sb.toString(); + } + } diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index 1a558d5..d32db78 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -13,10 +13,7 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.UUID; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -379,6 +376,30 @@ public void sendSessionIDToCoordinator(CtrlMsg ctrlMsg) { } + public List sendListFilesMessage(CtrlMsg ctrlMsg) throws IOException { + + logger.log(Level.INFO, "[ ControlChannel ] [ sendListFilesMessage ]"); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); + } + } + try { + sendMsgImpl(ctrlMsg); + CtrlMsg newCtrlMsg = getResponse(); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] [ sendListFilesMessage ] listing files on remote machine: " + newCtrlMsg.message.toString()); + } + FDTListFilesMsg msg = (FDTListFilesMsg) newCtrlMsg.message; + return msg.filesInDir; + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to retrieve response from server", e); + } + cleanup(); + return null; + } + public UUID sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { logger.log(Level.INFO, "[ ControlChannel ] [ sendCoordinatorMessage ]"); @@ -388,11 +409,9 @@ public UUID sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { Thread.dumpStack(); } } - try { sendMsgImpl(ctrlMsg); - - CtrlMsg newCtrlMsg = getRemoteJobSessionID(); + CtrlMsg newCtrlMsg = getResponse(); logger.info("Remote job session ID: " + newCtrlMsg.message.toString()); return (UUID) newCtrlMsg.message; @@ -403,7 +422,7 @@ public UUID sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { return null; } - private CtrlMsg getRemoteJobSessionID() throws Exception { + private CtrlMsg getResponse() throws Exception { Exception t = null; CtrlMsg newCtrlMsg = null; for (int i = 1; i <= MAX_RETRIES; i++) { @@ -412,7 +431,7 @@ private CtrlMsg getRemoteJobSessionID() throws Exception { return newCtrlMsg; } catch (Exception e) { t = e; - Thread.sleep(i * CONNECT_TIMEOUT/2); + Thread.sleep(i * CONNECT_TIMEOUT / 2); } finally { if (newCtrlMsg == null && i == MAX_RETRIES) { throw t; diff --git a/src/lia/util/net/copy/transport/CtrlMsg.java b/src/lia/util/net/copy/transport/CtrlMsg.java index 3027ae6..4f7726b 100644 --- a/src/lia/util/net/copy/transport/CtrlMsg.java +++ b/src/lia/util/net/copy/transport/CtrlMsg.java @@ -65,11 +65,14 @@ public class CtrlMsg implements Serializable { /** message types designated for third party copy feature */ public static final int THIRD_PARTY_COPY = 13; + + /** message types designated for list files feature */ + public static final int LIST_FILES = 14; private static final String[] CTRL_MSG_TAGS = new String[] { "KEEP_ALIVE_MSG", "PROTOCOL_VERSION", "SESSION_ID", "SESSION_TYPE", "INIT_FDT_CONF", "PING_SESSION", "INIT_FDTSESSION_CONF", "FINAL_FDTSESSION_CONF", "FINISHED_FILE_SESSIONS", "START_SESSION", "END_SESSION", - "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY" + "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES" }; /** diff --git a/src/lia/util/net/copy/transport/FDTListFilesMsg.java b/src/lia/util/net/copy/transport/FDTListFilesMsg.java new file mode 100644 index 0000000..9606f63 --- /dev/null +++ b/src/lia/util/net/copy/transport/FDTListFilesMsg.java @@ -0,0 +1,31 @@ +/* + * $Id$ + */ +package lia.util.net.copy.transport; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * The list files msg between FDT peers. + * @author Raimondas Sirvinskas + */ +public class FDTListFilesMsg implements Serializable { + + public String listFilesFrom; + public List filesInDir; + + public FDTListFilesMsg(String listFilesFrom) { + this.listFilesFrom = listFilesFrom; + this.filesInDir = new ArrayList<>(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("\n listFilesFrom: ").append(listFilesFrom); + sb.append("\n filesInDir: ").append(filesInDir.toString()); + return sb.toString(); + } +} From 1de18c67d192b38cedec350e279b67b4e58206ad Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Sun, 20 Aug 2017 07:55:50 +0300 Subject: [PATCH 08/55] One line log entry formatting (cherry picked from commit ae29937) --- src/lia/util/net/common/Utils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index b6eda5a..540ef9d 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1822,6 +1822,7 @@ public static void initLogger(String level, File logFile, Properties localProps) loggingProps.put("handlers", "java.util.logging.ConsoleHandler"); loggingProps.put("java.util.logging.ConsoleHandler.level", "FINEST"); loggingProps.put("java.util.logging.ConsoleHandler.formatter", "java.util.logging.SimpleFormatter"); + loggingProps.put("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); } if (logFile != null) { @@ -1832,6 +1833,7 @@ public static void initLogger(String level, File logFile, Properties localProps) loggingProps.put("handlers", "java.util.logging.FileHandler"); loggingProps.put("java.util.logging.FileHandler.level", "FINEST"); loggingProps.put("java.util.logging.FileHandler.formatter", "java.util.logging.SimpleFormatter"); + loggingProps.put("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); loggingProps.put("java.util.logging.FileHandler.pattern", "" + logFile); loggingProps.put("java.util.logging.FileHandler.append", "true"); From acfa1b901148142080653b4fa85d184290f511ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Wed, 30 Aug 2017 21:10:31 +0300 Subject: [PATCH 09/55] Third party copy feature In order to use third party copy feature with FDT there have to be two FDT launched in agent mode. In Agent mode the FDT will start listening for incoming client connections on . In Agent mode the client will listen for coordinator message with task. After receiving coordinator message Agent will try to send message to destination Agent requesting to open socket for transfer session. Destination Agent will take one transfer port from pool and open port for that session and then informs source Agent that transfer job can be started. After finishing task Agent will close transfer port and return it to the transfer ports pool. Launching FDT as Agent example: java -jar fdt.jar -tp -p -agent Sending coordinator message to the agent: java -jar fdt.jar -dIP -dp -sIP -p -d -fl -coord (cherry picked from commit 9d5ddbb) --- src/lia/util/net/common/AcceptableTask.java | 247 +++++++++++++++++ src/lia/util/net/common/Config.java | 192 +++++++++++--- src/lia/util/net/common/Utils.java | 111 +++++--- src/lia/util/net/copy/FDT.java | 132 ++++++--- src/lia/util/net/copy/FDTReaderSession.java | 25 +- src/lia/util/net/copy/FDTServer.java | 251 ++---------------- src/lia/util/net/copy/FDTSession.java | 100 ++++++- src/lia/util/net/copy/FDTSessionManager.java | 27 +- src/lia/util/net/copy/FDTWriterSession.java | 53 ++-- .../net/copy/gui/ClientSessionManager.java | 6 +- .../net/copy/transport/ControlChannel.java | 45 +++- src/lia/util/net/copy/transport/CtrlMsg.java | 5 +- .../copy/transport/FDTSessionConfigMsg.java | 5 + 13 files changed, 788 insertions(+), 411 deletions(-) create mode 100644 src/lia/util/net/common/AcceptableTask.java diff --git a/src/lia/util/net/common/AcceptableTask.java b/src/lia/util/net/common/AcceptableTask.java new file mode 100644 index 0000000..456e340 --- /dev/null +++ b/src/lia/util/net/common/AcceptableTask.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2017 NoMagic, Inc. All Rights Reserved. + */ +package lia.util.net.common; + +import lia.util.net.copy.FDTServer; +import lia.util.net.copy.FDTSessionManager; +import lia.util.net.copy.transport.ControlChannel; +import lia.util.net.copy.transport.gui.ServerSessionManager; + +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Raimondas Sirvinskas + * @version 1.0 + */ +public final class AcceptableTask implements Runnable { + + private static final Logger logger = Logger.getLogger(AcceptableTask.class.getName()); + + private static final Config config = Config.getInstance(); + + private static final FDTSessionManager fdtSessionManager = FDTSessionManager.getInstance(); + + final SocketChannel sc; + + final Socket s; + + public AcceptableTask(final SocketChannel sc) throws IOException { + + if (sc == null) { + throw new NullPointerException("SocketChannel cannot be null in AcceptableTask"); + } + + if (sc.socket() == null) { + throw new NullPointerException("Null Socket for SocketChannel in AcceptableTask"); + } + + this.sc = sc; + this.s = sc.socket(); + } + + public void run() { + + if (!FDTServer.filterSourceAddress(s)) + return; + + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, " AcceptableTask for " + sc + " STARTED!"); + } + final String sdpConfFlag = System.getProperty("com.sun.sdp.conf"); + final boolean bSDP = (sdpConfFlag != null && !sdpConfFlag.isEmpty()); + if (!bSDP) { + try { + s.setKeepAlive(true); + } catch (Throwable t) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot set KEEP_ALIVE for " + sc + + ". Will ignore the error. Contact your sys admin.", t); + } + + try { + // IPTOS_LOWCOST (0x02) IPTOS_RELIABILITY (0x04) IPTOS_THROUGHPUT (0x08) IPTOS_LOWDELAY (0x10) + s.setTrafficClass(0x04 | 0x08 | 0x010); + } catch (Throwable t) { + logger.log(Level.WARNING, + "[ FDTServer ] [ AcceptableTask ] Cannot set traffic class for " + + sc + + "[ IPTOS_RELIABILITY (0x04) | IPTOS_THROUGHPUT (0x08) | IPTOS_LOWDELAY (0x10) ] Will ignore the error. Contact your sys admin.", + t); + } + } + + final ByteBuffer firstByte = ByteBuffer.allocate(1); + final ByteBuffer clientIDBuff = ByteBuffer.allocate(16); + + Selector tmpSelector = null; + SelectionKey sk = null; + + configureSocket(firstByte, clientIDBuff, tmpSelector, sk); + } + + private void configureSocket(ByteBuffer firstByte, ByteBuffer clientIDBuff, Selector tmpSelector, SelectionKey sk) { + UUID clientSessionID; + try { + + int count = -1; + while (firstByte.hasRemaining()) { + count = sc.read(firstByte); + if (count < 0) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Unable to read header for socket [ " + s + + " ] The stream will be closed."); + try { + sc.close(); + } catch (Throwable ignored) { + // ignore + } + return; + } + + if (firstByte.hasRemaining()) { + tmpSelector = Selector.open(); + sk = sc.register(tmpSelector, SelectionKey.OP_READ); + tmpSelector.select(); + } + } + + if (sk != null) { + sk.cancel(); + sk = null; + } + + firstByte.flip(); + final byte firstB = firstByte.get(); + + switch (firstB) { + + // Control channel + case 0: { + if (config.isGSIModeEnabled() || config.isGSISSHModeEnabled()) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Got a remote control channel [ " + s + + " ] but in GSI mode ... will be rejected."); + try { + byte[] key = {0}; + sc.write(ByteBuffer.wrap(key)); + sc.close(); + } catch (Throwable ignored) { + // ignore + } + return; + } + + sc.configureBlocking(true); + ControlChannel ct = null; + + try { + ct = new ControlChannel(s, fdtSessionManager); + // fdtSessionID = ct.fdtSessionID(); + fdtSessionManager.addFDTClientSession(ct); + } catch (Throwable t) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot instantiate ControlChannel", t); + ct = null; + } + + if (ct != null) { + new Thread(ct, "ControlChannel thread for [ " + s.getInetAddress() + ":" + s.getPort() + " ]").start(); + } + break; + } + + // Worker channel + case 1: { + if (config.isBlocking()) { + sc.configureBlocking(true); + } else { + sc.configureBlocking(false); + } + + while (clientIDBuff.hasRemaining()) { + count = sc.read(clientIDBuff); + if (count < 0) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Unable to read clientID. The stream will be closed"); + Utils.closeIgnoringExceptions(sc); + return; + } + + if (clientIDBuff.hasRemaining()) { + // THIS CANNOT?!? happen in blocking mode ( JVM should throw exception if this happens + // ... but ) + if (config.isBlocking()) { + logger.log(Level.WARNING, + "[ FDTServer ] [ AcceptableTask ] Blocking mode ... unable to read clientID. The stream will be closed"); + Utils.closeIgnoringExceptions(sc); + return; + } + } else { + // everything has been read + break; + } + + if (tmpSelector == null) { + tmpSelector = Selector.open(); + } + + if (!config.isBlocking()) { + sk = sc.register(tmpSelector, SelectionKey.OP_READ); + tmpSelector.select(); + } + }// while + + if (sk != null) { + sk.cancel(); + } + + clientIDBuff.flip(); + clientSessionID = new UUID(clientIDBuff.getLong(), clientIDBuff.getLong()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "[ FDTServer ] [ AcceptableTask ] New socket from clientID: " + clientSessionID); + } + + fdtSessionManager.addWorker(clientSessionID, sc); + break; + } + + // Ping channel - RTT + case 2: { + break; + } + + // GUI special (sounds special, doesn't it) channel + case 3: { + sc.configureBlocking(true); + ServerSessionManager sm = null; + try { + sm = new ServerSessionManager(s); + new Thread(sm, "GUIControlChannel thread for [ " + s.getInetAddress() + ":" + s.getPort() + " ]").start(); + } catch (Throwable t) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot instantiate GUI ControlChannel", t); + } + break; + } + + default: { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Unable to understand initial cookie: " + firstB); + Utils.closeIgnoringExceptions(s); + return; + } + } + + } catch (Throwable t) { + logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Exception: ", t); + Utils.closeIgnoringExceptions(sc); + } finally { + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, " AcceptableTask for " + s + " FINISHED!"); + } + Utils.closeIgnoringExceptions(tmpSelector); + } + } +} diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 8c9bdcd..f4422d3 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -7,12 +7,15 @@ import java.io.BufferedReader; import java.io.FileReader; +import java.io.IOException; import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.Socket; import java.net.SocketException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,7 +29,9 @@ */ public class Config { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final Logger logger = Logger.getLogger("lia.util.net.common.Config"); // The size of the buffer which is sent over the wire! // TODO make this a parameter @@ -48,18 +53,19 @@ public class Config { NETWORK_BUFF_LEN_SIZE = defaultMSSSize; } + // env props which will be sent to remote peer - private final static String[] exportedSysProps = { "user.name", "user.home", "user.dir", "file.separator", - "file.encoding", "path.separator" }; + private final static String[] exportedSysProps = {"user.name", "user.home", "user.dir", "file.separator", + "file.encoding", "path.separator"}; // public static final String SINGLE_CMDLINE_ARGS[] = { "-S", "-pull", "-N", "-gsi", "-bio", "-r", "-fbs", "-ll", // "-loop", "-enableLisaRestart", "-md5", "-printStats", "-gsissh", "-noupdates", "-silent"}; - public static final String[] SINGLE_CMDLINE_ARGS = { "-v", "-vv", "-vvv", "-loop", "-r", "-pull", "-printStats", - "-N", "-bio", "-gsi", "-gsissh", "-notmp", "-nolock", "-nolocks", "-nettest", "-genb" }; - public static final String[] VALUE_CMDLINE_ARGS = { "-bs", "-P", "-ss", "-limit", "-preFilters", "-postFilters", + public static final String[] SINGLE_CMDLINE_ARGS = {"-v", "-vv", "-vvv", "-loop", "-r", "-pull", "-printStats", + "-N", "-bio", "-gsi", "-gsissh", "-notmp", "-nolock", "-nolocks", "-nettest", "-genb"}; + public static final String[] VALUE_CMDLINE_ARGS = {"-bs", "-P", "-ss", "-limit", "-preFilters", "-postFilters", "-monID", "-ms", "-c", "-p", "-sshp", "-gsip", "-iof", "-sn", "-rCount", "-wCount", "-pCount", "-d", - "-writeMode", "-lisa_rep_delay", "-apmon_rep_delay", "-fl", "-reportDelay", "-ka" }; - public static final String POSSIBLE_VALUE_CMDLINE_ARGS[] = { "-enable_apmon", "-lisafdtclient", "-lisafdtserver", - "-f", "-F", "-h", "-H", "--help", "-help," + "-u", "-U", "--update", "-update" }; + "-writeMode", "-lisa_rep_delay", "-apmon_rep_delay", "-fl", "-reportDelay", "-ka"}; + public static final String POSSIBLE_VALUE_CMDLINE_ARGS[] = {"-enable_apmon", "-lisafdtclient", "-lisafdtserver", + "-f", "-F", "-h", "-H", "--help", "-help," + "-u", "-U", "--update", "-update"}; /** * used in conjuction with -fl to delimit the eventual destination file name @@ -90,6 +96,7 @@ public class Config { private boolean isPullMode = false; private boolean isCoordinatorMode; private boolean isRetrievingLogFile; + private boolean isThirdPartyCopyAgent; // this should be used for syncronizations at application level () public static final Object BIG_FDTAPP_LOCK = new Object(); // default is 4 @@ -111,6 +118,9 @@ public class Config { private int portNo; private int portNoGSI; private int portNoSSH; + private int destPort; + private int remoteTransferPort; + private ArrayBlockingQueue transportPorts; private final boolean isStandAlone; private String[] fileList; private String[] remappedFileList; @@ -160,6 +170,8 @@ public class Config { private final boolean isGenTest; private final long keepAliveDelayNanos; private final FileChannelProviderFactory fileChannelProviderFactory; + private Map sessionPortMap = new HashMap<>(); + private Map> sessionSocketMap = new HashMap<>(); private static final int getMinMTU() { int retMTU = 1500; @@ -216,8 +228,7 @@ private static final int getMinMTU() { /** * @param configMap - * @throws InvalidFDTParameterException - * if incorrect values are supplied for parameters + * @throws InvalidFDTParameterException if incorrect values are supplied for parameters */ private Config(final Map configMap) throws InvalidFDTParameterException { this.configMap = configMap; @@ -301,7 +312,9 @@ private Config(final Map configMap) throws InvalidFDTParameterEx hostname = null; } else { isPullMode = (configMap.get("-pull") != null) || (configMap.get("-sID") != null); - configMap.put("-pull", ""); + if (isPullMode) { + configMap.put("-pull", ""); + } } final long ka = Utils.getLongValue(configMap, "-ka", TimeUnit.NANOSECONDS.toSeconds(DEFAULT_KEEP_ALIVE_NANOS)); @@ -309,8 +322,10 @@ private Config(final Map configMap) throws InvalidFDTParameterEx configMap.put("-ka", String.valueOf(TimeUnit.NANOSECONDS.toSeconds(this.keepAliveDelayNanos))); portNo = Utils.getIntValue(configMap, "-p", DEFAULT_PORT_NO); - List transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_PORT_NO); + transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_PORT_NO); isCoordinatorMode = Boolean.getBoolean("coordinator"); + isThirdPartyCopyAgent = (configMap.get("-agent") != null); + portNoGSI = Utils.getIntValue(configMap, "-gsip", DEFAULT_PORT_NO_GSI); portNoSSH = Utils.getIntValue(configMap, "-sshp", DEFAULT_PORT_NO_SSH); IORetryFactor = Utils.getIntValue(configMap, "-iof", 1); @@ -344,6 +359,8 @@ private Config(final Map configMap) throws InvalidFDTParameterEx destDir = Utils.getStringValue(configMap, "-d", null); sIP = Utils.getStringValue(configMap, "-sIP", null); dIP = Utils.getStringValue(configMap, "-dIP", null); + destPort = Utils.getIntValue(configMap, "-dp", -1); + remoteTransferPort = -1; listFilesFrom = Utils.getStringValue(configMap, "-ls", null); bComputeMD5 = (configMap.get("-md5") != null); sshKeyPath = Utils.getStringValue(configMap, "-sshKey", null); @@ -504,13 +521,11 @@ private Config(final Map configMap) throws InvalidFDTParameterEx Utils.closeIgnoringExceptions(fr); Utils.closeIgnoringExceptions(br); } - } - else if (configMap.get("-sID") != null) - { - String sessionID = (String)configMap.get("-sID"); + } else if (configMap.get("-sID") != null) { + String sessionID = (String) configMap.get("-sID"); String[] logFiles = getLogFiles(sessionID); remappedFileList = logFiles; - fileList = logFiles; + fileList = logFiles; } } else { configMap.remove("-fl"); @@ -537,8 +552,8 @@ else if (configMap.get("-sID") != null) + fileList[i] : " remapped to: " + remappedFileList[i]).append("\n"); } logger.log(Level.FINE, sb.toString()); - logger.log(Level.FINE, "Remote destination directory: {0}\nRemote host: {1} port: {2}", new Object[] { - destDir, hostname, portNo }); + logger.log(Level.FINE, "Remote destination directory: {0}\nRemote host: {1} port: {2}", new Object[]{ + destDir, hostname, portNo}); } } else {// server mode if (logger.isLoggable(Level.FINE)) { @@ -548,7 +563,7 @@ else if (configMap.get("-sID") != null) } private String[] getLogFiles(String sessionID) { - return new String[] {"/tmp/"+sessionID+".log"}; + return new String[]{"/tmp/" + sessionID + ".log"}; } public String getListFilesFrom() { @@ -562,9 +577,10 @@ public void setListFilesFrom(String listFilesFrom) { private String getFDTMode(Map configMap) { if (configMap.get("-coord") != null) { return "coordinator"; - } - else if (configMap.get("-ls") != null) { + } else if (configMap.get("-ls") != null) { return "list files"; + } else if (configMap.get("-agent") != null) { + return "agent worker"; } return (hostname == null) && (configMap.get("SCPSyntaxUsed") == null) ? "server" : "client"; } @@ -717,18 +733,15 @@ public int getSSHPort() { return portNoSSH; } - public void setPortNo(int port) - { + public void setPortNo(int port) { this.portNo = port; } - public void setGSIPort(int port) - { + public void setGSIPort(int port) { this.portNoGSI = port; } - public void setSSHPort(int port) - { + public void setSSHPort(int port) { this.portNoSSH = port; } @@ -764,6 +777,103 @@ public String getDestinationDir() { return destDir; } + public void setDestinationPort(int destPort) { + this.destPort = destPort; + } + + public int getDestinationPort() { + return destPort; + } + + public void setRemoteTransferPort(int remoteTransferPort) { + this.remoteTransferPort = remoteTransferPort; + } + + public void registerTransferPortForSession(int newTransferPort, String sessionID) { + this.sessionPortMap.put(sessionID, newTransferPort); + } + + public int getNewRemoteTransferPort() { + try { + if (!transportPorts.isEmpty()) { + int rtp = this.transportPorts.poll(20, TimeUnit.SECONDS); + System.out.println("Took new remote transfer port " + rtp); + return rtp; + } + } catch (Exception e) { + if (transportPorts.size() == 0) { + logger.log(Level.WARNING, "No transfer ports defined or no free transfer ports left...", e); + } else { + logger.log(Level.WARNING, "Failed to retrieve remote transfer port", e); + } + } + return -1; + } + + public void setSessionSocket(ServerSocketChannel ssc, ServerSocket ss, SocketChannel sc, Socket s, int port) { + List socks = new ArrayList<>(); + socks.add(ssc); + socks.add(ss); + socks.add(sc); + socks.add(s); + sessionSocketMap.put(port, socks); + } + + public void releaseRemoteTransferPort(String sessionID) { + if (sessionPortMap.keySet().contains(sessionID)) { + logger.log(Level.FINER, "Trying to release transfer port from session " + sessionID); + int sessionPort = sessionPortMap.get(sessionID); + if (sessionPort > 0) { + try { + transportPorts.put(sessionPort); + sessionPortMap.remove(sessionID); + closeSessionRelatedSocks(sessionSocketMap.get(sessionPort)); + } catch (InterruptedException e) { + logger.log(Level.WARNING, "Failed to release remote transfer port: " + remoteTransferPort, e); + } + } + } + } + + private static void closeSessionRelatedSocks(List socks) { + if (socks != null) { + for (Object o : socks) { + if (o instanceof ServerSocketChannel) { + try { + ((ServerSocketChannel) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close ServerSocketChannel", e); + } + } + if (o instanceof ServerSocket) { + try { + ((ServerSocket) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close ServerSocket", e); + } + } + if (o instanceof SocketChannel) { + try { + ((SocketChannel) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close SocketChannel", e); + } + } + if (o instanceof Socket) { + try { + ((Socket) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close Socket", e); + } + } + } + } + } + + public int getRemoteTransferPort() { + return remoteTransferPort; + } + public String getSourceIP() { return sIP; } @@ -785,7 +895,6 @@ public void setLisaPort(int lisaPort) { } - // TODO - As param ... public int getNumberOfSelectors() { return selectorsNo; @@ -813,10 +922,19 @@ public void setCoordinatorMode(boolean coordinatorMode) { } } + public void setThirdPartyCopyAgent(boolean isThirdPartyCopyAgent) { + this.isThirdPartyCopyAgent = isThirdPartyCopyAgent; + if (isThirdPartyCopyAgent) { + this.configMap.put("-agent", true); + } else { + this.configMap.remove("-agent"); + } + } + public void setRetrievingLogFile(Object sessionID) { this.isRetrievingLogFile = sessionID != null; if (isRetrievingLogFile) { - this.configMap.put("-sID", (String)sessionID); + this.configMap.put("-sID", (String) sessionID); } else { this.configMap.remove("-sID"); } @@ -996,4 +1114,8 @@ public String getLogLevel() { public FileChannelProviderFactory getFileChannelProviderFactory() { return this.fileChannelProviderFactory; } + + public boolean isThirdPartyCopyAgent() { + return this.isThirdPartyCopyAgent; + } } diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index 540ef9d..8525011 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -3,7 +3,11 @@ import apmon.ApMon; import com.sun.istack.internal.NotNull; import lia.util.net.copy.FDT; +import lia.util.net.copy.FDTServer; +import lia.util.net.copy.FDTSessionManager; import lia.util.net.copy.FileBlock; +import lia.util.net.copy.transport.ControlChannel; +import lia.util.net.copy.transport.CtrlMsg; import lia.util.net.copy.transport.internal.FDTSelectionKey; import org.json.JSONArray; import org.json.JSONException; @@ -12,10 +16,7 @@ import java.io.*; import java.net.*; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; +import java.nio.channels.*; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.*; @@ -1738,8 +1739,8 @@ static InetAddress getLoopbackAddress() { return localhost; } - public static List getTransportPortsValue(Map configMap, String key, int defaultPortNo) { - List transportPorts = new ArrayList(); + public static ArrayBlockingQueue getTransportPortsValue(Map configMap, String key, int defaultPortNo) { + ArrayBlockingQueue transportPorts = new ArrayBlockingQueue<>(10); int i=0; Object obj = configMap.get(key); if (obj == null || obj.toString().isEmpty()) @@ -1748,31 +1749,14 @@ public static List getTransportPortsValue(Map configMap return transportPorts; } String tp = obj.toString(); - if (tp.isEmpty() || tp.replace(",","").isEmpty()) - { - transportPorts.add(defaultPortNo); - return transportPorts; - } - else - { - - - - - int n[]=new int[10];//for integer array of numbers - - StringTokenizer stk=new StringTokenizer(tp,","); - String s[]=new String[10];//for String array of numbers - while(stk.hasMoreTokens()) - { - s[i]=stk.nextToken(); - transportPorts.add(Integer.parseInt(s[i]));//Converting into Integer - i++; - } - } - for(i=0;i 0) { + final FDTServer theServer = new FDTServer(transferPort); + theServer.doWork(); + } else { + logger.warning("There are no free transfer ports at this moment, please try again later"); + waitForTask(executor, ss, sel); + } + } + + public static int getFDTTransferPort(Config config) throws Exception { + ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); + int transferPort = cc.sendTransferPortMessage(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, "rtp")); + // wait for remote config + logger.log(Level.INFO, "Got transfer port: " + config.getHostName() + ":" + transferPort); + return transferPort; + } + + private static void waitForTask(ExecutorService executor, ServerSocket ss, Selector sel) throws Exception { + try { + + for (; ; ) { + final int count = sel.select(2000); + + if (count == 0) + continue; + + Iterator it = sel.selectedKeys().iterator(); + while (it.hasNext()) { + final SelectionKey sk = it.next(); + it.remove(); + + if (!sk.isValid()) + continue;// closed socket ? + + if (sk.isAcceptable()) { + final ServerSocketChannel ssc = (ServerSocketChannel) sk.channel(); + final SocketChannel sc = ssc.accept(); + + try { + executor.execute(new AcceptableTask(sc)); + } catch (Throwable t) { + StringBuilder sb = new StringBuilder(); + sb.append("[ FDT ] [ waitForTask ] got exception in while sumbiting the AcceptableTask for SocketChannel: ").append(sc); + if (sc != null) { + sb.append(" Socket: ").append(sc.socket()); + } + sb.append(" Cause: "); + logger.log(Level.WARNING, sb.toString(), t); + } + } + } + } + } catch (Throwable t) { + logger.log(Level.WARNING, "[FDT] [ waitForTask ] Exception in main loop!", t); + throw new Exception(t); + } + + } } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 85972db..8448781 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -11,7 +11,14 @@ import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; @@ -62,6 +69,93 @@ protected synchronized void internalClose() throws Exception { FDT() throws Exception { // initialize monitoring, if requested + initApMon(); + + scheduleReportingTasks(); + + if (config.isCoordinatorMode()) { + ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); + String sessionID = cc.sendCoordinatorMessage(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, new FDTSessionConfigMsg(config))); + // wait for remote config + if (sessionID.equals("-1")) { + logger.log(Level.WARNING, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " but no free transfer ports available"); + } else + { + logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " Remote Job Session ID: " + sessionID); + } + System.exit(0); + } else if (config.isListFilesMode()) { + ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); + List filesInDir = cc.sendListFilesMessage(new CtrlMsg(CtrlMsg.LIST_FILES, new FDTListFilesMsg(config.getListFilesFrom()))); + // wait for remote config + logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort()); + printOutResults(filesInDir); + System.exit(0); + + } else if (config.isThirdPartyCopyAgent()) { + waitForTask(); + } else { + if (config.getHostName() != null) { // role == client + config.setRemoteTransferPort(Utils.getFDTTransferPort(config)); + FDTSessionManager.getInstance().addFDTClientSession(config.getRemoteTransferPort()); + } else { // is server + if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { + // this is really wrong ... I cannot be already initialized + throw new FDTProcolException("The buffer pool cannot be already initialized"); + } + final FDTServer theServer = new FDTServer(config.getPort()); + theServer.doWork(); + } + } + } + + private void waitForTask() throws Exception { + if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { + // this is really wrong ... It cannot be already initialized + throw new FDTProcolException("The buffer pool cannot be already initialized"); + } + + ExecutorService executor = null; + ServerSocketChannel ssc = null; + ServerSocket ss = null; + Selector sel = null; + try { + executor = Utils.getStandardExecService("[ Acceptable ServersThreadPool ] ", + 2, + 10, + new ArrayBlockingQueue(65500), + Thread.NORM_PRIORITY - 2); + ssc = ServerSocketChannel.open(); + ssc.configureBlocking(false); + ss = ssc.socket(); + ss.bind(new InetSocketAddress(config.getPort())); + sel = Selector.open(); + ssc.register(sel, SelectionKey.OP_ACCEPT); + System.out.println("READY"); + Utils.waitAndWork(executor, ss, sel, config); + } finally { + logger.log(Level.INFO, "[FDT] [ waitForTask ] main loop FINISHED!"); + // close all the stuff + Utils.closeIgnoringExceptions(ssc); + Utils.closeIgnoringExceptions(sel); + Utils.closeIgnoringExceptions(ss); + if (executor != null) { + executor.shutdown(); + } + } + } + + private static void scheduleReportingTasks() { + Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, + TimeUnit.SECONDS); + final long reportingTaskDelay = config.getReportingTaskDelay(); + if (reportingTaskDelay > 0) { + Utils.getMonitoringExecService().scheduleWithFixedDelay(ConsoleReportingTask.getInstance(), 0, + reportingTaskDelay, TimeUnit.SECONDS); + } + } + + private void initApMon() throws Exception { final String configApMonHosts = config.getApMonHosts(); if (configApMonHosts != null) { long lStart = System.currentTimeMillis(); @@ -145,45 +239,9 @@ protected synchronized void internalClose() throws Exception { long lEnd = System.currentTimeMillis(); logger.info("ApMon initialization took " + (lEnd - lStart) + " ms"); } - - Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, - TimeUnit.SECONDS); - final long reportingTaskDelay = config.getReportingTaskDelay(); - if (reportingTaskDelay > 0) { - Utils.getMonitoringExecService().scheduleWithFixedDelay(ConsoleReportingTask.getInstance(), 0, - reportingTaskDelay, TimeUnit.SECONDS); - } - - if (config.isCoordinatorMode()) { - ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); - UUID sessionID = cc.sendCoordinatorMessage(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, new FDTSessionConfigMsg(config))); - // wait for remote config - logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " Remote Job Session ID: " + sessionID); - System.exit(0); - } else if (config.isListFilesMode()) { - ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); - List filesInDir = cc.sendListFilesMessage(new CtrlMsg(CtrlMsg.LIST_FILES, new FDTListFilesMsg(config.getListFilesFrom()))); - // wait for remote config - logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort()); - printOutResults(filesInDir); - System.exit(0); - - } else { - if (config.getHostName() != null) { // role == client - // the session manager will check the "pull/push" mode and start the FDTSession - FDTSessionManager.getInstance().addFDTClientSession(); - } else { // is server - if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { - // this is really wrong ... I cannot be already initialized - throw new FDTProcolException("The buffer pool cannot be alredy initialized"); - } - - final FDTServer theServer = new FDTServer(); // ( because it's the only one ) - theServer.doWork(); - } - } } + private static void printOutResults(List filesInDir) { StringBuilder sb = new StringBuilder(); sb.append("\r\n"); diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index d10ae42..42fc866 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -39,7 +39,9 @@ */ public class FDTReaderSession extends FDTSession implements FileBlockProducer { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final Logger logger = Logger.getLogger(FDTReaderSession.class.getName()); private static final DiskReaderManager diskManager = DiskReaderManager.getInstance(); @@ -77,9 +79,9 @@ public class FDTReaderSession extends FDTSession implements FileBlockProducer { * * @throws Exception */ - public FDTReaderSession() throws Exception { - super(FDTSession.CLIENT); - Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); + public FDTReaderSession(int transferPort) throws Exception { + super(FDTSession.CLIENT, transferPort); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "CLIENT-" + sessionID + ".log"), new Properties()); final int rMul = Integer.getInteger("fdt.rQueueM", 2).intValue(); final int avProcProp = Integer.getInteger("fdt.avProc", 1).intValue(); final int avProcMax = Math.max(avProcProp, Utils.availableProcessors()); @@ -111,7 +113,7 @@ public FDTReaderSession() throws Exception { */ public FDTReaderSession(ControlChannel ctrlChannel) throws Exception { super(ctrlChannel, FDTSession.SERVER); - Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "SERVER-" + sessionID + ".log"), new Properties()); fileBlockQueue = new ArrayBlockingQueue(Utils.availableProcessors() * 2); readersMap = new TreeMap>(); @@ -265,8 +267,7 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi .newReaderFileChannelProvider(this); for (final String fName : newFileList) { - if (!new File(fName).exists()) - { + if (!new File(fName).exists()) { logger.warning("File listed in file list does not exist! " + fName); throw new IOException("File does not exist! " + fName); } @@ -743,6 +744,7 @@ private void finalCleanup() { "\n\n [ FDTReaderSession ] [ HANDLED ] exception returning buffers to pool \n\n ", t); } + setClosed(true); try { FDTSessionManager.getInstance().finishSession(sessionID, downMessage(), downCause()); } catch (Throwable ignore) { @@ -761,7 +763,8 @@ protected void internalClose() throws Exception { logger.log(Level.FINER, " [ FDTReaderSession ] enters internalClose downMsg: " + downMessage() + " , downCause: " + downCause()); } - + FDTSession session = FDTSessionManager.getInstance().getSession(sessionID); + session.setClosed(true); try { super.internalClose(); } catch (Throwable t) { @@ -868,12 +871,12 @@ public void handleStartFDTSession(CtrlMsg ctrlMsg) throws Exception { if (role == CLIENT) { sendCookie = false; transportProvider = new TCPSessionWriter(this, InetAddress.getByName(config.getHostName()), - config.getPort(), config.getSockNum()); + transferPort, config.getSockNum()); } else { transportProvider = new TCPSessionWriter(this); } - - controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.START_SESSION, null)); + config.registerTransferPortForSession(transferPort, sessionID.toString()); + controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.START_SESSION, transferPort)); // I'm still in sync ... if smth goes wrong the state will not be set setCurrentState(START_SENT); diff --git a/src/lia/util/net/copy/FDTServer.java b/src/lia/util/net/copy/FDTServer.java index 1edd183..1cb2e2a 100644 --- a/src/lia/util/net/copy/FDTServer.java +++ b/src/lia/util/net/copy/FDTServer.java @@ -1,9 +1,10 @@ package lia.util.net.copy; +import lia.gsi.FDTGSIServer; +import lia.util.net.common.*; + import java.net.InetSocketAddress; import java.net.ServerSocket; -import java.net.Socket; -import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -19,17 +20,9 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.gsi.FDTGSIServer; -import lia.util.net.common.AbstractFDTCloseable; -import lia.util.net.common.Config; -import lia.util.net.common.NetMatcher; -import lia.util.net.common.Utils; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.gui.ServerSessionManager; - /** * The accept() task will run in it's own thread ( including here the server ) - * + * * @author ramiro */ public class FDTServer extends AbstractFDTCloseable { @@ -46,14 +39,14 @@ public class FDTServer extends AbstractFDTCloseable { final Selector sel; - final int port; - //used by the AcceptableTask-s final ExecutorService executor; //signals the server stop final AtomicBoolean hasToRun; + UUID fdtSessionID; + static final class FDTServerMonitorTask implements Runnable { public void run() { @@ -61,230 +54,17 @@ public void run() { } } - private static final class AcceptableTask implements Runnable { - - final SocketChannel sc; - - final Socket s; - - AcceptableTask(final SocketChannel sc) { - - if (sc == null) { - throw new NullPointerException("SocketChannel cannot be null in AcceptableTask"); - } - - if (sc.socket() == null) { - throw new NullPointerException("Null Socket for SocketChannel in AcceptableTask"); - } - - this.sc = sc; - this.s = sc.socket(); - } - - public void run() { - - if (!FDTServer.filterSourceAddress(s)) - return; - - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, " AcceptableTask for " + sc + " STARTED!"); - } - final String sdpConfFlag = System.getProperty("com.sun.sdp.conf"); - final boolean bSDP = (sdpConfFlag != null && !sdpConfFlag.isEmpty()); - if (!bSDP) { - try { - s.setKeepAlive(true); - } catch (Throwable t) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot set KEEP_ALIVE for " + sc - + ". Will ignore the error. Contact your sys admin.", t); - } - - try { - // IPTOS_LOWCOST (0x02) IPTOS_RELIABILITY (0x04) IPTOS_THROUGHPUT (0x08) IPTOS_LOWDELAY (0x10) - s.setTrafficClass(0x04 | 0x08 | 0x010); - } catch (Throwable t) { - logger.log(Level.WARNING, - "[ FDTServer ] [ AcceptableTask ] Cannot set traffic class for " - + sc - + "[ IPTOS_RELIABILITY (0x04) | IPTOS_THROUGHPUT (0x08) | IPTOS_LOWDELAY (0x10) ] Will ignore the error. Contact your sys admin.", - t); - } - } - - final ByteBuffer firstByte = ByteBuffer.allocate(1); - final ByteBuffer clientIDBuff = ByteBuffer.allocate(16); - - UUID clientSessionID; - Selector tmpSelector = null; - SelectionKey sk = null; - - try { - - int count = -1; - while (firstByte.hasRemaining()) { - count = sc.read(firstByte); - if (count < 0) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Unable to read header for socket [ " + s - + " ] The stream will be closed."); - try { - sc.close(); - } catch (Throwable ignored) { - // ignore - } - return; - } - - if (firstByte.hasRemaining()) { - tmpSelector = Selector.open(); - sk = sc.register(tmpSelector, SelectionKey.OP_READ); - tmpSelector.select(); - } - } - - if (sk != null) { - sk.cancel(); - sk = null; - } - - firstByte.flip(); - final byte firstB = firstByte.get(); - - switch (firstB) { - - // Control channel - case 0: { - if (config.isGSIModeEnabled() || config.isGSISSHModeEnabled()) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Got a remote control channel [ " + s - + " ] but in GSI mode ... will be rejected."); - try { - byte[] key = {0}; - sc.write(ByteBuffer.wrap(key)); - sc.close(); - } catch (Throwable ignored) { - // ignore - } - return; - } - - sc.configureBlocking(true); - ControlChannel ct = null; - - try { - ct = new ControlChannel(s, fdtSessionManager); - fdtSessionManager.addFDTClientSession(ct); - } catch (Throwable t) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot instantiate ControlChannel", t); - ct = null; - } - if (ct != null) { - new Thread(ct, "ControlChannel thread for [ " + s.getInetAddress() + ":" + s.getPort() + " ]").start(); - } - break; - } - - // Worker channel - case 1: { - if (config.isBlocking()) { - sc.configureBlocking(true); - } else { - sc.configureBlocking(false); - } - - // TODO - Use SelectionManager(?), - while (clientIDBuff.hasRemaining()) { - count = sc.read(clientIDBuff); - if (count < 0) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Unable to read clientID. The stream will be closed"); - Utils.closeIgnoringExceptions(sc); - return; - } - - if (clientIDBuff.hasRemaining()) { - // THIS CANNOT?!? happen in blocking mode ( JVM should throw exception if this happens - // ... but ) - if (config.isBlocking()) { - logger.log(Level.WARNING, - "[ FDTServer ] [ AcceptableTask ] Blocking mode ... unable to read clientID. The stream will be closed"); - Utils.closeIgnoringExceptions(sc); - return; - } - } else { - // everything has been read - break; - } - - if (tmpSelector == null) { - tmpSelector = Selector.open(); - } - - if (!config.isBlocking()) { - sk = sc.register(tmpSelector, SelectionKey.OP_READ); - tmpSelector.select(); - } - }// while - - if (sk != null) { - sk.cancel(); - } - - clientIDBuff.flip(); - clientSessionID = new UUID(clientIDBuff.getLong(), clientIDBuff.getLong()); - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "[ FDTServer ] [ AcceptableTask ] New socket from clientID: " + clientSessionID); - } - fdtSessionManager.addWorker(clientSessionID, sc); - break; - } - - // Ping channel - RTT - case 2: { - break; - } - - // GUI special (sounds special, doesn't it) channel - case 3: { - sc.configureBlocking(true); - ServerSessionManager sm = null; - try { - sm = new ServerSessionManager(s); - new Thread(sm, "GUIControlChannel thread for [ " + s.getInetAddress() + ":" + s.getPort() + " ]").start(); - } catch (Throwable t) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot instantiate GUI ControlChannel", t); - } - break; - } - - default: { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Unable to understand initial cookie: " + firstB); - Utils.closeIgnoringExceptions(s); - return; - } - } - - } catch (Throwable t) { - logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Exception: ", t); - Utils.closeIgnoringExceptions(sc); - } finally { - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, " AcceptableTask for " + s + " FINISHED!"); - } - Utils.closeIgnoringExceptions(tmpSelector); - } - } - } - - public FDTServer() throws Exception { + public FDTServer(int port) throws Exception { hasToRun = new AtomicBoolean(true); // We are not very happy to welcome new clients ... so the priority will be lower executor = Utils.getStandardExecService("[ Acceptable ServersThreadPool ] ", - 5, - 10, - new ArrayBlockingQueue(65500), - Thread.NORM_PRIORITY - 2); - port = config.getPort(); + 5, + 10, + new ArrayBlockingQueue(65500), + Thread.NORM_PRIORITY - 2); ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); @@ -314,6 +94,7 @@ public FDTServer() throws Exception { *

* Note: Invoking this method acts as a signal for the server. Any ongoing transfers will continue until they finish *

+ * * @return true if server was signaled to stop */ public boolean stopServer() { @@ -340,6 +121,10 @@ public static final boolean filterSourceAddress(java.net.Socket socket) { } + public UUID getFdtSessionID() { + return fdtSessionID; + } + public void doWork() throws Exception { Thread.currentThread().setName(" FDTServer - Main loop worker "); @@ -347,7 +132,7 @@ public void doWork() throws Exception { final boolean isStandAlone = config.isStandAlone(); try { - for (;;) { + for (; ; ) { if (!isStandAlone) { if (fdtSessionManager.isInited() && fdtSessionManager.sessionsNumber() == 0) { logger.log(Level.INFO, "FDTServer will finish. No more sessions to serve."); @@ -409,7 +194,7 @@ public void doWork() throws Exception { } public static final void main(String[] args) throws Exception { - FDTServer jncs = new FDTServer(); + FDTServer jncs = new FDTServer(config.getPort()); jncs.doWork(); } diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index dd29e96..a77a400 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -12,14 +12,18 @@ import java.io.File; import java.io.IOException; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.attribute.PosixFileAttributes; import java.util.*; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; @@ -83,6 +87,18 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti // should be 0 in case everything works fine and !=0 in case of an error protected short currentStatus; + protected ServerSocketChannel ssc; + + protected ServerSocket ss; + + protected Selector sel; + + protected SocketChannel sc; + + protected Socket s; + + ExecutorService executor; + protected static final String[] FDT_SESION_STATES = {"UNINITIALIZED", "STARTED", "INIT_CONF_SENT", "INIT_CONF_RCV", "FINAL_CONF_SENT", "FINAL_CONF_RCV", "START_SENT", "START_RCV", "TRANSFERING", "END_SENT", "END_RCV"}; @@ -133,6 +149,8 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti // do not try to write on the writer peer protected boolean localLoop = config.localLoop(); + protected int transferPort; + // is loop ? protected boolean isLoop = config.loop(); @@ -149,8 +167,9 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti protected final boolean customLog; - public FDTSession(short role) throws Exception { + public FDTSession(short role, int transferPort) throws Exception { super(); + this.transferPort = transferPort; // Utils.initLogger(config.getLogLevel(), new File("/tmp/"+ (role == CLIENT ? "CLIENT" : "SERVER")+ "-" + sessionID + ".log"), new Properties()); customLog = Utils.isCustomLog(); @@ -162,7 +181,7 @@ public FDTSession(short role) throws Exception { setCurrentState(STARTED); this.role = role; if (this.role == CLIENT || this.role == COORDINATOR) { - this.controlChannel = new ControlChannel(config.getHostName(), config.getPort(), sessionID(), this); + this.controlChannel = new ControlChannel(config.getHostName(), transferPort, sessionID(), this); } rateLimit.set(config.getRateLimit()); @@ -204,7 +223,7 @@ public FDTSession(short role) throws Exception { final void startControlThread() { if (ctrlThreadStarted.compareAndSet(false, true)) { - new Thread(this.controlChannel, "Control channel for [ " + config.getHostName() + ":" + config.getPort() + new Thread(this.controlChannel, "Control channel for [ " + config.getHostName() + ":" + transferPort + " ]").start(); } } @@ -420,6 +439,11 @@ public final void notifyCtrlMsg(ControlChannel controlChannel, Object o) throws handleListFilesMessage(ctrlMsg); break; } + case CtrlMsg.REMOTE_TRANSFER_PORT: { + setCurrentState(COORDINATOR_MSG_RCVD); + handleGetRemoteTransferPortMessage(ctrlMsg); + break; + } default: { FDTProcolException fpe = new FDTProcolException("Illegal CtrlMsg tag [ " + ctrlMsg.tag + " ]"); fpe.fillInStackTrace(); @@ -446,10 +470,17 @@ private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { config.setCoordinatorMode(false); config.setDestinationIP(sessionConfig.destinationIP); config.setFileList(sessionConfig.fileLists); - config.setPortNo(54321); + config.setPortNo(sessionConfig.destinationPort); final ControlChannel ctrlChann = this.controlChannel; - FDTSession session = FDTSessionManager.getInstance().addFDTClientSession(); - ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, session.controlChannel.fdtSessionID())); + int remoteTransferPort = getFDTTransferPort(sessionConfig.destinationPort); + if (remoteTransferPort > 0) { + FDTSession session = FDTSessionManager.getInstance().addFDTClientSession(remoteTransferPort); + ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, session.controlChannel.fdtSessionID().toString())); + } + else + { + ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, "-1")); + } } catch (Exception ex) { logger.log(Level.WARNING, "Exception while handling coordinator message", ex); } finally { @@ -459,8 +490,16 @@ private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { } } - private void handleListFilesMessage(CtrlMsg ctrlMsg) { + public int getFDTTransferPort(int destinationMsgPort) throws Exception { +// openSocketForTransferPort(destinationMsgPort); + ControlChannel cc = new ControlChannel(config.getHostName(), destinationMsgPort, UUID.randomUUID(), FDTSessionManager.getInstance()); + int transferPort = cc.sendTransferPortMessage(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, "rtp")); + // wait for remote config + logger.log(Level.INFO, "Got transfer port: " + config.getHostName() + ":" + transferPort); + return transferPort; + } + private void handleListFilesMessage(CtrlMsg ctrlMsg) { logger.log(Level.INFO, "[ FDTSession ] [ handleListFilesMessage ( " + ctrlMsg.message.toString() + " )"); try { FDTListFilesMsg lsMsg = (FDTListFilesMsg) ctrlMsg.message; @@ -475,6 +514,45 @@ private void handleListFilesMessage(CtrlMsg ctrlMsg) { } } + private void handleGetRemoteTransferPortMessage(CtrlMsg ctrlMsg) { + logger.log(Level.INFO, "[ FDTSession ] [ handleGetRemoteTransferPortMessage ( " + ctrlMsg.message.toString() + " )"); + try { + final ControlChannel ctrlChann = this.controlChannel; + int newTransferPort = config.getNewRemoteTransferPort(); + if (newTransferPort > 0) { + openSocketForTransferPort(newTransferPort); + ctrlChann.sendRemoteTransferPort(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, newTransferPort)); + Utils.waitAndWork(executor, ss, sel, config); + } else { + ctrlChann.sendRemoteTransferPort(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, -1)); + logger.warning("There are no free transfer ports at this moment, please try again later"); + } + } catch (Exception ex) { + logger.log(Level.WARNING, "Exception while handling 'get remote transfer port' message", ex); + } finally { + this.setClosed(true); + } + } + + private void openSocketForTransferPort(int port) throws IOException { + + executor = Utils.getStandardExecService("[ Acceptable ServersThreadPool ] ", + 2, + 10, + new ArrayBlockingQueue(65500), + Thread.NORM_PRIORITY - 2); + ssc = ServerSocketChannel.open(); + ssc.configureBlocking(false); + FDTSession sess = FDTSessionManager.getInstance().getSession(sessionID); + ss = ssc.socket(); + ss.bind(new InetSocketAddress(port)); + + sel = Selector.open(); + ssc.register(sel, SelectionKey.OP_ACCEPT); + sc = ssc.accept(); + config.setSessionSocket(ssc, ss, sc, s, port); + } + protected void buildPartitionMap() { if (logger.isLoggable(Level.FINEST)) { diff --git a/src/lia/util/net/copy/FDTSessionManager.java b/src/lia/util/net/copy/FDTSessionManager.java index 6cf1690..aad1510 100644 --- a/src/lia/util/net/copy/FDTSessionManager.java +++ b/src/lia/util/net/copy/FDTSessionManager.java @@ -6,7 +6,9 @@ import lia.util.net.common.AbstractFDTCloseable; import lia.util.net.common.Config; import lia.util.net.common.Utils; -import lia.util.net.copy.transport.*; +import lia.util.net.copy.transport.ControlChannel; +import lia.util.net.copy.transport.ControlChannelNotifier; +import lia.util.net.copy.transport.FDTProcolException; import java.nio.channels.SocketChannel; import java.util.Arrays; @@ -95,18 +97,18 @@ public void addFDTClientSession(ControlChannel controlChannel) throws Exception } //called from - public FDTSession addFDTClientSession() throws Exception { + public FDTSession addFDTClientSession(int transferPort) throws Exception { FDTSession fdtSession = null; try { - if (config.isPullMode()) { - //-> Start a writer and connect to the server - fdtSession = new FDTWriterSession(); - } else { - //-> Start a reader and connect to the server - fdtSession = new FDTReaderSession(); - } + if (config.isPullMode()) { + //-> Start a writer and connect to the server + fdtSession = new FDTWriterSession(transferPort); + } else { + //-> Start a reader and connect to the server + fdtSession = new FDTReaderSession(transferPort); + } fdtSessionMap.put(fdtSession.sessionID(), fdtSession); inited.set(true); @@ -138,10 +140,9 @@ public FDTSession getSession(UUID fdtSessionID) { public boolean finishSession(UUID fdtSessionID, String downMessage, Throwable downCause) { final FDTSession fdtSession = fdtSessionMap.remove(fdtSessionID); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, " FDTSessionManager removed sessionID " + fdtSessionID + "; removed == " - + (fdtSession != null) + " new size: " + fdtSessionMap.size()); - } + fdtSession.setClosed(true); + logger.log(Level.FINER, " FDTSessionManager removed sessionID " + fdtSessionID + "; removed == " + + (fdtSession != null) + " new size: " + fdtSessionMap.size()); //I know ... it's not very well sync, but should be enough for the client side ... which will have only one FDT Session if (fdtSessionMap.size() == 0) { lock.lock(); diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 5d2a964..865778b 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -3,6 +3,14 @@ */ package lia.util.net.copy; +import lia.util.net.common.*; +import lia.util.net.copy.disk.DiskWriterManager; +import lia.util.net.copy.disk.ResumeManager; +import lia.util.net.copy.filters.Postprocessor; +import lia.util.net.copy.filters.Preprocessor; +import lia.util.net.copy.filters.ProcessorInfo; +import lia.util.net.copy.transport.*; + import java.io.File; import java.net.InetAddress; import java.util.*; @@ -11,31 +19,16 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.FileChannelProvider; -import lia.util.net.common.NetloggerRecord; -import lia.util.net.common.StoragePathDecoder; -import lia.util.net.common.Utils; -import lia.util.net.copy.disk.DiskWriterManager; -import lia.util.net.copy.disk.ResumeManager; -import lia.util.net.copy.filters.Postprocessor; -import lia.util.net.copy.filters.Preprocessor; -import lia.util.net.copy.filters.ProcessorInfo; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.CtrlMsg; -import lia.util.net.copy.transport.FDTProcolException; -import lia.util.net.copy.transport.FDTSessionConfigMsg; -import lia.util.net.copy.transport.TCPSessionReader; - /** * The Writer session ... - * + * * @author ramiro */ public class FDTWriterSession extends FDTSession implements FileBlockConsumer { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final Logger logger = Logger.getLogger(FDTWriterSession.class.getName()); private static final ResumeManager resumeManager = ResumeManager.getInstance(); @@ -54,9 +47,9 @@ public class FDTWriterSession extends FDTSession implements FileBlockConsumer { private final AtomicBoolean finishNotifiedExecuted = new AtomicBoolean(false); - public FDTWriterSession() throws Exception { - super(FDTSession.CLIENT); - Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); + public FDTWriterSession(int transferPort) throws Exception { + super(FDTSession.CLIENT, transferPort); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "CLIENT" + "-" + sessionID + ".log"), new Properties()); dwm.addSession(this); sendInitConf(); this.monID = config.getMonID(); @@ -64,13 +57,13 @@ public FDTWriterSession() throws Exception { /** * REMOTE SESSION - * + * * @param cc control channel * @throws Exception */ public FDTWriterSession(ControlChannel cc) throws Exception { super(cc, FDTSession.SERVER); - Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "SERVER" + "-" + sessionID + ".log"), new Properties()); dwm.addSession(this); this.monID = (String) cc.remoteConf.get("-monID"); } @@ -284,7 +277,7 @@ private void finalCleanup() { ignCtrl); } } - + setClosed(true); try { FDTSessionManager.getInstance().finishSession(sessionID, downMessage(), downCause()); } catch (Throwable ignore) { @@ -359,7 +352,7 @@ public void handleInitFDTSessionConf(CtrlMsg ctrlMsg) throws Exception { @Override public void handleFinalFDTSessionConf(CtrlMsg ctrlMsg) throws Exception { final boolean isFiner = logger.isLoggable(Level.FINER); - + config.registerTransferPortForSession(controlChannel.localPort, sessionID().toString()); FDTSessionConfigMsg sccm = (FDTSessionConfigMsg) ctrlMsg.message; this.destinationDir = sccm.destinationDir; @@ -485,11 +478,12 @@ public void handleFinalFDTSessionConf(CtrlMsg ctrlMsg) throws Exception { } } else if (role == CLIENT) { transportProvider = new TCPSessionReader(this, this, InetAddress.getByName(config.getHostName()), - config.getPort(), config.getSockNum()); + transferPort, config.getSockNum()); } // Notify the reader that he can start to send the data - controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.START_SESSION, null)); + logger.log(Level.FINER, "FWS handleFinalFDTSessionConf starting session on port: " + transferPort); + controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.START_SESSION, transferPort)); setCurrentState(START_SENT); @@ -508,7 +502,7 @@ public void handleStartFDTSession(CtrlMsg ctrlMsg) throws Exception { if (role == CLIENT) { if (transportProvider == null) { transportProvider = new TCPSessionReader(this, this, InetAddress.getByName(config.getHostName()), - config.getPort(), config.getSockNum()); + transferPort, config.getSockNum()); } } @@ -554,7 +548,6 @@ public void handleEndFDTSession(CtrlMsg ctrlMsg) throws Exception { logger.log(Level.INFO, "[ FDTWriterSession ] Remote FDTReaderSession for session [ " + sessionID + " ] finished ok. Waiting for our side to finish."); } - } private boolean doPreprocess(String[] preProcessFilters, Map preProcMap) throws Exception { diff --git a/src/lia/util/net/copy/gui/ClientSessionManager.java b/src/lia/util/net/copy/gui/ClientSessionManager.java index e0d4fad..e160a34 100644 --- a/src/lia/util/net/copy/gui/ClientSessionManager.java +++ b/src/lia/util/net/copy/gui/ClientSessionManager.java @@ -40,8 +40,6 @@ public class ClientSessionManager { private RunnableScheduledFuture fdtInternalMonitoringTask = null; private RunnableScheduledFuture consoleReporting = null; -// private Runnable progressReporter; - /** * Called in order to initialize a connection with a remote port... * @param host @@ -50,15 +48,13 @@ public class ClientSessionManager { public String initTransfer(final String host, final int port, final boolean isPullMode, final String[] fileList, final String destDir, final FDTPropsDialog d, final boolean isRecursive) { // start by constructing a dummy config -// System.out.println(" PullMode = " + isPullMode); -// this.progressReporter = progressReporter; constructConfig(host, port, isPullMode, fileList, destDir, d, isRecursive); HeaderBufferPool.initInstance(); fdtInternalMonitoringTask = (RunnableScheduledFuture)Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, TimeUnit.SECONDS); consoleReporting = (RunnableScheduledFuture)Utils.getMonitoringExecService().scheduleWithFixedDelay(ConsoleReportingTask.getInstance(), 1, 2, TimeUnit.SECONDS); // the session manager will check the "pull/push" mode and start the FDTSession try { - currentSession = FDTSessionManager.getInstance().addFDTClientSession(); + currentSession = FDTSessionManager.getInstance().addFDTClientSession(port); fdtSessionMTask = currentSession.getMonitoringTask(); } catch (Throwable t) { logger.log(Level.WARNING, "Got exception when initiating transfer", t); diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index d32db78..2db673a 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -5,6 +5,7 @@ import lia.gsi.GSIServer; import lia.gsi.net.GSIGssSocketFactory; +import lia.util.net.copy.transport.FDTListFilesMsg; import lia.util.net.common.*; import javax.security.auth.Subject; @@ -376,6 +377,22 @@ public void sendSessionIDToCoordinator(CtrlMsg ctrlMsg) { } + public void sendRemoteTransferPort(CtrlMsg ctrlMsg) { + logger.log(Level.INFO, "[ ControlChannel ] [ sendRemoteTransferPort ( " + ctrlMsg.message.toString() + " )" + this.remoteAddress + ":" + this.remotePort); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); + } + } + try { + sendMsgImpl(ctrlMsg); + } catch (Exception e) { + e.printStackTrace(); + } + + } + public List sendListFilesMessage(CtrlMsg ctrlMsg) throws IOException { logger.log(Level.INFO, "[ ControlChannel ] [ sendListFilesMessage ]"); @@ -400,7 +417,30 @@ public List sendListFilesMessage(CtrlMsg ctrlMsg) throws IOException { return null; } - public UUID sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { + public int sendTransferPortMessage(CtrlMsg ctrlMsg) throws IOException { + + logger.log(Level.INFO, "[ ControlChannel ] [ sendTransferPortMessage ]"); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); + } + } + try { + sendMsgImpl(ctrlMsg); + CtrlMsg newCtrlMsg = getResponse(); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ CtrlChannel ] [ sendTransferPortMessage ] got response: " + newCtrlMsg.message); + } + return (Integer) newCtrlMsg.message; + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to retrieve response from server", e); + } + cleanup(); + return -1; + } + + public String sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { logger.log(Level.INFO, "[ ControlChannel ] [ sendCoordinatorMessage ]"); if (logger.isLoggable(Level.FINER)) { @@ -413,7 +453,7 @@ public UUID sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { sendMsgImpl(ctrlMsg); CtrlMsg newCtrlMsg = getResponse(); logger.info("Remote job session ID: " + newCtrlMsg.message.toString()); - return (UUID) newCtrlMsg.message; + return newCtrlMsg.message.toString(); } catch (Exception e) { logger.log(Level.WARNING, "Failed to retrieve response from server", e); @@ -574,6 +614,7 @@ public void run() { } } } finally { + config.releaseRemoteTransferPort(fdtSessionID.toString()); if ((downMessage() != null) || (downCause() != null)) { close(downMessage(), downCause()); } else { diff --git a/src/lia/util/net/copy/transport/CtrlMsg.java b/src/lia/util/net/copy/transport/CtrlMsg.java index 4f7726b..4c297cb 100644 --- a/src/lia/util/net/copy/transport/CtrlMsg.java +++ b/src/lia/util/net/copy/transport/CtrlMsg.java @@ -68,11 +68,14 @@ public class CtrlMsg implements Serializable { /** message types designated for list files feature */ public static final int LIST_FILES = 14; + + /** message types designated for remote transfer port feature */ + public static final int REMOTE_TRANSFER_PORT = 15; private static final String[] CTRL_MSG_TAGS = new String[] { "KEEP_ALIVE_MSG", "PROTOCOL_VERSION", "SESSION_ID", "SESSION_TYPE", "INIT_FDT_CONF", "PING_SESSION", "INIT_FDTSESSION_CONF", "FINAL_FDTSESSION_CONF", "FINISHED_FILE_SESSIONS", "START_SESSION", "END_SESSION", - "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES" + "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES", "REMOTE_TRANSFER_PORT" }; /** diff --git a/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java b/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java index 6dfb7c3..fad07ec 100644 --- a/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java +++ b/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java @@ -20,6 +20,7 @@ public class FDTSessionConfigMsg implements Serializable { public String destinationDir; public String destinationIP; + public int destinationPort; public boolean recursive; //future? use @@ -42,12 +43,16 @@ public FDTSessionConfigMsg(Config config) { this.destinationDir = config.getDestinationDir(); this.sourceIP = config.getSourceIP(); this.fileLists = config.getFileList(); + this.destinationPort = config.getDestinationPort(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("\n destinationDir: ").append(destinationDir); + sb.append("\n destinationIP: ").append(destinationIP != null ? destinationIP : ""); + sb.append("\n destinationPort: ").append(String.valueOf(destinationPort)); + sb.append("\n sourceIP: ").append(sourceIP != null ? sourceIP : ""); sb.append(" recursive: ").append(recursive); sb.append(" dirOffset: ").append(dirOffset); sb.append("\n UUID[]: ").append(Arrays.toString(fileIDs)); From 2f370c1c392302cd240f0384e6d1915bde3b8277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 2 Sep 2017 10:09:12 +0300 Subject: [PATCH 10/55] Fix maven dependency from rt.jat --- src/lia/util/net/common/Utils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index 8525011..95cd384 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1,7 +1,6 @@ package lia.util.net.common; import apmon.ApMon; -import com.sun.istack.internal.NotNull; import lia.util.net.copy.FDT; import lia.util.net.copy.FDTServer; import lia.util.net.copy.FDTSessionManager; @@ -1529,7 +1528,7 @@ public static void getRecursiveFiles(String fileName, String remappedFileName, L * * @param closeable to be closed */ - public static void closeIgnoringExceptions(FDTCloseable closeable, @NotNull String downMessage, Throwable downCause) { + public static void closeIgnoringExceptions(FDTCloseable closeable, String downMessage, Throwable downCause) { if (closeable != null) { try { closeable.close(downMessage, downCause); From c11916098ec4617c5891eda3e26b57eff3cd65c3 Mon Sep 17 00:00:00 2001 From: Raimondas Sirvinskas Date: Sat, 19 Aug 2017 20:44:31 +0300 Subject: [PATCH 11/55] Fix retrieve session log file To retrieve log file for any transfer session please use following sample. Usage: -c -d -sID (cherry picked from commit 28b3759) --- src/lia/util/net/copy/FDTReaderSession.java | 4 ++-- src/lia/util/net/copy/FDTSession.java | 2 -- src/lia/util/net/copy/FDTWriterSession.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 42fc866..62cc4c4 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -81,7 +81,7 @@ public class FDTReaderSession extends FDTSession implements FileBlockProducer { */ public FDTReaderSession(int transferPort) throws Exception { super(FDTSession.CLIENT, transferPort); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "CLIENT-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); final int rMul = Integer.getInteger("fdt.rQueueM", 2).intValue(); final int avProcProp = Integer.getInteger("fdt.avProc", 1).intValue(); final int avProcMax = Math.max(avProcProp, Utils.availableProcessors()); @@ -113,7 +113,7 @@ public FDTReaderSession(int transferPort) throws Exception { */ public FDTReaderSession(ControlChannel ctrlChannel) throws Exception { super(ctrlChannel, FDTSession.SERVER); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"R-"+ "SERVER-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); fileBlockQueue = new ArrayBlockingQueue(Utils.availableProcessors() * 2); readersMap = new TreeMap>(); diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index a77a400..3efb05a 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -170,7 +170,6 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public FDTSession(short role, int transferPort) throws Exception { super(); this.transferPort = transferPort; -// Utils.initLogger(config.getLogLevel(), new File("/tmp/"+ (role == CLIENT ? "CLIENT" : "SERVER")+ "-" + sessionID + ".log"), new Properties()); customLog = Utils.isCustomLog(); @@ -491,7 +490,6 @@ private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { } public int getFDTTransferPort(int destinationMsgPort) throws Exception { -// openSocketForTransferPort(destinationMsgPort); ControlChannel cc = new ControlChannel(config.getHostName(), destinationMsgPort, UUID.randomUUID(), FDTSessionManager.getInstance()); int transferPort = cc.sendTransferPortMessage(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, "rtp")); // wait for remote config diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 865778b..32e802a 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -63,7 +63,7 @@ public FDTWriterSession(int transferPort) throws Exception { */ public FDTWriterSession(ControlChannel cc) throws Exception { super(cc, FDTSession.SERVER); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "SERVER" + "-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); dwm.addSession(this); this.monID = (String) cc.remoteConf.get("-monID"); } From 0e6feab217180af27690ba1a79cce986bfb0589f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 2 Sep 2017 10:47:08 +0300 Subject: [PATCH 12/55] Fix fdt session closing messages while doing transfer job --- src/lia/util/net/copy/FDTReaderSession.java | 3 --- src/lia/util/net/copy/FDTSessionManager.java | 2 +- src/lia/util/net/copy/FDTWriterSession.java | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 62cc4c4..ceea229 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -744,7 +744,6 @@ private void finalCleanup() { "\n\n [ FDTReaderSession ] [ HANDLED ] exception returning buffers to pool \n\n ", t); } - setClosed(true); try { FDTSessionManager.getInstance().finishSession(sessionID, downMessage(), downCause()); } catch (Throwable ignore) { @@ -763,8 +762,6 @@ protected void internalClose() throws Exception { logger.log(Level.FINER, " [ FDTReaderSession ] enters internalClose downMsg: " + downMessage() + " , downCause: " + downCause()); } - FDTSession session = FDTSessionManager.getInstance().getSession(sessionID); - session.setClosed(true); try { super.internalClose(); } catch (Throwable t) { diff --git a/src/lia/util/net/copy/FDTSessionManager.java b/src/lia/util/net/copy/FDTSessionManager.java index aad1510..97ee1bf 100644 --- a/src/lia/util/net/copy/FDTSessionManager.java +++ b/src/lia/util/net/copy/FDTSessionManager.java @@ -140,7 +140,7 @@ public FDTSession getSession(UUID fdtSessionID) { public boolean finishSession(UUID fdtSessionID, String downMessage, Throwable downCause) { final FDTSession fdtSession = fdtSessionMap.remove(fdtSessionID); - fdtSession.setClosed(true); + //fdtSession.setClosed(true); logger.log(Level.FINER, " FDTSessionManager removed sessionID " + fdtSessionID + "; removed == " + (fdtSession != null) + " new size: " + fdtSessionMap.size()); //I know ... it's not very well sync, but should be enough for the client side ... which will have only one FDT Session diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 32e802a..004794a 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -277,7 +277,6 @@ private void finalCleanup() { ignCtrl); } } - setClosed(true); try { FDTSessionManager.getInstance().finishSession(sessionID, downMessage(), downCause()); } catch (Throwable ignore) { From 067bfb20fde75f9b98d862955bd519667192830d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 2 Sep 2017 11:49:08 +0300 Subject: [PATCH 13/55] Fix destination ip issue when transferring between two different machines. --- src/lia/util/net/common/Config.java | 4 ++++ src/lia/util/net/copy/FDTSession.java | 1 + 2 files changed, 5 insertions(+) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index f4422d3..85b71c5 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -718,6 +718,10 @@ public void setHostName(String hostname) { } public String getHostName() { + if (configMap.get("-agent") != null) + { + return dIP; + } return hostname; } diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 3efb05a..2bf23f5 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -468,6 +468,7 @@ private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { config.setDestinationDir(sessionConfig.destinationDir); config.setCoordinatorMode(false); config.setDestinationIP(sessionConfig.destinationIP); + config.setHostName(sessionConfig.destinationIP); config.setFileList(sessionConfig.fileLists); config.setPortNo(sessionConfig.destinationPort); final ControlChannel ctrlChann = this.controlChannel; From cb66a2f8f5aebae00579d3a29305c7c90ec1e506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Mon, 4 Sep 2017 23:06:59 +0300 Subject: [PATCH 14/55] Misc fixes --- src/lia/util/net/common/Config.java | 2 +- src/lia/util/net/copy/FDTReaderSession.java | 16 ++++++++++++---- src/lia/util/net/copy/FDTWriterSession.java | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 85b71c5..2b94190 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -102,7 +102,7 @@ public class Config { // default is 4 public static final int DEFAULT_SOCKET_NO = 4; private int sockNum = DEFAULT_SOCKET_NO; - public static final int DEFAULT_PORT_NO = 54321; + public static final int DEFAULT_PORT_NO = 43210; public static final long DEFAULT_KEEP_ALIVE_NANOS = TimeUnit.MINUTES.toNanos(2); public static final int DEFAULT_PORT_NO_GSI = 54320; public static final int DEFAULT_PORT_NO_SSH = 22; diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index ceea229..3605cf8 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -255,7 +255,9 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi int c = 0; if (remappedFileList != null) { for (String f : newFileList) { - newRemappedFileList.put(new File(f).getAbsolutePath(), remappedFileList[c++]); + if (new File(f).isFile()) { + newRemappedFileList.put(new File(f).getAbsolutePath(), remappedFileList[c++]); + } } } else { newRemappedFileList = null; @@ -271,9 +273,15 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi logger.warning("File listed in file list does not exist! " + fName); throw new IOException("File does not exist! " + fName); } - FileReaderSession frs = new FileReaderSession(fName, this, isLoop, fcp); - fileSessions.put(frs.sessionID, frs); - setSessionSize(sessionSize() + frs.sessionSize()); + if (new File(fName).isFile()) { + FileReaderSession frs = new FileReaderSession(fName, this, isLoop, fcp); + fileSessions.put(frs.sessionID, frs); + setSessionSize(sessionSize() + frs.sessionSize()); + } + else + { + logger.warning("File listed in file list is not a file! " + fName); + } } buildPartitionMap(); diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 004794a..812ec98 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -49,7 +49,7 @@ public class FDTWriterSession extends FDTSession implements FileBlockConsumer { public FDTWriterSession(int transferPort) throws Exception { super(FDTSession.CLIENT, transferPort); - Utils.initLogger(config.getLogLevel(), new File("/tmp/"+"W-"+ "CLIENT" + "-" + sessionID + ".log"), new Properties()); + Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); dwm.addSession(this); sendInitConf(); this.monID = config.getMonID(); From dde2242f2815ac454810aebd9cfed48a63833ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Mon, 4 Sep 2017 23:09:58 +0300 Subject: [PATCH 15/55] Build jar and rpm with one command --- build-all.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 build-all.sh diff --git a/build-all.sh b/build-all.sh new file mode 100644 index 0000000..598437e --- /dev/null +++ b/build-all.sh @@ -0,0 +1,23 @@ +if command -v yum > /dev/null 2>&1; then + OS="Centos" + CMD=yum +elif command -v zypper > /dev/null 2>&1; then + OS="OpenSuse" + CMD=zypper +else + OS="Debian" + CMD=apt-get +fi +if command -v mvn > /dev/null 2>&1; then + echo "Maven already installed " + echo "Detected OS $OS, using command $CMD" +else + echo "Maven will be installed " + sudo $CMD install maven +fi +echo "Addling libraries to local maven repository" +./add-lib-to-local-maven.sh + + +mvn clean install + From 3ca6ec80fe54747c2f80cfbb72694781fb7f1079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 5 Sep 2017 22:29:34 +0300 Subject: [PATCH 16/55] Add fdt documentation to /docs dir --- build-all.sh | 0 docs/_config.yml | 7 ++ docs/doc-examples.md | 180 ++++++++++++++++++++++++++++++++++++ docs/doc-fdt-ddcopy.md | 145 +++++++++++++++++++++++++++++ docs/doc-security.md | 174 ++++++++++++++++++++++++++++++++++ docs/doc-system-tuning.md | 79 ++++++++++++++++ docs/doc-user-extensions.md | 100 ++++++++++++++++++++ docs/img/FDT_diagram.png | Bin 0 -> 132105 bytes docs/img/SC06.png | Bin 0 -> 343481 bytes docs/img/ciena_sc08_1.jpg | Bin 0 -> 285021 bytes docs/img/figure1-m2m.png | Bin 0 -> 66739 bytes docs/img/figure1.png | Bin 0 -> 106495 bytes docs/img/figure2-m2m.png | Bin 0 -> 127830 bytes docs/img/figure2.png | Bin 0 -> 106179 bytes docs/img/figure3-m2m.png | Bin 0 -> 132806 bytes docs/img/figure3.png | Bin 0 -> 50676 bytes docs/img/figure4-m2m.png | Bin 0 -> 147669 bytes docs/img/figure4.png | Bin 0 -> 137034 bytes docs/img/figure5-m2m.png | Bin 0 -> 89295 bytes docs/img/figure5.png | Bin 0 -> 121106 bytes docs/img/figure6-m2m.png | Bin 0 -> 166549 bytes docs/img/results08_1.jpg | Bin 0 -> 251893 bytes docs/img/results09_2.jpg | Bin 0 -> 231653 bytes docs/perf-sc08.md | 4 +- 24 files changed, 687 insertions(+), 2 deletions(-) mode change 100644 => 100755 build-all.sh create mode 100644 docs/_config.yml create mode 100644 docs/doc-examples.md create mode 100644 docs/doc-fdt-ddcopy.md create mode 100644 docs/doc-security.md create mode 100644 docs/doc-system-tuning.md create mode 100644 docs/doc-user-extensions.md create mode 100644 docs/img/FDT_diagram.png create mode 100644 docs/img/SC06.png create mode 100644 docs/img/ciena_sc08_1.jpg create mode 100644 docs/img/figure1-m2m.png create mode 100644 docs/img/figure1.png create mode 100644 docs/img/figure2-m2m.png create mode 100644 docs/img/figure2.png create mode 100644 docs/img/figure3-m2m.png create mode 100644 docs/img/figure3.png create mode 100644 docs/img/figure4-m2m.png create mode 100644 docs/img/figure4.png create mode 100644 docs/img/figure5-m2m.png create mode 100644 docs/img/figure5.png create mode 100644 docs/img/figure6-m2m.png create mode 100644 docs/img/results08_1.jpg create mode 100644 docs/img/results09_2.jpg diff --git a/build-all.sh b/build-all.sh old mode 100644 new mode 100755 diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..6fdd43b --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,7 @@ +title: Fast Data Transfer +description: FDT is an Application for Efficient Data Transfers which is capable of reading and writing at disk speed over wide area networks (with standard TCP). It is written in Java, runs an all major platforms and it is easy to use. +show_downloads: true +google_analytics: +theme: jekyll-theme-cayman +gems: + - jekyll-menus diff --git a/docs/doc-examples.md b/docs/doc-examples.md new file mode 100644 index 0000000..18fbde0 --- /dev/null +++ b/docs/doc-examples.md @@ -0,0 +1,180 @@ +[[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] + +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [Examples] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] + +### Examples + +1. To send one file called "local.data" from the local system +directory to another computer +in the "/home/remoteuser/destiantionDir" folder, with default +parameters, there are two options: + +- Client/Server mode + +First,the FDT server needs to be started on the remote system. ( The defaultsettings will be used, which implies the default port, 54321, on boththeclient and the server ). -S is used to disable the standalone mode,which means that the server will stop after the session will finish + +``` +[remote computer]$ java -jar fdt.jar -S +``` + +Then,the client will be started on the local system specifying the sourcefile, the remote address (or hostname) where the server was started inthe previous step and the destination directory + +``` +[local computer]$ java -jar fdt.jar -c -d /home/remoteuser/destinationDir /home/localuser/local.data +``` + +OR + +``` +[local computer]$ java -jar fdt.jar -c -d destinationDir ./local.data +``` + +- Secure Copy (SCP) mode + +In this mode the server will be started on the remote systemautomatically by the local FDT client using SSH. + +``` +[local computer]$ java -jar fdt.jar /home/localuser/local.data remoteuser@:/home/remoteuser/destinationDir +``` + +OR + +``` +[local computer]$ java -jar fdt.jar ./local.data remoteuser@:destinationDir +``` + +If the remoteuser parameter is not specified the local user, runningthe fdt command, will beused to login on the remote system + +2. To get the content of an entire folder and all its children, +located in the user's home directory, the -r ( recursive +mode ) flag will be specified and also -pull to sink the data from the +server. In the Client/Server mode the access to the server will be +restricted to the local IP addresses only ( with -f flag ). + +- Client/Server mode + +Multiple addresses may be specfied using the -f flag using ':'. If theclient's IP address(es) is not specified in the allowed IP addressesthe connection will be closed. In the following command the server isstarted in standalone mode, which means that will continue to run afterthe session will finish. The transfer rate for every client sessionswill be limited to 4 MBytes/s + +``` +[remote computer]$ java -jar fdt.jar -f allowedIP1:allowedIP2 -limit 4M +``` + +OR + +``` +[remote computer]$ java -jar fdt.jar -f allowedIP1:allowedIP2 -limit 4096K +``` + +The command for the local client will be. + +``` +[local computer]$ java -jar fdt.jar -pull -r -c -d /home/localuser/localDir /home/remoteuser/remoteDir +``` + +OR + +``` +[local computer]$ java -jar fdt.jar -pull -r -c -d localDir remoteDir +``` + +- SCP mode + +In this mode only the order of the parameters will be changed, and -ris the only argument that must be added ( -pull is implicit ). Sameauthentication policies apply as in the first example + +``` +[local computer]$ java -jar fdt.jar -r remoteuser@:/home/remoteuser/remoteDir /home/localuser/localDir +``` + +OR + +``` +[local computer]$ java -jar fdt.jar -r remoteuser@:remoteDir localDir +``` + +3. To test the network connectivity a transfer here is an example +which transfers data from /dev/zero to /dev/null using 10 streams in +blocking mode, for both the server and the client with 8 MBytes +buffers. The server will stop after the test is finished + +- Client/Server mode + +``` +[remote computer]$ java -jar fdt.jar -bio -bs 8M -f allowedIP -S +``` + +``` +[local computer]$ java -jar fdt.jar -c -bio -P 10 -d /dev/null /dev/zero +``` + +- SCP mode + +``` +[local computer]$ java -jar fdt.jar -bio -P 10 /dev/zero remoteAddress:/dev/null +``` + +4. The user can also define a list of files ( a filename per line ) +to be transfered. FDT will detect if the files are located on multiple +devices and will use a dedicated thread for each device. + +``` +[remote computer]$ java -jar fdt.jar -S +``` + +``` +[local computer]$ java -jar fdt.jar -fl ./file_list.txt -c -d /home/remoteuser/destDir +``` + +5. To test the local read/write performance of the local disk the +DDCopy may be used. + +- The following command will copy the entire partition +/dev/dsk/c0d1p1 to /dev/null reporting every 2 seconds ( the default ) +the I/O speed + +``` +[local computer]$ java -cp fdt.jar lia.util.net.common.DDCopy if=/dev/dsk/c0d1p1 of=/dev/null +``` + +- To test the write speed of the file system using a 1GB file +read from /dev/zero the following command may be used. The operating +system will sync() the data to the disk. The data will be read/write +using 10MB buffers + +``` +[local computer]$ java -cp fdt.jar lia.util.net.common.DDCopy if=/dev/zero of=/home/user/1GBTestFile bs=10M count=100 flags=NOSYNC +``` + +OR + +``` +[local computer]$ java -cp fdt.jar lia.util.net.common.DDCopy if=/dev/zero of=/home/user/1GBTestFile bs=1M bn=10 count=100 flags=NOSYNC +``` + +- Launching FDT as Agent example: + +``` +java -jar fdt.jar -tp -p -agent +``` + +- Sending coordinator message to the agent: + +``` +java -jar fdt.jar -dIP -dp -sIP -p -d /tmp/destination/files -fl /tmp/file-list-on-source.txt -coord +``` +- Retrieving session log file. + +To retrieve session log file user needs to provide at least these parameters: + +``` +java -jar fdt.jar -c -d /tmp/destination/files -sID +``` + +- To retrieve list of files on custom path there is a custom mode which can be used. + +``` +java -jar fdt.jar -c -ls /tmp/ +``` + + + + diff --git a/docs/doc-fdt-ddcopy.md b/docs/doc-fdt-ddcopy.md new file mode 100644 index 0000000..f49a01b --- /dev/null +++ b/docs/doc-fdt-ddcopy.md @@ -0,0 +1,145 @@ +[[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] + +[FDT & DDCopy] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] + +### FDT +**FDT** can be used in one of these seven modes: +* **Server**: java -jar fdt.jar [ OPTIONS ] +* **Client**: java -jar fdt.jar [ OPTIONS ] -c \ [file1 ...]|[-fl \] -d \ +* **SCP**: java -jar fdt.jar [ OPTIONS ] [[[user@][host1:]]file1 [[[user@][host2:]]file2 +* **Coordinator**: java -jar fdt.jar [OPTIONS] -dIP \ -dp \ -sIP \ -p \ -d \ [-fl \] -coord +* **List Files**: java -jar fdt.jar [OPTIONS] -c \ -ls \ +* **Agent**: java -jar fdt.jar [OPTIONS] -c \ -tp \ -agent +* **Session log**: java -jar fdt.jar [OPTIONS] -c \ -d \ -sID \ + +In Server mode the FDT will start listening for incoming client connections. The server may or may not stop after the last client finishes the transfer. In Client mode the client will connect to the specified host, where an FDT Server is expected to be running. The client can either read or write file from/to the server. + +In the SCP (Secure Copy) mode the local FDT instance will use SSH to start/stop the FDT server and/or client. The security is based on ssh credentials. The server started in this mode will accept connections **ONLY** from the "SCP" client. It is possible to restrict the access for the FDT Servers started from the command line using the -f option. The option accepts a list of IP addresses separated by ':'. + +In order to use third party copy feature with FDT there have to be two FDT launched in agent mode. In Agent mode the FDT will start listening for incoming client connections on . In Agent mode the client will listen for coordinator message with task. After receiving coordinator message Agent will try to send message to destination Agent requesting to open socket for transfer session. Destination Agent will take one transfer port from pool and open port for that session and then informs source Agent that transfer job can be started. At this pont first agent now has session ID and it sends it to the coordinator, that later coordinator could see that FDT session log file from remote Agent. After finishing task Agent will close transfer port and return it to the transfer ports pool. + +To retrieve list of files on custom path there is a custom mode which can be used. User needs to specify host and port (if not default) ant specify path from where he want to get list if files. It will not list files in directory where he has no access. + +The OPTIONS currently supported may be server or client specific, or may be used in both modes. + +**Common options used for both server and client :** + +**-gsi** enables GSI authentication scheme in FDT. When started in server mode the FDT will open two TCP ports: one for GSI authentication and the other one for data channels + +**-gsip \** specifies the TCP port used for GSI authentication. Default value is 54320. + +**-p \** specifies the TCP port to be used (for the server it is the port used to listen on; for the client the port to connect to). The default port is 54321. + +**-preFilters f1,...,fn** User defined preProcessing filters. The classes specified by the f1,...,fn paramenters must be in the classpath. The prePRocessing filters must be defined in the FDT "sender" command line. Please see the User's Filters section for more details and examples. + +**-postFilters f1,...,fn** User defined postProcessing filters. The classes specified by the f1,...,fn paramenters must be in the classpath. The postPRocessing filters must be defined in the FDT "receiver" command line. Please see the User's Filters section for more details and examples. + +**-bio** Blocking I/O mode. n this mode every channel (socket) will be configured to send/receive data synchronously and FDT will use one thread per channel. By default, non-blocking I/O will be used. On some platforms/systems the throughtput can be slightly higher in blocking I/O mode. The limitation in the blocking mode is the maximum number of threads that can be used and, for very high numbers of streams (thousands), the CPU used by the kernel for scheduling the threads. By default, FDT will use non-blocking mode. + +**-iof \** Non-blocking I/O retry factor. In non-blocking mode every read/write operation which returns 0, will be repeated up to times before waiting for I/O readiness. By default this value is set to 1, which means that every network read/write operation will return in the select() (which can also be poll()/epoll()) if no more data can be processed by the underlying channel(socket). The default value should work fine on most of the systems, but values of 2 or 3, may increase the throughput on some systems. Values higher than 5 will only increase the CPU system usage, without any gain in performance. + +**-limit \** Restrict the transfer speed at the specified rate. K (KiloBytes/s), M (MegaBytes/s) or G (GigaBytes/s) may be used as suffixes. When this parameter is specified in the server it represents the maximum transfer rate for every FDT session. If the parameter is specified in both the server and the client, the minimum value between them will be used. + +**-md5** Enables MD5 checksum for every file involved in the transfer. The flag may be specified for both client and server, but it will be used by the "sender" session only. When the transfer finishes the list will be sent to the "receiver" and it will be printed in a `md5sum`-like mode + +**-printStats** Various statistics about buffer pools, sessions, etc will be printed + +**-v** Verbose. Multiple 'v'-s (up to three) may be used to increment the verbosity level.Maximum level is three (-vvv) which corresponds to Level.FINEST for the standard Java logging system used by FDT. + +**-u, -update** Update. If a newer version of fdt.jar is available on the update server it will update the local copy + +**Server options :** + +**-S** disable the standalone mode; when specified the FDT Server will stop after the last client finishes. By default, the server will continue to listen for incoming clients. This option is automatically passed to the server started in "SCP" mode. + +**-bs \** Size for the I/O buffers. K (KiloBytes) or M (MegaBytes) may be used as suffixes. The default value is 512K. If the number of clients or sockets is expected to be very high is better to decrease this value. The memory used by this buffers is directly mapped in the operating system memory pages. The memory used by this buffers is limited by the JVM and can be increased passing -XX:MaxDirectMemorySize=m (e.g -XX:MaxDirectMemorySize=256m) to the 'java' command + +**-f \** A list of IP addresses allowed to connect to the server. Multiple IPs may be specified, separated by ':' + +**Client options :** + +**-c \** connect to the specified host. If this parameter is missing the FDT will become server + +**-gsissh** used in the Secure Copy Mode to specify GSI authentication instead of normal SSH authentication scheme. The remote sshd servere must support GSI authentication. + +**-d \** The destination directory used to copy files. + +**-fl \** a list of files. Must have only one file per line. + +**-pull** Pull mode. The client will receive the data from the server. + +**-N** disable Nagle algorithm + +**-ss \** Set the TCP SO_SND_BUFFER size. M and K may be used as suffixes for Kilo/Mega. + +**-P \** Number of paralel streams to use. Default is 4. + +**Common options used for FDT Agent mode :** + +Agent can use booth Server and Client options too, because at any time Agent can be Server or Client + +**-p \** specifies the TCP port to be used (for the server it is the port used to listen on; for the client the port to connect to). The default port is 54321. + +**-tp \** specifies the TCP port lis to be used for transfer sessions. Ports are separated with comma. + +**-agent** Option for FDT to run as agent. + +**Common options used for FDT Coordinator mode :** + +**-d \** The destination directory used to copy files. + +**-fl \** a list of files. Must have only one file per line. + +**-dIP \** destination Agent IP address. + +**-sIP \** source Agent IP address. + +**-dp \** destination agent message channel port. + +**-p \** source agent message channel port. + +**-coord** Option for FDT to run as coordinator. + +**Common options used for FDT List Files mode :** + +**-c \** connect to the specified host. If this parameter is missing the FDT will become server + +**-ls \** path from where user wants to see lits of files + +**Common options used for FDT Session log file retrieving mode :** + +**-c \** connect to the specified host. If this parameter is missing the FDT will become server + +**-sID \** session ID retrieved from coordinator. + +**-d \** The destination directory used to copy session log file. + + +### DDCopy +**DDCopy** is very similar to Unix `dd` command and can be used to test the local disks or file system. It is bundled in the fdt.jar and has the following syntax: + +java -cp fdt.jar lia.util.net.common.DDCopy [ OPTIONS ] **if=\ of=\** + +where OPTIONS may be: + +**bs=\** size of the buffer used for read/write. K or M (for KiloBytes/MegaBytes) may be used as suffixes. Default is 4K + +**bn=\** Number of buffers used to readv()/writev() at once. If this parameter is 1, or is missing DDCopy will read()/write() a single buffer at a time, otherwise the readv()/writev() will be used. Default is 1 + +**count=\** Number of "blocks" to write. A "block" is represents how much data is read/write. The size of a "block" is: *. If <= 0 the copy stops when EOF is reached reading the . The default is 0 + +**statsdelay=\** Number of seconds between intermediate reports. Default is 2 seconds. If <= 0 no intermediate reports will be printed + +**flags=\** The field can have of the following values: + **SYNC** For every write both data and metadata are written synchronously + **DSYCN** Same as SYNC, but only the data is written synchronously. + **NOSYNC** The sync() is left to be done by the underlying OS + The default value is **DSYNC** + +**rformat=\** Report format. Possible values are: + K - KiloBytes + M - MegaBytes + G - GigaBytes + T - TeraBytes + P - PetaBytes + The default value is self adjusted. If the factor is too big only 0s will be displayed diff --git a/docs/doc-security.md b/docs/doc-security.md new file mode 100644 index 0000000..0758ff7 --- /dev/null +++ b/docs/doc-security.md @@ -0,0 +1,174 @@ +[[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] + +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [Security] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] + +### FDT Security +FDT provides several security schemes to allow sending and receiving files over public networks. +The FDT architecture allows to "plug-in" external security APIs and to use them for client authentication and authorization. The current version supports: + +* SSH channels + +* GSI-SSH [ NGS GSI-SSHTerm: http://www.grid-support.ac.uk/content/view/81/62/ ] + +* Globus-GSI [CoG JGlobus: http://dev.globus.org/wiki/CoG_JGlobus_1.4 ] + +`Please note that FDT distribution does not include these security packages. The user should download the libraries for preferred API from its source. See below the instructions to install different security libraries.` + +There are four security modes that one can set when transferring files with FDT: +##### **1. Source IP address filtering** + +In this mode the server activates a simple IP-based firewall where each source IP is checked against the list of allowed IPs. In this mode no user authentication is done. + +By default FDT starts allowing clients from any destination. + +To enable this mode, pass the "-f" option when starting FDT server: +-f , where allowedIPsList: A list of IP addresses allowed to connect to the server. +Multiple IP addresses may be separated by ':'. +You can use CIDR notation to specify an entire subnet. + +`However, please note that this mode does not enable any privacy or confidentiality on client-server control channel and it may be subject to source IP spoofing.` + +IP filtering can be used together with other authentication schemes. + +##### **2. Using SSH channels to securely start remote FDT client/server** +This mode is enabled when you use "SCP syntax" to transfer files with FDT. In this mode the local client starts on-the-fly an instance of FDT server, using an SSH connection to pass the start-up command to the remote machine. It is required the server system runs a ssh demon and the user has a valid shell account. The FDT server will accept data connections from only this client and will exit when the transfer finishes. +When both the source and destination are remote (i.e. the client uses a third-party machine to initiate the transfer) a different SSH connection is made for the client and server in order to start them on the specified machines. (the remote hosts should already have running an OpenSSH compatible SSH server). +During the transfer, the control channels with the remote hosts are kept open and the status messages are streamed back to the user console. + +Example: +Using local FDT client to transfer files to/from remote hosts: + +``` +fdt /path/to/file1 user@hostname:/path/to/file2 +``` + +``` +fdt user@hostname:/path/to/file1 /path/to/file2 +``` + +3rd party transfers (start both FDT client and server remotely): + +``` +fdt user1@hostname1:/path/to/file1 user2@hostname2:/path/to/file2 +``` + +##### **3. GSI-SSH mode** + +In this mode the FDT client can use the local GRID security credentials (i.e. proxy certificate) to authenticate to a remote GSI-extended SSH server. + +`N.B. In this case, the authentication and authorization is deferred to the GSI-OpenSSH server, which means that any user allowed to connect to this server is also allowed to start FDT client/server.` + +The hosts involved in the transfer have to fulfil the following requirements: +* the remote hosts need to have installed a GSI-Enabled OpenSSH server; +This is usually distributed in the current major grid-middleware software : VDT,gLITE. +See http://grid.ncsa.uiuc.edu/ssh/ for more details on how this server can be manually installed and configured. +* the machine running the FDT client needs to have an Grid-UI interface loaded: +The proxy certificate used to authenticate to the remote GSI-SSH server is searched in the following order: + 1. in the path specified by the X509_USER_PROXY environment variable + 2. in the default /tmp/x509up_u location + +* there are additional libraries that need to be appended to FDT client CLASSPATH: +1. Download FDT : + +``` +[~/fdt]> wget http://monalisa.cern.ch/FDT/lib/fdt.jar +``` + +2. Download gsi-sshterm libs: + +``` +[~/fdt]> wget http://www.grid-support.ac.uk/files/gsissh/GSI-SSHTerm-0.79.zip +[~/fdt]> unzip GSI-SSHTerm-0.79.zip +``` + +3. Set the CLASSPATH + +``` +[~/fdt]>export GSISSHLIBS=`find GSI-SSHTerm-0.79/lib/ -name "*.jar" -printf "$PWD/%p:"` +[~/fdt]>export CLASSPATH=$PWD/fdt.jar:$GSISSHLIBS +``` + +4. Set FDT command alias: + +``` +[~/fdt]> alias fdt="java lia.util.net.copy.FDT" +``` + +This mode is similar to the previous one in the way the remote FDT instances are started. +You have to pass **-gsissh** option to instruct FDT to use Grid credentials. + +Example: +Using local FDT client to transfer files to/from remote hosts: + +``` +fdt -gssish /path/to/file1 user@hostname:/path/to/file2 +fdt -gsissh user@hostname:/path/to/file1 /path/to/file2 +``` + +3rd party transfers (start both FDT client and server remotely): + +``` +fdt -gsissh user1@host1:/path/to/file1 user2@host2:/path/to/file2 +``` + +##### **4. GSI-enabled FDT server** + +This mode offers a more flexible way to authenticate and authorize users in Grid environments. The control channel between FDT clients and FDT server is secured using Globus GSI API. Mutual authentication is performed between FDT clients and servers. +To explicitly set this mode you have to download the Globus JGlobus libraries (see below) , set the CLASSPATH variable accordingly and pass the -gsi parameter when starting the FDT clients and FDT server + +**Server side:** +The FDT server have to be started using a pair of X509 public/private keys. The search path for these files is: +* X509_SERVICE_CERT and X509_SERVICE_KEY properties passed to the java virtual machine +* X509_HOST_CERT and X509_HOST_KEY environment variables +* /etc/grid-security/hostcert.pem and /etc/grid-security/hostkey.pem files + +`Note: +It is highly recommended to start the FDT server using an unprivileged account. Usually the host certificates are read protected from unprivileged accounts. In this case you should consider running the FDT server with different service private/public key files.` + +The clients connecting to the server are authenticated using the current environment setup on the server side: CAs certificates, CAs certificate revocation lists directory: + **default location:** /etc/grid-security/certificates + override with X509_CERT_DIR environment variable +By default, the authorization of users is based on grid-mapfile file available in the current Globus installation: + **default** /etc/grid-security/grid-mapfile or + Override with GRIDMAP java property or environment variable +Other authorization modules may be plugged-in in the FDT server by specifying : -Dgsi.authz.Authorization=customAuthzPluginClass + +**Client side:** + +The machine running the FDT client needs to have an Grid-UI environment loaded: +The proxy certificate used to authenticate to the remote GSI-enabled FDT server is searched in the following paths: +in the path specified by the X509_USER_PROXY environment variable + +in the default /tmp/x509up_u location + +###### **4.1. Setup client and server environment** + +1. Download Globus GSI (both client and server): + +``` +[~/fdt]> wget http://www-unix.globus.org/cog/distribution/1.4/cog-jglobus-1.4-bin.tar.gz +[~/fdt]> tar -xzvf cog-jglobus-1.4-bin.tar.gz +``` + +2. Setup CLASSPATH + +``` +[~/fdt]> export JGSILIBS=`find cog-jglobus-1.4/lib/ -name "*.jar" -printf "$PWD/%p:"` +[~/fdt]> export CLASSPATH=$PWD/fdt.jar:$JGSILIBS +``` + +###### **4.2. Start FDT server** + +Start FDT server using /home/fdt/fdtcert.pem and/home/fdt/fdtkey.pem credentials: + +``` +[~/fdt]> java -DX509_SERVICE_KEY=/home/fdt/fdtkey.pem -DX509_SERVICE_CERT=/home/fdt/fdtcert.pem lia.util.net.copy.FDT -gsi [server_options] +``` + +The server is using the X509_CERT_DIR environment variable as CAs certificates and CRLs location and default /etc/grid-security/grid-mapfile file to authorize users. + +###### **4.3. Start FDT client:** + +``` +[~/fdt]> java lia.util.net.copy.FDT -gsi [client_options] +``` diff --git a/docs/doc-system-tuning.md b/docs/doc-system-tuning.md new file mode 100644 index 0000000..70e33e8 --- /dev/null +++ b/docs/doc-system-tuning.md @@ -0,0 +1,79 @@ +[[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] + +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [System Tuning] + + +### System Settings +##### Linux + +We suggest to use newer linux distributions, or if this is not possible, update at least the kernel(2.6.20+). The newer kernels provide adequate TCP settings. We suggest to use the following settings to improve the TCP throughput, especially over long RTT links: +1. Increase the TCP buffers (newer kernels have this buffers in creased by default). Add the following lines in /etc/sysctl.conf to make the changes permanent accross reboots: + +```net.core.rmem_max = 8388608``` + +```net.core.wmem_max = 8388608``` + +```net.ipv4.tcp_rmem = 4096 87380 8388608``` + +```net.ipv4.tcp_wmem = 4096 65536 8388608``` + +```net.core.netdev_max_backlog = 250000``` + +```net.ipv4.tcp_no_metrics_save = 1``` + +```net.ipv4.tcp_moderate_rcvbuf = 1``` + +After adding them just run the following commabd as root: + +```#sysctl -p /etc/sysctl.conf``` + +The settings above will set a maximum of 8 MBytes buffers. +we suggest to use at least 4Mbytes maximum TCP buffers and a maximum of 16Mbytes should be enough. You should use reasonable values. Don't set very high values for this parameters, and especially don't set the same value for all the fields in net.ipv4.tcp_*. It's also a good practice to have the same value for net.core.r(w)mem_max with the last value in the net.ipv4.tcp_r(w)mem. Do not modify the net.ipv4.tcp_mem parameter. It is computed by the kernel at the system boot. + +2. The TCP congestion protocol (if available). To check if it is available for your kernel version: + +```$/sbin/sysctl net.ipv4.tcp_congestion_control``` + +Set it to cubic if kernel version 2.6.20+, and to scalable if older kernels. + +```#sysctl -w net.ipv4.tcp_congestion_control=cubic``` + +To make this persistent accross reboots add the following line in /etc/sysctl.conf + +```net.ipv4.tcp_congestion_control=cubic``` + +You may try to experiment various TCP stacks. You can list all of them using: + +```$/sbin/sysctl net.ipv4.tcp_available_congestion_control``` + +3. Increase txqueuelen size your ethernet card + +```#ifconfig eth2 txqueuelen 50000``` + +4. If your network infrastructure supports jumbo frames you may set the MTU size to 9000. Please notice that this setting might broke your AFS installation (newer versions of OpenAFS supports jumbo frames) + +```#ifconfig eth2 mtu 9000``` + +You may also try to disable the TCP timestamps. On some kernel versions this setting disables the automatic window scalling: + +```#sysctl -w net.ipv4.tcp_timestamps=0``` + +To make this setting permanent add the following line in /etc/sysctl.conf: + +```net.ipv4.tcp_timestamps=0``` + +##### OpenSolaris + +We obtained good results on OpenSolaris using these setting for the TCP buffers: + +```ndd -set /dev/tcp tcp_max_buf 8388608``` + +```ndd -set /dev/tcp tcp_cwnd_max 4194304``` + +```ndd -set /dev/tcp tcp_xmit_hiwat 524288``` + +```ndd -set /dev/tcp tcp_recv_hiwat 524288``` + +This page will include other settings or operating systems in the near future. + +For further comments and suggestions please send an email to: support-fdt@monalisa.cern.ch diff --git a/docs/doc-user-extensions.md b/docs/doc-user-extensions.md new file mode 100644 index 0000000..aa2a6a5 --- /dev/null +++ b/docs/doc-user-extensions.md @@ -0,0 +1,100 @@ +[[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] + +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [User's Extensions] [[System Tuning](doc-system-tuning.md)] + + +### User's Extensions +FDT allows to load user defined classes for Pre and Post - Processing of file transfers. +This functionality can be used to easily interface FDT with mass storage systems and to implement any additional Access Control List (ACL) to the files transfered by FDT. + +It can also be used for packing, compression or customized integrity check. + +The user can define its own syntax for managing files on different MS systems and the implementation for the Pre/Post Processing interfaces allows the user to define the mechanism to perform local staging or to move the transfered files to a MS system after they are transfered by FDT. + +The two procedures act as filters for the source and destination fields in the FDT syntax. + +The list of files to be transfered, the destination directory and the GSI authentication are passed to the class implementing the PreProcessing interface. If the FDT is used without the GSI authentication the Subject will be a null parameter. Based on the user defined syntax, the implementation can initiate local staging for the files . This can be done in one or multiple threads. It can verify the credentials for the authenticated user to access the files. It can also be used to perform data compression on the files to be transfered. +In case the final destination for the files is a MS system on the remote site, the preProcessing implementation should change the destination with a temporary directory on the remote system. The naming scheme for it is used by the PostProcessing implementation to start moving the files to the MS system after the FDT transfer is done. The Post Processing implementation can also be used to verify the user's credentials to write into the MS system to uncompress data, or make an integrity check on the MS system. If the Pre/Post processing classes are used to interface FDT with a MS system, they should modify and act only on files using the user's defined syntax in the name. They should not modify the naming scheme for local files. + +The preProcessing filters must implement **lia.util.net.copy.filters.Preprocessor** interface and the postProcessing filters must implement **lia.util.net.copy.filters.Postprocessor** interface. The functionality of these interfaces may be extended in the future. + + +**Preprocessor.java** +``` +package lia.util.net.copy.filters; + +import javax.security.auth.Subject; + +public interface Preprocessor { +public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception; +} +``` + +**Postprocessor.java** +``` +package lia.util.net.copy.filters; + +import javax.security.auth.Subject; + +public interface Postprocessor { +public void postProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception; +} +``` + +**ProcessorInfo.java** +``` +package lia.util.net.copy.filters; + +public class ProcessorInfo { +public String[] fileList; +public String destinationDir; + /** + * @since 0.9.25 + */ + public InetAddress remoteAddress; + /** + * @since 0.9.25 + */ + public int remotePort; + /** + * @since 0.9.25 + */ + public boolean recursive; + /** + * Non-null on writer side ONLY. + * Gives access to the transfer map of an FDT session. + * Key - the final file name (including the destination directory) for a FileSession + * Value - the FileSession + * + * @see FileSession + * @since 0.10.0 + */ + public Map fileSessionMap; +} +``` + +### Example +We provide a simple example in using this functionality to help users in implementing customized filters. +In this example the pre/postProcessing classes are used to compress a list of files before sending and to decompress them at the destination. + +To run the example please download the **FDTZipFilterExample.tar.gz** and follow these steps: + +1) Untar the archive and go to FDTZipFilterExample directory. The directory already +contains the fdt.jar archive. + +2) Go to FDTZipFilterExample directory and use compile.sh script ( javac must be in the $PATH) +to compile the filters. +``` +$./compile.sh +``` +3) To start the FDT server with PostZipFilter already enabled use the startFDTServer.sh script +``` +$./startFDTServer.sh +``` +4) To start FDT client and enable PreZipFilter the startFDTClient.sh may be used +``` +$./startFDTClient.sh -c localhost -d /home/test dataToTransfer +``` + +The **dataToTransfer** file will be first zipped in **dataTransfer.zip** by the **PreZipFilter** and it's name will be changed in the ProcessorInfo and returned to the FDT client. Then the **dataToTransfer.zip** will be transfered to the destination where the **PostZipFilter** will uzip it and delete the zip file. +> diff --git a/docs/img/FDT_diagram.png b/docs/img/FDT_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..8acb004778667f8ff8ac8904df831a03fedd035b GIT binary patch literal 132105 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
c8P9A~BPg=$t=xae|MmXJ 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 z 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

VJaCPD1BzP)c(S*C5RzLQ^%<=x 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 z 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

+4J~3q~=_yAa|=dq+~!z+fM4 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 literal 0 HcmV?d00001 diff --git a/docs/img/figure2-m2m.png b/docs/img/figure2-m2m.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8fb6c93556905cdd89d8fadecc9587514fc032 GIT binary patch literal 127830 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

06!-u>M>i~jXYXRBYeEw(*%US;wYC1XJ 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

|-!j`> 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 K&-0(Y_WggpfzyBh literal 0 HcmV?d00001 diff --git a/docs/img/figure3-m2m.png b/docs/img/figure3-m2m.png new file mode 100644 index 0000000000000000000000000000000000000000..90e75615de497f92c2230a8bc9b754e9849006db GIT binary patch literal 132806 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 BWEubf literal 0 HcmV?d00001 diff --git a/docs/img/figure3.png b/docs/img/figure3.png new file mode 100644 index 0000000000000000000000000000000000000000..20f2db500e7ad751fb18b05ca58c0b7911ffffde GIT binary patch literal 50676 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

6!$u*$4~ko{^w6Cy!Z~+I(C2GJfVS*%}{E` zm}oHulYPO>c`O`iDk>SBAFT|)msS;CW?VVtTBm%;BmbbPw)9k(mB4C4|6|Rmwxx_q zsoP%&{*k4wuCu=L*aBINOJ6b;pH^j7aoyR@JM(;3xwpV!U8lb$e5`zP#ER?!K)(@Z zT0^MmDELM9n;!Gg{pCj~woMtH-b*t1l}cXnCI>xe@xbMq_uu%2RTUNUO?V{9@fW8` zBMw0%iN6PmWPW!r@AFZ^J^ibjij2W;&wp!HZY(0!FX9;Xtx2kCrpm(ynfUw)3+=K0 z9HnLv))Tw&sze`-+ve`Rzx7>$H4k-aJ=T%&Elu}h(z}w5D6L^qby^ueeexT%C%6|p z=8d-ojZueL_TA=j(R|*V3;}Cl$kMe7ny-?%Nsra*&iFk?GD zxO7cJ)NUpilZEI~A^juI#w1kyJqqhq5fk}Imm2|D(IwiE=<@-VSfcbly9B-oHNHs# z@Kmx`56pMr)V1C%Rcv1=3fk9nHevozJJ_Ha9JgdmVWYX=A=I65^=-W-oH7q0+p`J! zaC%Sw!c}%R0|$7n%x`GA_ilS_KMx<|&kyI=D^29O5`HDAPUq}w;yyTwxmuQ7wg=)p zB%#XKWXV=I&zX*mOs+xj+oalP3XHlIs$nzsF^(HY&OKrOz>_tz)p9V#ln=>5R04?GsY5+Ii z*wS=vd+dV|UU-{X^sY}ROKGT7lHL?FyR(I799D;K+4OJrFY?$Vi(dtN8&`eN(Tzqi z=9xDCfz`dO4U$5w(fma|@x4>QuB2yJdlqyPd%51Y^+|ln86>-eOYw{+;fa=qlLn>2 z9a4;4{611plsH1Y5wA7ufwTVJPBDZqv}7?j|Gb_VTbtZIc7u*-0ZMYac=x`;z#Qs& zT*JA^W=8;H2zo{jL5w&Az#6SX0QGZ#zdF2KSU{-#GO>msQrDWIc=0DD)vy`&gTTw} zY^ff;?>%Mui5IqQqFmgzorE{v*_xwBI6ew?eS(}hH}5#w$y+H z1&mT~^n}%G_YkZq@034TKYGvCHZp!J~CMXQ5X5$G*~rx}Cry0NSta0m6{tNisHJ za(bgTs#s}{UiY8Y7GBA0(#w=@nKLy;w}AyAEPAQ-C3!-t)o34?W*adr_ARb4>n;Cw zOU^1nf2}Ng^>Q1RUdU9l+tZ4dj7q^TmhoE@ZWutF@6TG`x?Hx z)t`IgS}(`?7wVtg;^D z8%TEm0wXO^_;y(3ZzWHoQN*ub0_h434RJ4~$5;ve5ir4D(0QIpdwgmtEFT(-zsoL8 zLcLi>id}|~4-L~?aDO;og8^R?ZL2uFo-K1aKp=tnA716yMnO~0xlfbf-+xBEu~Ltx zdviAbYuUk>Kb)UoVZ9m}dYuf$Z91^2-YD@efLXBmS$0Pz2JAZ8^lH_2Yv2E8-U^cS zEme+>y}oLYy@5Gv^_mdN*Ea9NAsN;Z?t%vRTTSCb(|DmX&y8T(f->Sh% zF6sZ60B^lsfxu=n-IFUIpMwPWeL2u~=*Z-S1|E_!%DRdk6D0>lQ7!Y?%-aRw|I6n4 zwy!rqU-^$6=h(Bs`hU<|_Ho+=Zy8@8!6Yg))sb=PZqBV$Hn++TbHPB+B=IRT=qY`> z(T4WyzYHC6>i{~cdyuu2O(h8dk|4tW03Uz6iiWR6_|K638|Y!y{WsJ+-%C$TR0IUa zvJ9qSeKj+R_Ymk0L1nPi?VlduH>~WSpkYufX!ONB8$2KFaRxuYB!Nr+9YZJJ8MR+z z>?PiJBuk9BQiW5nhzty(eNCojE0wLA$dMYlc>A|0;Vpcn$28i~ciiJtFA;2P2bw1X9Rv{(fw` zo?v0)^&c$!PT1HlnJwfXFl-VDlrgujAlLN$_+owcf6-yN@W8HF)g&Saa2S#dTHhxN zTQwfNz5EB$bsE*)Z)sUBB@_teV1YSrbU--rB-sB!zen! z=|3pk9PX>YN$}ON@M||4OP93HKg<2TQRlB{Ih#`c_0p@7_Ieq-wUxC3F3wUbmyDm~iMnK* zN?TWov=F;2PEs~JuFdB2NPlRtT6O2K)WB-=7{nF87y6X4M#5X)VY+Pn9M39ZtsCG67vR1dx z#8~PJD_e3LiGIB?oF;)|hr6=xS-CQWTBV20Xp(x*>rH9C|HI|`vslZzLcTzC4~ra~ zG;t>W^tpX^WK1+chtuJzGvIXj^i1Cuo5L8^GIgxvyS23LcEn%5yDb^%(+Qx0Kb1E!|qTYMh-yL)y zWV9pPcr%^FA89dIqmuYh3HXf+lUz-Xa3!SP&t7gq?d_RSQDfS&Lkr$_E>@o+{-yz(MozZXm4h<0Rtg0rJA6e{BVJG&f#Na#XULcGM8WbJyCkO z%~{Z8X}p4U|Ey*-kus_JEB${M-bV-P>v2$sDQsYt4qgRa)O! zIF<~?(1E~`f$^I@pSaNcmx&?S#YzaITgh?sO*@0e)H zXhiNC3$p3VMhF3$rp`Mn<+9w%~wWzCMRt}^qw2NqpBDk|(WzOaXX zlF&1@j;4}8@2m__;wK>FyVNWfnZi@A5ea`fs|!r?bqwDTC%Na{F)@@iL3Xd9{Jwt_;Iz-+VLo z{^Y}L5+wch_LP!NLSpLt8Uxzyj_8@(!M^yyETA#0P5x!R*w2rES&uy?rsS{6NHJxL z-9?fzl0`tCt;cHy{+=78b$A$4bM5umh(*g!ji;L|{FSVEVPWZ;H-ENai-yB0@krmj`NUbK5j}MS= zcQ{T$!|8ds0`TuCC^&UHN09IoB+!eqo<3v2Xg-;ksBCu8V=;^rT=qe+&j{PwiwAzm zmzi#kjFe$y1Y~7hx;sMpUz7Gl&%<;y$jOGtwvB9TkTap=;h4zeD{E**9y{lU!}(-g z2L;W%z6J{j5D*bgLBkYUt>@&P#P*z=5+8B5o6OZ+8q+O zUy%n%UXUUa5BRV(z(`2&(Lp~uxPuy$nRNoLKDmaLeHRs}+}{sX3I!l;hc36YQBh^} z^=aqk;ACYNQxxd!K0Rp@5*6-tY;F{=sVw*~Td(srii$V@@j#W2SNr&<7B`oqJ@gAE zI!9SqV;#XNTr*v<T>h2RqRyLoy=uTFJsv!11~ zGY3m;ZJQvxH%tT}M=mj>vew|=qK=>ggE!TzSlWqIG7=KPUD=DF=;LaZ?g)=#%^3)q zRX!%W!c4q9%bvD+siewa&+D{%>G0Y_SeTWK}8bTVj3ou~>f7duE zg7N3$!Z=SqUT=Rm9|wPqOVn!%qU)V0uwv1G0DH5+HW7ym2lu>8ydI>1aF%odL?sx( zrda8x1SGK0T?l`SeD>i8Cy?f+>HNxyDhEe}t?ev(G_<<>-28qQrPiwF@85jXfp+F! z@DZnjXi+`A6*yUh#AAwzc5EQON-FN|?!bD`weRkdLVa-r5%yCL$OOm);s}3TmM#wv z++X{C9E|o6b`(K^0a1nMi8U6iwhsq^CK*Ip*nTUNNk(=}?AG3{)E3w^L)eK5>7;9E z%V?tmeddsQ{P4xr)}qqOfu*;Yt28C0IZs;0@!J8Eod9$EeP~m#{7pv!0pj6zM9TiI z{ZaeGP;u>Ya%bn)si~v7Is;$5&;sm=tPWIG>F_VJab?ULQGfn;*p?(d&YInen$E(o z5AI<%Hy@X-pwQF6PfY4kY>QJ*S`lwTV#1l2pGkgK{A15m+ zgNAz$@Nv8GL#B=YR$I!(I7^SEX5oW!=b=-FiXE62Al@Rp!e99Kp9l&wWf&MkyS~Vc zjw7>%tnA`JeaPeDdWp_MV+KY|7tNgbG%j#KCN~jQvHqvUhs1PHGmS zSu1Jm>H>;3k7NWh0)+CG|l*8Ye23v38PF;@07ea&PLiu&- zTUM5&kIzi^HCX{fYX%8^U}1QfV~wO2k2LgI@Op@=Wb3L`qtFVug{B@>&f4)Q;$|WdI$o-pDu=l4KN1%)h*lex1oFQ=^q<-(I7lv4|q>*y}34I^<>TDPM*lu2e)3+ z>bs`pVF3kb+13()^ekXzEJKelx_(&58uo>n8&a5YZb$1OYICd3WTq}GPLEIGFa=-v z_iyX}fS`Gl{fUN)M?esKfCJyo&%;)P<^a1#HlyI;_-zCNy5|RRHBq|4-c6Z9EDLGa zcRXeXW`o%yguKt!W_5LvzP^SdXq46?>AXO5$vhqy^3tO)9)1?Rov+t48$_;k9;R%N zJGkfvI*{>upWgL;!e1U1)8MxSt$6T8XPBi49dW_zLLeZ1f)ch65$JUWv7mnvE8K@M z4<{DbP|_3~@FgaO4=6%HI$^gusnYJo!y7|ENnrbYNTR35h@KG|s_4SLg3k~)lSv{| zDk`ifE-oS}TfvY**NNDw57DqbiEKjpInlqRS&tG8i$+c3(#7`TfL^^fv|kf^>lU#U zLQfq4DeFl`DpY3QXtB#7IQB^n7UL)SD?-RSjUq2YOb_m_;=m9;>KqvhtPrTho6!-p zKlw?WPeP3a{>d3%TB*$5-yM2{%nZ6$`bBugp(Y}%$!t}Aa&ba(x6rHd9K#d>ivkdB z(IaUb_ed9neq2~sw^nt2DfFHO`FnX)xrOT5ceeA)#90v&2he|3xHNKN?R3c_(IAnhzCtP5F1)=V z=Eg@t;X%dNl;-03KDL!axi8#KcvQ6t%g_FW|6S@0tzEgfm|Cm$RNCNs@()z6rj zaDe=yDgMZhkJ$0G&B%5ob)}#Xhy0#*_b7=L;PL6zh3PA_ zn`YfAQ%!xt{spV7L?@rTRTCkR368dkNqPp&XFCJVP&1_8QPc)Z5-FI(vIHDx&nE0O zPBhhj_85L>W+s42!INDa(s+!bgb9Xoa`z&(_qn!)nE`~=H?+}dDFnelHPIqtMJH(A zK0Xl4q$VjqMA$hwG$buB1u5e-GYrRs)B@kT?4H!y5tNB}86+|lIfTaQF+$CTsn9GX ztK#UUXK5W>YFpoQNPWb~#dUy`CWnwtIHxs$Is=a)Vcb_E%}75WY;tYgF<$?|7b_uVT6y&_D}m zmbwO&s3kG1xK^Jg0}DvIz=ZL_D0tgh&fWTgmxAs;lMTei2hI)zS#a&O9WyH$T;Jk_ z1dCSncX2l$A*|XLSMgZDNhD{K*sB*7d-b1e0=}t$bDLc)Ef3@C)Kut{7U-gP zJ9B-GflI=ZtwR!uiXzOA572U+z4OkYso0fKaw7eHeOORgN1-ZmyVb3#Fc^wwKvq;#!=X0OAqICTA)oV?Z%o#YI|+@VR{~B0=8*B(y1K1C zNH;>FCLuqy);}Z!#}ix?3>aigxPLVwBn_+3jCT=6SoEf4bMf(uWSAS8VLPYPBZEIr z!NMSvI`ttJ^e)~6Oo&ni3q$e;pbC}o&CfsGxGg(7LZPOm%`@o;`2r9baQRZV^A3&Y zGroinp?*9^XSa8IVM8MOsaEb4!s?OOT4CjU~f2QW7N^h zqblVjg!{8fMO9{RsJa+!AqlX5Y$_`AG&7sYDVbBkg4+v3G9@P2J{^iu3uJ(|EH>B_ z2#~7L5q5Ze+%kcx>R%P1FV0%Lfyq|4-$t1n-?x-){nL}%qJ3~lu?Y(+cD=!>U~VpU z-2kme>w-EwJ7^vpOw#$P4dJ4?h9sVL1I`(?PXMXVd6~8%pN>hkxZ4YV#t4Do%lNNa zgm(fiG$B*hTK!0f1rKUK;5AwALifU| z@|wu4i>$Q2oX`usOtg`YD7&%3RIPdb$n_L$itdPujtUBrl3_Igr$XDGz%nMJkVLQ zT@za54(q^aUrI5inQjN{T9~C!Pf##wG9+)r`#`wd-3wjrm-+L!(!MgXFl_kb(Fbo2 z8jkP)8m$pd_|`V{oa__iZ^M(k?#Dd8amk=UmYwBfcl1g)l{*~xYaLWSqB}{jGjfqD zkP(WPPKAZ(tE$q+!x3xw)rhxAeZIxT*^Q?==tJx$)LaQZ?ZwWd2GSU4%%WKkWlYt+ zzD}uAL$cXC9%{E#gZUtY`Prms-QuLvae5H@P?~7evK%ysr_hf;6$1lJxk-YV)x5(rahIa{1=mqln9E?_E?`1UqnPQv(ka`VOgPEiAt8%Fxw@Ul6CFlYn`F(iXcbm1IQ#E!y1np4gB5ccT*kfI$- z>mMp9Ib2!-=LZ*B3OcYx?|>XJUA!;#(HID^*1U*q?H)xy`}y1A;rMl5-#bRg&y(S1 zH}upx%F4WsnA@aBaf2Xotw3U34b3N>pMfvDPGS&E&+l*PZ+Qv;3<+b+zr(lo;+OZ= zKazXY)#YdJr{#z%pCM?V&z6fFswiK!FWwA za7p}iXLd3>vYvGkLsS$A7k3S=2ob4c!c4K(;1lSbNO7OyY1l{aYzWq`A!vquW2@%NWp8dO;_AJ##wEqfv z${oK!gTYVTp zpRV7tZ7TZ4ocQUJ$MSbomMIed6O3$HlSs<6cS;1D_!d9QY2Wgz!=ANq@^^nCpuNhL zorO(Bsr{^J^oyR6^ypOF@{Uk>iu&7m^`aTa><9}R8C$PCnuDh;qx~D7Nj*S^2zycF zb!f<5X9Rq(zz6)8{%B-kuetsk5Ez``d{fh->->sJ+*npqRb^|mN1(YnOojj7uQd7T z{cIGIHxRP&>NQ&g2xLrSW4?+-%0w4@378(|%1~A9eBN?)Jxtx=A={;V=lPDr`^5}4 z1L0{Cj$vx@(AHopmLOL*D;50r7zqE#<(0QjnQ3+wTU5I1~Qwe!HM$3`2ez8$i&!`WINA4<{o4^NrdFpZ`W7{(y{9r}-olQ{FU3Be`2jcypuxB{;Xb6~2zP+=jm8Y1$ ziy#I+>UA2DGwH8dvZTTY=7E1|h~cB!`dahrP;AU=j2<;IE%J#W%oaC`B`;1(KIFQunTw*1jg@?R#mUaT zy1nDu0}S&*jwIjRJBR3V)-~Iay#wBa1^=@STOWaBq&4KY&Uo3fEqwY+ zY)xGirA?RTJRcTMtTc=58hS5RLZzuN0LYQlV`T(+HC*e2)eK1 z4W8fp-@+*KPZA_afYU9&b|=8ce}9%yQ)^zFO$5AOOie8zARIO|$>DPIwxE67 zc6;^zrw~Bg^S=)8&Mzqlc$u6A_|@q7TEZz`13Y~;Ha+*IeK8GjkR%fAlGk&GL%{Q}+V>)CbBi56JluE?%+@s>{J0|Zqd{!6 z-Q=k^LC|bV%E)xg-|%0>A{eKyj_v4B`vFh+zV_^Oqo02IV^doIH?GxzOw&TfhPPq1 zBqU#5tkPb7;>3+bp1e+ zC6@|;GL&M_CtIMwXv$#7lQJWproO$8d`ECHf?5r5Akj=M6Y0~Eqh7`I);X8f>5#~{ zFWHL8s3N7`iIXK$E)jbki}v_I?8aUTEU8QUi0P4%ozU`rv4n-~*xV!u4{t&9ml_>U zm6SwEN#!oL-+CoChK&K2ZJ1LE&}a1Q9Vg>y=p^~+aTi(qOI~aZzrw|fCG{iOy` zi+chX0_K>>WOg7)pY7d$1i%)%k8VO>1pIEHTGHkC0Wrb-74BN-$pW9T=$BZEg(OTB z55{nFAZx@$+rSBONna$_E`kS2>xXJ0iHYmpRHUy7$)p2qz-mawjyu6>b5y04%#soA zJ57j`Ar~5)R1{ENz?+kxB~?_luC*F<(}WmL6XKMTWb5kX z#mu3<@|^4?1Mg9QDLie>@7>qx-rCmn#T2V`!7=!YCcWxaE0!Z0)UOf2S7o>+fM1~n}$F0BkZO{8%#2VSoB6i$-b23SeN}` z-K1t4AZb|3^=^m<1tr>dSBMOSrV=-{Rlcy8!iL!hX_+5V6-awhuT1qa*%rZ!Q%Ma8 z)UKuEQYOoTzpW0fdXl#lX&kK3kj17+Q|nC5Md}l#rW&=96J?xs$qC8BdFEtw0%8(y zTjtv-YN#rhlgDes&cnx1l(B<&W?^kDNXyFym33-7=cisRlY_{*;ozYDGO$J)We8Cf zaIb|10$y(+IpD3nX}-ceLH)`nGq-@-ea%}>o*AndYpJDhTSygSa}~g)X!G%03q^X> z>KQGS*X8_XdFnsi&{B1g4b~>+^%Go=CjVy}O7v{QsP}M;-eS<`?MnPms?)vA2b8n0 zYB_;eY9^5;Cg@I2+k3_OnF@>tmwo+ufgRY;x({lXmpK>1&*cgKay%?hENk3FZlgz~X|fnltWVpm91 z9^2@xX8rZegRGU9y;#PjJU!sFg@AuJv2}aI-bxJF9pNl)hJ^5S+8XP}r>4`!GDPu2 znkn~kYI1aLrx4i;p)^7UMfyW|;f$S^fdiHze-9SE&*1vW&;z~Z!i4wI3!R#Pzp*M> zC>F|Ap!FPW&92^?E>rrgR2&rt4?~p8^JBwVjub(ARE;MfP`~%mr!B@^;;#iG^hhEfY4D#3x~fGHHMVByRO8Net1 zBUAtt4aE}@9dNepI|T3hh3GR-twIk@dWnqZ?vQf>iFeWM*W5P89g72t=mH`Erg1A3 z#7`kao|fRMKIl6Fs?Y-T?#TC+kfM*ZC_#ruZNpL#{W^qHont*cX(nL+K?(u0<5Zhn3z;OfVK zvaip`Sq88)<#UDP1#8)li|Q<*{od$^1JLwhUvu-GhYJfA^bSa^8tVJt z-4?gWlvVv&z?Vi}AkhSHuXw3;D)Xyyk30!G6jV6%K5pkH(HMM2N*VMvaLaDSeMT)Z z+B>Q(mTh=7h|jW-<7!E%WmH9Y_$Z%<{eWnLs#WJ&^aOJI7!FW$QzQqp4+3r5X}b&(VpH2UD(SF_Oow?d}2R-BfS{(-pc{CK3x`=JNWY;PX#v+ahFp z)aU7VsQ}4c{pO_qr?$6_YV+y-MWK|^qQ%|4SShY4P~4$F@j|iU?gUzj7kBq!1&TX? z;_k&QxCMs*A-U=MzUTLS&%O7*vrg8mtd-0&duI0RXJ*gy%;&QynV!9WiIsyElJ9CK zuoN%#Mnk?6A~rNGwvBswa3MqF!*uE-X6tN@Lb;JId)ATXXG3al%0w!b`<$cMM>`g{ zJv%Uq>PCFLXZCj zgXhP&WVA(Rdv(LoNOLPKGgOiB6y>rzP`*h%wCr73)RAR2CXEO}F`1%_9)OEqSd&Zxy-kX{2w>qS}U>;j?R8BUJkB8C2-goElc zacuEzjYaxRWsQ&3iQ7!kYaeON;x(lfq>Nr*)A_v=XEy#oW%MFS(aX%yIr3TI@8_M# z#)VXDUQ&hM=)q!(JAF`z9|o-dp1RX*w^-?G>P_HyW@wFyud+M1fL7ZQZwf7 zfI;*@*Q-fVR`Sw0Kf;R(&pC`$dDk8WS z+EPSp(U%g*r7>uqVBFhbX~=Zdm6w7sB(={sM{e`vY?PrcPcTohl(00(w|JIs*R-Ng zbfQi;nKC@_0uz}3@%>3i5YjyrtyL?1=1|f^KOv)sd&*I}U}h9VXWoxp>$b-iJo#Kn zh-KlGxC8iODfhY}Dw=etm-M6HWG2hhc8T5wPN8-lR@Z7imPQaDf+>H9j(L{I+NVWI z5y~Agf`V;;n)}S)sRu!)?n{-gcd{>>d3{)k9lqG*i0z}h(K9f-Ev_2UbR~L)frrwN z_vyu~`U$6v&qXgcZ#*NgWS$8EmR}{ULwxMs!50*_* z%=Rt@Ddg&A4#vZ_*CdkZNpU#4jFN|;qBb+@n9{b|ZJw%wW|zfRIef4o^d2s2{?$Cy7eZzzw1L{tkabBxsXSVM^|9a}94!lt zXl;lJ^(SDX08wqPkz-I}b~Pre3}LG-Ger~4H{*ulwLjOEydF}qa3)hBTX0eBNl^?O zPdj&iHp-lx@XeWJjPggFld1&scbKR^hi^Yx>=8&M|86oFfYzEBYHDcrBXEen#6R$hjs1v>< zhZW0JYBFTtHDq2`-2d+H3-^-JgthYh;&I$IW=MKY_PW8FNCB5BOC^C`MRZI`{?r90 zd$&ul^sBt=9GL@Z*M{ExJ%ecg)LM8j3Loc7XfS&T_mhlFT9Fc!7hDwc!l+|S$LxDo z&oQFeXf>?S{jsluFmbFzMZQ2S?ym1&!{1sBz|g0o0m`E*RIVyHI)p?>UiT zdaf!*I>#7OV!|%|iH)*7ZYZ_{FR-+}0mxHD#O)vzJ-O0;8WK5^{Pc{5jqwRF^(*;A zO@WuK=tkR0n9dSS?sT{91ufa&1&~efBV3F$d^}UWKW0ZfsHat+;#1`hBU6l6Oj|m4 zQFiP}%1T@!O0Mu9@`gR6U+(i|y6wJQAWzMBpstZQ+W28pJ$EQj-H|J?@)jbhj13J{ z>QVd}$rAT8XpfSbTEAKi59euc&>(8L0x3%Vy$!|t_CoZx-oh}wQdD;pVT~{{G*6z3 zFTL9}=%@OP6dLvtT3TAa@d03Zk|AM$MKMdK{K zJRIx=9{J|6#YLs_-A$l@LdcQHu8H4%O@-Tp2^ZI5=di!8t%_DeT1!j8WoLDU98OqO zZ3ML2n_Au@4d-Gxd^)je`19kZ8j-OlqeXg7_y&nqh*_y(mxujn9hu^a&d4RvKU4O8 zt#gu51>|j!Nh(>%(&|nimz(X!%S1;m!}m4XdpRPaFm)VE(>Zp3(T2v4VOc=SP+;4-Bha9LtklGew@D0lr$Kg3;SrS!DDOnalq_& zrHUogXr$qDeK7ORUZz<$J^5>l0HR5X$Cim-_pH&GrZ(?!C2yP$5)3l~V^+k5e>;8; z>4~%-^H3K#nlOeXMZxyjumrnAq3FS{9z_lwd_&A|KE2OzRa-v4chiRO2>T+a%AYZ@ zMX>^+<@fN6YBf^d^)m?-@mq8oO`Fk+V?q?bxoSmAk?+$JUtwMR8r&(Hv3ZkW-d}i# z7$weORyPELYWpiR+G&b0SGn1*#?l)%;I;!8tlaB=oPIWTx?F1PXQO2_S=prE2>W_W z#t+lR06x?o9Q5NyN0#ElP&7!X#7y426n-DxD(238&?G+-Se5x}cZvS-K{xNDU#^Pr z5C227UUdT0+c6v`Kq%37|g{hRm>q&IE!Q{?-K2Pst<}GzaEpVDeS~ zovqi$G?OpF8J~*wb+Pcj9i4Rdww2H7XgTO4oapL6v;$hB#&zj}FG6zpQMRNVopJYV zFzV5BNZ3q;_dOtuAqpw-J968B&q;ZojVes{VDG=WR?06Zr2s%tvoUCv9PUt2>J=~* zr4DAD`aAsRgWEJd{Q32nP`@4)pU|+HUjCy%%oNqFDZl%bWX{7Di|d0=V(9S%_s6dT zXrCvq*%F5=T4_Qu@7XVGc;{cQmQorJWe3i^8ejgZ{m%5rx)*tZN#BVt-{bH0IXu!A z*o#;9Wq*2*P1$f6-1{PGro=1r-Pjr}z(Fl|+9`0X{+udNBEV%$uY56B&HCDuOvu_{ z!+ht8Cz%gp%d26+$g;YxJmJ*S0(*%VF|ri&nQ~RvLFs|QT8J6ZJY_c|xYvLoz6Zfc z^Y3+ErVlQZS5c#BZ+4CK{+cl}{RpYJz5BVA2|vlnlCn*}w*_9tES^qk&HzB`XWz?) z@duLSJDRet^teL-8w7qL>lthkjh=7S|gQ17LZo zVH$TPzhAz+r=sjzYCj&mIZu{94FSqnzAt58T#hF>eR-+R3~74pRH%F&TKlnQ^kq*k zICxxWfHE0d%=jWi_gpB)Qkkn78<0GYc&CO)YQ6B^!Bnn|n%u2FO!(}X74cs-=x3_j6S}lveKJE@l_c7{q z&Qw#CBumCi-g$clt|pylEHbJo1&Su~OG|wI?sESdi#$r5IT{dm(^CUIFKO4AIK~mL zF@9I~qkAzif-u$+S3GCNl;eo9={Gv*FbmBqYWK70ye+*@}ZB+P|3B)F0Zd>cld zfXrha##)TNAAv+%TeVKZd2eq;1WRJcsKf(x=j3jGb<}v5$xXlZIEbbA3`k>|a8q2f zIB}mH+$}BJlB#Q7^{WLgrvTnD2fn^QLN=qn&0!Sv+-&2DvOOGFK$s8}ndXJ{<`O^s z3B4YY9r&XSONjYNE6d2=%)It5yNf=Tufo;kq5f#F1gNIMeHf zjTQ{W_%LEeJ!{^L4EH7tmg3I@V!uEdiem<|_W6=v!>mt~3&Xf?#W8sgi`w*S}c~Sj zEF#$4f|ixVpN716gE-H^vZ?nNN`CUl!ZI+{@!cbP+%5L^<)=q%2f{zJc$qCcTa;?w zWw$svtf@FC{h=3mpY)#TBWd&lcI3-}Pyu#oh^xb&mW2EgC5*!qptU})6u`2N_D;sj z5^st5(Xr%I{CaDAps_7$+0M#;+bDeTk6${6BK+z0@(aSMZ)57t?>*-3(4?fpgB4Z3 z>?FTWedkmm%u`VyTv0I}i*91zkO5FB@ebEM7goH;s;F=pCVw=-3FXgL*(GxxhifeD${NWSf+(NW2Wq18k!K%NM5XA}RA-1qmA zuC~J%wP}(G%@h0Fu2%M^+xze`c3N{Wu7jCTD9sC(N3CL7XHsR!p~w^J)XBEHwOH2z zzagW6omO_bJ#SbB>5b1r!cXD224P@bm;5?^IA)HAJBckOk>fI}mRu56`#I%&cWc~soX1!KYe-!3A z3D_UGkc)JQ-2ree*U;kH6d0KOM`p>!{0C4K9Cz)F;$aszbHI%NPw6vHw3^}b=N zN8WME-blT1T?$KQrI zlQk&NS}mQQ`a3y~{$MkBrVtSPRq9QPi^Ns|=xyH@RF6?*C=+)JYmD1)+++tGd?sz1 zgsT;sm*sj);V_>2fJOOkJ90aaU3!~L@e~{@EGWe9%c17&WC*UaAT z?GC@%_Wjtj1HQ`3buGNVxKAMwdChS(f;5vW`~1B@ajy`PvR01WJH$8Kgq_=f#?#nz zu+ucb!&y02X_=ygkAQ;uVG6-|wc_{Bv=+U$U*B9djKtjkT#&EnB0MQ|*7@no^I3Q( z^Q|j!T<$L0N+g_l#DT#SGS}tgy248G;0&#P`pPH@D#+?RS{z3kCEdw`eP6~8RZhPo zf`hd{r{?Au=NU#hXYnfc^hBIuHR`s<^YewFlPRyBBSZxbm#4o?xDh-IY5_)7j@e%z z8tG~Q?h&bmHF{BGmIW?%X2Cv{aY}v`%7Ff7W9-wA#r>@-1&XCsbx$Qcaox@bU~7?{ zn$pAybi%Fh+1@uiMLg<^O_9hzSEI>^f{pXT_(D7;Jiu->_~3y0fY2hJ`;9he-|E${ zG6A4ejUyVs6QQFXA9)789>wtF?wWZbtDY` zq0`_r8f;4Bi2Fec7G*YCz+c^S-Jq8#cdDW?*ck~Rg()^<1ygt4?+u&Jr`0S znd9vv+a9g;eU(1GU%!1_|mSdz77Y!4ArdU*Hmn@xqe)MjJPIOp>>3-FKyE!vfrH4+nlOAPG*`0}!n+ zI@}Oo7WgW|!Y-|)VC=YjcdHj4n@MRW_j>!UId1NBZEP=}V2qf5Tn8wr-ZRl}FH0kw zpml7#JRjM(&_(F;)zu~^sjAJA>ffx$TZaVVI)(D3veUA#cwktU2%@Pe9jBAb^GTGO z$_;*u^Rk`PSmW8mt{K_q+?(Q(TL~!QEDAZ`>y@b$mp`+P=gS^=5$@*Qsy*UZ^>9{H zD#fLh>`#@up!|#xSBRm^(DB{6``$V<3M-&97ViPm9<{V~LDB~-RmM%2DdPPh9vZdZ zyXH@5gnzr^)N6U?l6wKyR?#}m+FwiWE}e}nP3?71ks2hWBhDyZ>%scu+eI%Uoo-Y`h-u3@l^0wwf6u>j{ww^+8mQu=iNLl6}R5IEQV zY%V0?m%ozp@>6FJEV3hT*p+&j0khilVuko_mEiQ$@(DTzCQR!P;ikJGlrCk8`V2O{ z@Xcx`$zLl8n$LfT)p;MX`3PpjxC9z%!dPp-r+mLVH(YyzMqFr11S>R*W=Z?jCQ41e z_(kasQf*>94`2Um{^emp+JW=T{yc7awST*>Y2VP-{MJ6!>`92GZz$AQZm${hF-Wt_ zEO=W5!A`}gZvk>g{Ae7xK;XJ6E7`>R7^qagQ&esmi-8!Dh&R4c+Vsql4!|4)3J+1s zM9|(j!fy4spYZy^+1zXwWj(Zb)QIZ`Z{L)ZJxRxl!FeDl9}zIMKjqMrpbN}M^apZb z(yVL`w?(UbogsWNf2LhtdNzNF7S^cH?^2-g{UC$nt)e<;UJU_1C$=0tIQCp z!1zq5@SzwzAd%qAE_|XsZIGuh@m`TJ5Z_PrM&LtX&DI%&YoNz72fQmVGouW?z$>w( zFcC4^6tYVlUs1Hf4~VP>mN84UsGDlx|O)g#K8`$ z`h>W%*{*Kg`LYTtjfSZwcr@$Lm#;A`deurCcsf+fTCYd_%cQQ)^APtzfBBfyNg%Tn z%x~UdlUBPwADyDDgTr#lms7jwdaTy-`RIDL`b|zpx4uiIO|S1B{z$YH_pE;FF^tnP zvBvfph@3n&*5yG&1e)os*wmXgLC6r6VvSol@_1O*z6= zmeEd85s19o744kN>1q;KBbrysKATp*FJ{AhPD0(rZS7$Ed^4KITF} z3*Pg41^%46{RK(N@E8^n3M0#2;4_$(5d0A+J*W|JY4Mt2Oy7}F>)niDD8`dcHEeji z^`Fr^9ztr8GxQ;UU<@WDOEhI1-6KD5i@eL(N4Q+sRu=XtEqzb(EM9!Dc_>p<|7B1i zS1R*@+FmZ9LknB4P4V@?R!T{%ey%tqz|3j5Z?XR5EYsbk<_gRYUwgNF_qkvl`qe@$ zvCdfjg-WHC!I>UA?;{gCEi0=>>eE10Hv*$$VG+R%dCV6gui$lqd1maLAccU??z?d3 z^o@e1nd7w9Ax+h~!b3*`F&UM4WEeSp&Z{ZCmeTXEp2c9&2{^uEBh!>431XWwwdTc* zvg-=V_OMw=n;J<3v5G}U!}J49A`ZW^L0-VhJ8MwQP`n$iHExJLXLwC!?xA?fF4gTO zEQnCvNy_xCPoDIP^8o25Va49KPVt#@t`PxIZ00{xSeF`)`4`b$rEE8arW;5EF#TILVV*_zW_61E$`KM$DH z%h*ZBnRWu>Fg>*L6{{*z2g-|tE8_$KO11#@MKdeqmI9Zl6woF5kbt!ai0cy0+#k?B zy2qM+8VlvZrRrU0b4g3w`MEI7OTEk7e-*9#dAc7(as7Sj6l~FZ`R>h>H&CeCv8oqQ zvK@4~a~GyS+h-$G{4}i0h(s0BE0c_HCYQG8yV5Zs>4QkCc!Lu!{kjqr%Vn&ROIk8S zoOz~!y+mY8C5EaOPu{ZkRv{W9pZ!Ugc?IReWjKrY_vhzt38#3LZhCGE*s1^>8^4yO zw^;ui^XC-U^ZB;zG@pbfHrh@smNc5jE=2~52~|`sHE8ksc3i$r+gI&ehy0>$S^Ru1 z=hb&z#i-Lh;CMmPG-SJGRZ(N8GC(t#OBGe1_#jL38tuy?1e8TTQ( z&|)pNWZ@HR{%&-1btJmCsZ0$ttg7)(hhriGwJ@H8ZrA)O1r6{)9eb%&+@>SF5(Hm; znV;S&Jcp1U&zMrI@X?oA`?X2@hTSX5)U0wyeP7QpLq%g+z;zU*+*}uW(KdD~_w-8j z`5~h?hrg?l^XYN;ghhC)_WFdv1Do=V>{K$jdKEQ8a{qQCZA+?xZAS~b_Jx1NpEnPU zR`ALm{*w96v$G<9jO$u`h7RGX73HEBnTI;9eTn>$$>YiFA686C{T#Z_f5)@B_U{v^ z@|T&|khsBq_N-~gi1Ab1wpwNZ-%~mxBKOrIN^1;~IO@i;~0sc+D< zzRD(`Oj(wdA|2A!4_U3+r>SZN8!SXuh z;&pU6BR&!4Cq3rCdsr_#h^t@zM&EJOuEitwuF)aI)^>@7rR3d@5?QE3o2kEr-$>uU z&9me7W-q;kZR%RuTSa2^Z)LlrBD?+J2{2jm&4Ow7J!iZch1@nhc~y6KDRkRWN~*2* zp0)ScK!!$(emGZsZwduN23LJw%945Tma4TxrT=M?FKNHa%#Ed1MLB^`d_zfeS<9Ek zmOs24*Mw{GUg+oQl0^)Xtuh*M<*~b{cZV1mw?1~|=${|y-$=5%P7=3-W+*CQ%OC?r zB&rn|iyn}>ujpoS5XlPT?%HTkYqrX zSZn8Ib2v>#E8i})r&9scs)$L66x+3*RMz!eRYdj#XZLxEGF;X%6cRFAx|tJRrwp6x zS#XxN6<>;+CN6RNO|%{hTY?^fkWtkKvO{p(7pzaKw&R!iK*-R%0o6z2!D2GlxHjJtV=$@fjG@cRM9^zpY9^^`myAXn#Yh)(kCGQwj&i74q&&OK8)Wh~0zR zeaTH|?h>O0cC_1JXh{Q?I@v<@pBf>4SAH4B>7~W}{8|%#%Q61GFEQr)-$Ggk&zcqX z!l9#`)=BS-D`f}w)JoL7YRCy{JFMfUZ}H3Bo^WDT_33U)^Zu+ZJ_MBGhB;nJhl=K- zPi0&U9R`XQ0w?2U{QBC>kqbhV=hFR|3-z`P>qOb-4AbU~0>34thvwvtbRUafk6Uf| zMPTu=My3Wxk!0p`-B^Zl+bvFoD8FncNsb{ zC;TcMTNa6DZ@VZ_Kj4+9Aj|AJnz9gY2$uVK`=M?~*8?==xQ!O-*F>2k>bw#IA{(QR z4o?d8WyM(7k=-Z$28oV>hSJg~sP5B8LzcHOiu}`PR$1$OIO%onfxZ@}YzUyLr$DK- z?taOpX95C%bh^EAT;w7V`(#zYtwr^o|1Z!HBmbr@?Nh1?S42zoqho0Itl%DgC_>>IQ#dB zV0JXtVLHW1A|R7!MLT6yg5xNV?+u(5$V2R}sacR6Pa>a+0T$xoIzexWQmnC#E|HB# z2No*U2UG^U^?d7V>5E?0RV}P+1Pp%o^ylCpd1$Cs_jZYRfx7JnpQPQGkWg~U+ME}uT0moE?glaHuF_F-$Jb{t=H}C?E^ZRydYJ^w<$cVGd~2VM6|3d zb?Q91^y~M#!F_WqEKR2?$c?Iid~6Ci#oG7?V@{3$AJR?9ynQ);l(FI-jWvHcJnj4n zz%g8l7sc7GIr!;iRLgBf(X>)L8?Ax{Hzb=ctc`TS%d;w8$x>fmC@NCH5u$2@XaD8P zQ)Yt?M9E1jY&@(#el+3Ha=ST~hNu{r;o^?h)>U|TvUGMfF%ghgY3eXXkpez6|5{pV zy%{C1wOS2w!P7)vU+*TiCq_4n66D3 z)@Rs!eYBs%#H`b3n_62-nUWGxnjMR$s9O0wW*o)pE|tac;lWzZIihFlBs)0e>(7FM za=NfzzYgVNpDRgq&^4)Mh6lWz>Jyi*uIM&tG7qQ@$-)(_v@@-Bpw#o4#UDH$P3CB6 ztv1v<-G0X}aJpfl@ae(xE6Gaz!LvpwWUO{^VIU0CqQRa#UF>%APbG0pjgIu_!Smw!zuFVNU&wNMB-7H;-mscN`>X)%dCEeW^ zi;Ib4V(yS(YoEsNB>S+07@rM_Z07j&bI*$kE0sDyE2~)@9R&^#Q+Z5&R#xo}*Vb`# zb#$Rbe}$!T2cUHHRoA%0ztf6noS80{@sq-nkr5#;FdAePByl&ve;F4oy4DfpiSM;m z?dBHoMQRoPs7Fn02U%ql6l_Z6t~99qme|q}z!fu*^Z1fx^XBCC_yp^08v}2!SUC$@ z)6`@g_UJTZVKI{OfV9T(jB5?!>?5oSU7hJ0QTP3-+UlKosd5Rd0UV_dlB{ogK+>E^ znJ~T`p*+AOcNssMjKYFo)cXi$3DMcHm+ck%U+sXXXDUx54%NzmUb_q)e6efI}ksRuMOi=W5#efXc z{NnK2sEI3EP5>_Dt|9z=X&>roz-PdC)Len(ImjY??r+Y^wqIQ}kYKiRMr-t|NDo@J z>qnKtdHKUzeO---v#yDZG|3`AHHD_LuFkREbqS%X#FvOpSfLVwehkN*P(n$iFmjL%rVr7_?yYLN}Tw{QWzRYtQ8X*ia1Q#1E6aViLY z7?k#N&Y+*5IuCpP7-UjLzgpaV!3n`_;$<$v=sn?u3 zlc&)Gw@ZKQiqI;1yNo)y6$H_1!PX}yYkw2VL-CXvmpXvi501Eq)M8UJ!yvng@*D*H z2r0fR>*1}s7LtZZNdpR{t5u`bm zDP$PvI^QQ$ZXQ z=+(&iDd_=#j+t!U4d1}zUDMeH34l%rmKcYU18qx|3t+=JPNwMEv8e{4o6#x;`)Yim zIhYp`DNW5B`1mhp_a6k-6u7fbj&FWhRRyD_rd6LXn6=C0=;RYGkAqGos; zBe5}E+qP=CLc8|K%ZCF(#D3yR(KKRr^LZdb{yQs#H_N-rd-_EkJI0Cpu{%{wkDAmq zIQ;RZkg75?J>$EE5LKnMWdGcb(AM_4N`XAhIaTGM2*lzJ%oyF~^)h+U;zq7kGi?7X zchVdQ8T)WQ|ARmatdezSFJq6XXg2j2c{Lna3wl+{fTJrsYWPWTGw$*1a?SL#ma@d*5khiOTo zaMQSjt=f#n0vD}fnGN413W~+(ozvEhD1Q!n?4`bm6g!rqYA&_0geiZ!akAR`k!GtF z^F9~f%_1$je>CyXZ9FI2?R-|6gSt5Q&<;uO5Z#-r=l!XxOKMQ$Zhn~=D8W;>!DKwK zkX^g-I;=}$;#YY6&%Cs!*S+3>g;AZJRj6f(G}EuI+k2f^tzClr28*xWg5Jo0+NLr- zf6-;e-@K-m)st2P!ig3C^kn_^`#c5D4-?n6ANS5&?pp}8rbsP$NT|?@a&yfwYV$mm z(_HG<=TF!*CQgTapil?Tybz+m%kGq`GAq2S*6sMS0LjLZLt@L2ptkiF%}}Ke7GH{W zlA>EtrQEQe$ZN++E_L8AUfvClxy z`TjycIBR+dlsO-x2>$*Ujj|5<7d|fSFSF1h6=FG?>|<#9i*!^ggKi`)4sZ!3|Hxv2 zghi%(M*pXDP47Q>p~!3hVmt*05ROdG(#))+t%A~ZUn4Pa6Ybte-Tap@<$u@B`%j*k zRggaN_(ks6Ifya>)hZ3ymXAM(|KfP~cydsYqW%$vZ1mrj|5sib`NzLFF>5#2shoMx zzhH^~Y30AW^^dT+zgFaEGYPS*lDY43pA6CilNugQy4( zJZ_GtR5Ge_tjn)CL63F_6P}Z_1x?FzYquYN} z%?n2Gu)>kM;Oe<&NTB0C>P48!|Mf39xwxWjp8-vW8j%3eoeBTS<=^XGPxzQ%Rf9LH zeVhalYxLAWd0sxlbJL)7{6IZI1md~Yp3LLU{HYiJ?8JZc5-7$C4m@IjM-MOKL_1tx zaV;6I$h$en-;J*ip`6`1+Gjc;PW*3~-=KhIuPa?wIVNs%5>cTo^D91#cfc_6v}Kz1 z2i=|f6#wcRG=KHZ^FO-}{=mtkFX`(*?H0J@8wMiTX9fcT4Um)e}TNayXsmXB6vdbXm67l zLIIvgMuT4nrwMIv17YA)usY ze`V|3Yp=Be#AP5HFL2vq5B8O%YavgbZ9@}*O?|1#gpj(Vvr=ff?>K(D$o ze^M%&#nF3m3}{OQ%o8H2;d7)JCycwYF3<~dT($b7Z7B2U2xs_}bd0*{9tyxK+4Lm4 zzZ!H`6%`SH>%u}ZZn8QGA;9}OqhDP-M*)#^8~73oxqz-6f8!d zJ_|-O*Lf58qfugd0J}up^ZHp!B#Cg3l{TN@Cqg?*OJd(`NJqcZw!1UWdg!WK>7i{5#FZ1 z67iM|fG3`M1OR8>5yu12;|c7DyVH1SiOJpaT&Ir13{6A+l$)?Aejse+k{SlihOn68BM#!frU z3#`QVS6}g*10xXk?7N5$`h3VhgZuZ!^5a9~Sg9#(!k!_E2SN{~cMcoz z0q9w#-qdZymIBXjtJmAt`+D(8xhTI5(xfR@y>E(!vhRK^I7)JHnLnJPgr6cFL7q2N z#xgLrnz{Z}tFB_pzty1R@Vcp)oqGUF5eQ`Lp%`kh{9{{HB$PQ1Q%HlK>c?>T!-*WM zdAEhKIuoiH=e%_&fa{!0<-8o8+qS!ili_jwz0qNPPM|loAcS(hY6Mkp-AM?%R~4@< zu-dGviAGS_>H&(1pe@D{sv0qSKk{z_`t<3#xA_yC|{b^C^ARtIZ%&+%)9bCNcq!rJuuwv1@eajF}m3LCf4=w6Rk} zFxvMM;oJSkKg7 zh$ZK%IstcP>f9nUwoqnP*HbGaBh@5JiUar0hJ<8c!^5xs2*%tV4Yf(bhBM#W+f!#2 zz(|pio?T|I4(c4&eXiuf17}!>4GjnWoIO-dhmwDW4J>+oNqZQUg?GVLO$qVHT{ar)66p&0g z4nl8g=Gs0^*?Q1C*>?gx<{Mv-U&GG$$e6hK`R&VJ^ApR3F-Ys;g`E8@IS-M`fB? zxL-_wuumz#dnlQU6l4bV1}kCV2(didSg*02l6_C)Y9aF*9M|URvc5aEth$qS*B5YQ z-08^(A2V#sOkbv;&_7eYTcYt;isAacz?d@jwb&q8Q;^h9YGBu4EIB2J3vJBkcayhaNOJz?$&-Rw{2@InSV*608dhW#*81YZ zVTDtHd65I@t$>-@SmF1~Og%yGvs4M+E9_Vm!cSRb>{Ga&EtbW_{O6y!XpT&zk;YTUXu6+f>EV=flzgqpu}Jug0@ z#s`3!tG8V*`_Xgf65q?jY{@2xyqkXQJ zJe=H=Z@wM*PSq&>wUIhJ=&%1h{mZ}oU%t)%uW$0dc|ZS0S0F&)P@ZJnD&q z2SDJ%Th>7Fh=Y#m80a$mb1vczak6TtK%`05bsi@Bu4&}vBf5e9%i1J|TKeZwX{m9M zkq~Ax7HM~0j>MuUSy>IMmu7h667Rg&BKDtXUTH{tvne7-anRkZvh$H*dbeMJHqg^3 zCd*QD1HkSXSag!y73Tzrh*?{IZO|C%!h`)g^^gFY#9F|kcQ-r%2+w}N>=|NQ{-0c0 zcxh>=UNn-_#&6kIuWC3Zrg6fV`9OVV+5KU>@F0Rab1Ws{&BM@)K{jGzlkZ@P5AJme zu(q(0efMrZCl9@H)9C`bJDM(LJ8QG3{8Ka&fc;)qBDMN`i#4;dy1o6%;xJYq3P|J{ zwSp1g61APZTAxH1cLI-&wMF#eVB08poucGg6=vR=7L zXcI9>YPK+!)ZI{IgPmLbm~v`mxsy!_bm@9|MDOR+T~sP(DrUwVjt^xlHQ>6qZoM^6 z0CwbdiAN>=>dWyp;RUm>iD!9Xg=rmENq)|*-!1%8ixL?V>^!kVyxOs}%7=Mv!t|Fy zAMOBIvPN@9^G{g)({=gyY_$IS3+(aze$`+L9~j?e%v`A-Ifk$h98JxB0CuPITVm_GvZ zVsk~A)-wt-#;mYfJ=HI;Fe91bIz~^&o(6;;+)p62 zEfn(SbUXPI+Co*u?N5_BH6Ar8CYaRB=uw?F|o9gp( zZHo2!LyK%WzOA6SuTog>-S4J1zWqGO0I#cbd$H^pTx!YP>uXptIY5pIk9|3(O5dZ)H literal 0 HcmV?d00001 diff --git a/docs/img/figure4-m2m.png b/docs/img/figure4-m2m.png new file mode 100644 index 0000000000000000000000000000000000000000..b15272022f68d35970246b2f9cfb1652716da1d4 GIT binary patch literal 147669 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

p%4dMtf>UYW$Z2#UgB%WG>ERIiU$ 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≻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

7?7*Ed?qFpCTQso)$O+xx6)en$`| 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

PA3ky9*RO$ zsHh`m!+>goVG4{S!{I^fdNP(}*CA=hp+<-U*ohMZ!1bqy980~rU!)LkU|jK!ImNAQ znnzbV52X4PzZFpazw1;&$nHrLRe{VnI;8Jcjyc$~4I!SO6tPyc%;G7r^}A+~r*K`G@j*Ul6mRHXm`p&3t5sFV@{!u8d!(*89! zfvV8GY_}7RP?`}ih=M0X1t=)t2nQ%Cr2r2Qj;H`t`>w0Buib3-6`E5&Co-Gv@`+=(j_2W(%sDvBGM%eh=6qWcl>?N z|2!zq9d4Yv&))m&z1EtT*Xjy5Smam`2n0t-QC15AK`wT>N&S|vKGYlFn>9`Q0k4&tzn0U(3wE2tAG<)&o@x z3~g+3N`_)FxIyqR>aA%#?&*;rS3zQ8Y;-;7T@WPSnI{;K9<{6w3DU(7NqMA@oOC8_$Y)f@8@QDv0`i&z@`gG1_Y5Q?=PKhZ62$lu z?K7m$A0ae^Hqo*Wdr?T`lwPbH#DE(@WUDeD3|ZiSa4YHCDnhkPiBKZ@2v17Jsylqxssf;?YRt90YP-6@Z7`nbbCl$-25(yiT zparr;rX**dUj`X(DF4%+r{fUF=a0|8+n(P0PU2QiP6{S8;hJ;ocB4JgSz00gjF#l@}7 z%?0HFX=95q{lEviKJy;^d#CFlvAfIjpIyHgLU~O?6;Q8!_KlyZ7Ew$kVTN0-{7zK3 zYku;0M>#_`pk&!#)a?iD^WLPBhnl3);snJfdR&RD&~ zFa`u7`#F^LtJL$eUQ+fRG>YE;R(hY@y%7wTW*F#`#*@af4xw;=^P)0Lnk91LD<$h2 zt{;+={5_iXk;!hjoPD}2xUbxv-dJGd_Of(@VxmfoU=Ui+E=40*#v0HkV31@)T+#L_ zq7uY0(2PbCY0{~D6j0>Riq)afRir$5<&G>IWg_49QFH{t8+I4jp}_Mgv`+KY59~&n z*3y^c;qnDfzFB+nWF``S&idxwK>q5zVE*>E3j3!^iQ+QD+&zCt(fJ0PxH*UVrE7)Q zF_W#}wRDx(6+`4-^(s-?W6rInYJE@%Lgjie1yOyZX)LIu6b)1j)E5*>X($-@@s}`P zVxoj=_A%2Xm#Q={wc%e6kXrKcL>VZu)6Wqk5^~|ajg}dB$v_Yz!^_Z`i_Uj~ z@ABVWwCH!(%d+?t$?20vm#iJ@(1ky-)3A+1Z02nCZ}M$E-=sY;#>%mjgnhZv{>7p- z=AcZrg}24Og=Uk^E2UeQskK^~uY)g;Xe3>g-=tNe8C6Qi@0BS$@s3zYrKqYPWrp0I z#QqXm^T)Fde>H_lJ?)$RcleINpR_+HXFk|Ckh*J^i6)G^mR6DU4;LX~eeoxsR_o}=sUs#4unNPo7>-3YY1Ue!ec9rd^j=9ZJ zl6D#tyGC^Q;@6Ak#r&$RJT@kd<*8|@1{I1GniUFror2Z-B{{{Na_xq(lL0a*&?eWfqdF>RPhq*SUQ|5y*9~ zbMJgpa;|`FfL4#qfVPZ1Nh&rd0CN?i;kREmk-yA20v( zJ#@$f!GLfcavmyVLSv$3Qc>GgJIK^hYb}0JEUGH1Iy^BxQ7|!=A&~WoXP$REYddQ` zYrXl6k*^VZQ)yF+jZ)pFQL*7cQ-@20k+5Nrfv%Bhy?j+p72{lcReK43NvnFYdPVM> zUH{kPrqQOdX0oqa3uw#LP0me$7Iu~-cFlv;{RhdN$$@Qw@%OLLaiSJ+D=3X9gFG7C ziA3~7)-orv4b3r+K6q zuF>zBYaX#K6HNbT{pkNV4Jip>M6yGP36%;pM!voB51{Q%kPun6xu{#*mwNxXRqB~k zVpvt!R&N!p{m9=BRkWAe>60HPp|}yRgZ|8YL$~;C5!WAt%^VRGp&u!c;1+E_KFyOR z;4{4<EdONrkzo)G#l zCSdvXQTExgC;k1{A>hX_$XCR^)f&!R_Mt-+M|CwzB$qhT zo5q>1ZX+vF$0~4pVnMU~a1PGAt{^~#&&f~L3IR+H-vPhAh&@H{uZ%RR*e6(`8x4xkUR7JB)ommhxwqYL!-m|OmbgpQD!x2R>JsUW#Moo*rCVg z^Xc@8!X;Y7yQznd=ZQ~c{!7eAgh%2;?&S)J3k&~}aKGQVE0LM9o?^-+c_?^*SCbHP z1zpYV%}-9JPv@tdq$PJd2Nd1!_Uw_>ED!Y!QFV>_^6{9R>D})4`mFd+9u}@Y)z}Q6 z31)e;zpg#k{#J6EOqQJdh>DVadS}JQu?*@>R68j(DF~!CA>dXs864ACDQc-fApVRH zNXR<~1c>62}v4`Y7lyza_w zH{TZWljj?QeZnWeV!%gDf72t_#!+P{=;9mYM4fjW{TML?j)5-(54 zUwgWDcC&ToqTVf`K7~-bR3!hh)5Ys~&y)8uaJ|B#LOrKk#A{baGXc?~`_=jR^P@aG zme{2?eP~WSXbtZWLi}p3M;p4hm2p;kZIj&C{oGB?t_B|;J>koBl{n(yB&VLpjdkw3 z?v>P!#ZaXr!KTE=ojJjNZn3F$Do%f;g=l8y9<)k6(#Yf8dF)5OP(+Kq3#XwE^fODW z;%MZ?WX1mBccSEX>eFv)>fT&I`MLGw-dt@yT3)})o4Bx?K5P!Y1F|;|AG(O(UAB}&9rn7Y-xFJ z$v@G>Fm?T~kVP@6?x2?{s^HJ-5%UTGO1n;BS3}~dCm$6*>y$5Ch@iJo3`;Q(RL*{B ztf%DS6;zw4HE1z%CLe<9@#0D%ouzX0Z@3K{UOpO;1}!(qY3k^nhoVyn`Jc(U9)5*7 zIKH_f?SGBkPbBwL5@in>#nS&8f)O^{RNE!5 z^P#-`ZtQFZsg0Ar6ASF&T1edQ{|N1wKZ~^_UFOD)C17#L(HF5=CWkL&{&vgu31p^B zkg2WghkYvUZOS>%@f_k8(EP$bVNFW?rkZQx4XF{{UwIiD(vJmZYnFBt!{fHw=9g{D z>vz9jHoSe>ePeewVOy_Nw(7q(F>mTq#qeS~KuRP*;AL6?)WzO@0p3Nn5Dv@a9~~W)7O?D< zM!$%(z@=DN2V-&3hV(|H0zcJecxVXXwAwbyKQG!dbQ&PinXX>K)%M47reSX4NEBC$ zYHEC3r`2S!(UG}FXtIqwC;<^W*f%h+S#2tM|Ip*_o@;aM-zmt$vo=4Ur|MmOYGg&K zH`7q4kT`&-?d{!2D_J9pEP`%3n%^3Un;R)n2A%g_T=$Jy^O9ntzuRaer6E>XXxcjV zoy%)-6GH8&V)*PPRJs?*7S2p?Y+c7j9^WSIOZO22O{V&Nt|CKNuOvIMhokUyK#&xk zZ*HP&-=5f+O`0iHjO+^i3i@Z=Tc;da;<)qk^LFV6U@vGUF$Ss$tr%4zGXZ5aMgSdt zoIHJXZ1!aeTgLQsm5Pc=cVr}Wf>r4*`Hf>(PrF=ZyI(hUzrWZ)G@hKMi(w!#XTm|j zEc(yO+toI0JlrT#h82$FLy>E-DG!Qmx(!aOjyOGPNfsjiEkzw&!<^_-N39;a!e{?7 zN$^eMnPb=cmqL$wXxROg_`l=F0=FT>#lvR8vHlpn&oSBSeZ3) zJ#GG2)(Qy<7PG{%6~gNRehpxn&d&$i>*@{<4?8wy8YhdtdX=MI@)G;80zY%2xEQ+S zzF4KZQGioAzgNfJK6^7K*QYk)B$!p*LYbm0H^blj>0^0$qtQea*}T^(N1n|5xlxr~ z#jHuKcZDtPnx@M5}cX*TNDzYVE#{4C6wkq@+w@U?jlQ$0&ie zRaC$^o&#%(ii(<&Wp4ZRc{JM}?(M<1++C*E78jwJ#casbiNm_OYD~#{68!z#qt@RC z#mW;B5(LKB7rF1YA#H7KCZDiLFnv^>5GD=}o{8j5ZCeVD++ErtgUK=wV}70+cysA;m!^$&MmfJ&ZVlAdN=sEV7=xjaStW7pyk;e z>Nk7-6>=kBqhD_z-~!lp8$+}0iq8F$-@n2Hu=|VJLOG{es`p&}TaF3x*mHvOtATlF z{>15bI~Ief*{uZjyDkeFjx!|u==w@IYVWc$?dQ`m9R*M^2o zJu})6tulTIiNmpP5z-bg*mRY(m7M19?QPTP$W)smtryJ+ZE7=~JShUnAfm{ce-J4h zc)$mJK3>Qd^6>E?QseR_Z0&4qO^yY;O!M;ceBk?^SsY-n+Se?VN)&@${jQlz!mKX$ zMS2yQL^h=IdpEZAi~QK{gpjI42vU?I&P;6#o6d)|${Hs1)xm=p+uH}pnw!iA=$W+Z zALawHCW_;pn=2&|$-%Zpv^9wgQ&$Ou`?+;=z4r8)3s_uQ{hOQ#{X8lti^|JgFgu`` zu?3`&y>*-mPc(^Qa)p`GmV#frA(hu8Qc5x@)09WeMYJ?3{(&tXEjLkOpjsP>__<4K zGDsb{Eb>xdMX;tQ)8M&L+_rA#CJxf|_rJ?5hSzPLUOO}{{rI7(piod#Q?mF8)C0a^ z9v*S0r*S0a9iqf>vwP>Wn#qmyp*D+dLVr`O8IqMq-sMpx4sV~fTB5bJx0`Cppepu# zWXqE1Ue8lxSYd_!I`$P275z`j&E`yzfdCr~IHAp=ub8?xaNQW$U#gkTIjXL9b|uEF zoJ3fImbK^2Hv?k8d3g`3L4GnW;hK zx|#%@Z{^*tY=h15TlGih=Ls@Mj-`blLRP;k^uGC)!YK8*u&}>Mmli|X+&nL?V0n3Y zPwz!@Xed&112?R7rF?B^W(G0$%{rq!eS#bVbw!!~2`cjH>MG|FMzdbq{GJz2N(xJc z$1m#|+~iiLt5KKPLz2f$jfdN`?z^q-tGMpA%eCO!itZb>%;NgRzUHoTR~eapdV2cH z^~b|4_l$yc1u8%5fV+hheX}B#rB{~$i8R+^`dxjnzqzlr57!@89`0}dlX_#=ltK++ z+c&qlQecJHF_TFt$+(t=gnlj6MmPbXJkkrJAej<^w0MoFFC5TqG>O>!uGCpUEr zI)4|ZT$~cQMciT_Jv?ZG`+3o+>qO>NY@b8KM%@NT90u$nxNFywKApf~Z`W_vDxeoX zeh`J&<@%kJmE_*^J`Fk=rbuGGfe8CmCa~in;nVj!_ zIA6ccc}gkb`C+@&HE_=uau?KH47HXSIt?2}*pxPE=_Vk)%s`tfU26?I_q@46uCK30 zN0sywO!+d#H#fh;WwIoXwj`}6U}d!a*^f_5Y%jvu{{7peqn{Tnn5gL9+@E;){wneu z5Y-^nbM!rvGc+=CXKXi{KUZZ!6fA!nwJx9CWs~izWACrzKRaN)kseis5h$&rW-(wJ8cM?3k4xqt{9!3z! zO-xLH$Kpoa2X(WzbUW~qx0-O~s2{GjlSr27 zfWwc93`AJa%qEb+E^n=BN!4afPfxM?QQ!4S(tzZN6%jVS7e9<(hjVgp?6iqU`2`6yrn)&7F>3kODMk8HauQzW(ig%QY zv?^vXG~24VI62)Z=I7^!>%g{`las?3!DMYXXXNp$i%MY+hVCizsQpBuao zX(X+F(dl(Zd3pY879uSCg1ew#27(PCVBmUo5G)(%(98msAp}e*Y4_Ye&T3!wqk~Tp z!};Z9J$rjtsi%lqSO&iucsY@TdduRGmJC&S%}*N}7X6^bO3%#PJvc}hw3L-5lH1wY z+3?GK4?hji*C#3SDK2*i4wg`x*_Uk;nxh+Qat3}CFF&e!egE*9g(23GGcDOB!|lBZ z3#b^rcqlO?(jU;MhI0Ku#eSIV59YKa7(R`zf2xYFIF0w^#X-Q zGB%oF>hpN{`bCBj_*NY|JdEZwk#5aAS!CI)LwKoHnGR@>T)4yZL4uVc6LF-9mS z$wq(o3be2Y4R99FeDdT;P!99elZ3u$yM7MOuKQ?`2w}R}nA&F5G+X z+`rL?u|Pc3kUWzKye^Le}CJgU!(B2h8Ioa8LJI&mWG+9&1XIZ7x zhl-$WB_$;SE}j%1j*Uj0X3Qgd=ORj&hx_}D&TlO&dSThX76}OQc=;kNu%KULIM~_0 zvOkX-1Zci({g=Hr{N#+4cyc_X*f#-PmbInjtoXup`+{oqays>ks!YjdHO)CJztW9T z<)Z0C_=vFN=evYjApGPYj}626DjbIswJ)z2m2MIrTMiG}yB#tY8lZ%W zIs#d0N?NwI#A-84uv`C5bp}aY-AuHpdWamay3*@d8;y#J^Tny42Nm-hEy4DYW6RF2 z4yE$>y@3zOEP7RXaNGJO=e;&gxOl z`IYC83C^!yWXJrp)0R77J6f}QE`qO@Y9pXVzc5mG0Yk#mRS&BGo45fxfzzh0F|Wcv z?V?jsD2Oh>tRfcW8Gbp<=|Bei#4nv`y^~~F zIw@Wrl_G*`-6E~g?NfGm65H1ztr%4kQBAX&vBdL7;Yx_<=V6iQC z&=sc2oe3xfA72M-MulfAI;CD|$zmJHLNn#ll{hVxl5|*zjFw4$%_k8O8m69Ji+yD4 z^Pn>-h7w#x*$@*gVk6819#5@lCh~Y#g8mtaD(rK%lbpX|X zeaM_7DE?BE%?kfH8U+Q#`$0>P6~DZ9bllY+Cz?hJHzS~8#%7jXY+6Mi}Yo-iBv@sQrWW4@q#s+>M>*!!8We zHs(wNyOAT~+}=6piwxKWg@hKI1dZwD3s(2et7&3=i$E^}qsS~?jw#oxxDLqusyOcz z7K%439%EgR1K(1e!H()b_RSV^&U-A`9X0g&{#(T$Q z@3>O=LRuv)!7RpOqdNWoaq4M4SrmZVtbiJJyb41&yGPxGJqJZy zPz)0@U4HrQ(GmABEJ8YzMP?W_lIajby@*QSBpZcSKqN=&I_qL%)cd^7&K_1{RJGeS zc`CLc@v*|T{zWXE5>q34+vB7o_>!T20IXAEVumk0MP_D)cId z@LAfy z>RW-4wr$+@0grE4x>rO6pyXGC_sKLY;1t32eKGmxyn zvB?(g5R4v{UBDuJ8;2*CsvXBN!|%LoOBxw4!;P(V^c_ZFIuFfcGN0kE%Bekj;ftRD zo|LM5%ZNO~Z|u5IFb|_pn^_um=1Ikn6y=*5;W<$jO6gN0!s_Q9FxhEa^Y=R`l_P@i z&VjpARnf%KxSFPzy*j`;rmvSVOS&u1$*|ir5IuQ@X!3}8ukv>Vt6*s`SM`QW*!x1Z zk)HYR+f7X|lmkjTLKaEODJ>f5y(w!yYR6kIyM)oR$+{Rc***{&I+VRe-kioNh@O(|dz)%gMN=NQ@{EGM zu1$Km=CwEnXCvV(c^ZbiN^7Iu7FL1L$a0KPRbKb?gzNm?#J@7PqfPpr!+8S zEsqbk;>R%>A6_R-ZEF+JQ{dvEGm>H_aToq8g)I3eSe5Ci5_6Ty?Fuwq;T5s+g%rc_ z@85^_^UO3TDfeQ*Yx|xRK--FNmn5uo;}7QOBP|$|PW(HqiOo^3V;x=hADy|-)okw> zgQeeN!BPoh24Z2$QD(=j_uZiK=thwW8iFkcn^nw$HtzO*e_P3pqGC&{^592iT(nVWhv{qm01pApRGTgL*2&39;)nXpgQ?6I{pLp0qZK^W83lzN!r8h>u-wGV#56QC zJ(ZsacLWh*aew6N{e%1L4N; zNJ;}x2Zyl~ie@K4Q2~K1J%2w?_CU$1t&RQx39NFvZQ}nvZqo*}9VVL#{y}O3ih+TF zTZOxMm7d4Pe~?patFXRSx`3KW$xMw{=Yh>(R7r8MnIH2XzEl&>|8?!yXpBnNJ?P@T zz5kJzc}`!)$Zpb8Ut@%@)pVRhK6hzFBp4sI?NPeET{fPQ2xbmhf4pBWVR3+`a;)DU zbl)FSKk`VO? zRaj6WInV^u;BVB#Zi@d>YgX>6C|6dWSk_1Z1&@-4T4OK?&+)Jf>f6J%r@K{;8 zLCXhFlEFq9NlXLJ&HhKZ0NfJoZm9<}@69^pcaw(!M-BJ86&n*v9*MfTjHZ$!ulP^? zlhafblzjVcMDG{>XgEW)Bz(7qnVo~9RrmWBt+GnZ+v56A6V7eDYZ}8;)5>B?!_>I= zrQ(tj(@LxQuU~)aO<(v-nScr%vx)|~3co>#LLn-TKiSj(5Skhln5 zJ3G7iQumLyk$EN+G|lR4DX|4C44$ep{ADfDtfP1y>N5=tN+85cIGYvHD`p*9O_-Wr z%w6A)${D-YetR~fa?ss|TwZQlZ>V1v#GCQn_%}LeP(h7aoqhOWd7LvfnyFFEge+&} z*2mF9`oOz=HYe3yu{qTbboI4D(E3GBIrxiM>GHsqBD>x58*&@Zw%{R+rjvo#nh`T$ zD_;2GBP=jv+b^?tb(fMLUS3HTM_&>zT$1MJ7+EkoF+E9~sqpWo5z=2o#%`Wc#>l$2 zW*ZADfF?db9K7g~#?2g=eI}Dkd&<*i1_3uD*MEMowbTELzGC~2SfFif#gfdtdM za6Cz0&V4=D`=3*&;QIjRlqHSLjjpKB~T0Zf~zt^U4J1^P1h^!U@l*sJ!7< zI5;@1azbh|Uo^9f8^A^3{TBL>p!@rR(QK^DM2g+UduUeU1Mtqlfd?=I#%R_6bSnS6 zO{gty{@P>(^^I;uj(MJvPnsJE^Qvi|DK=Z1%0q0q!1M4&tUnk3e@dV?f$e>M1>lZ! zH_*jx18h?GoSg`ZkB`r#XdYm1i;IT(Mgt-Ve8R#zz-@JPwaY#km*i?|Yr8PM?AhNG zS4vV`{(c^%RR)^n73II*_?_L|zo%FM6xr6!Zgq^Y&iSpat!=z}GeED|{)5*6bg{NH zHO`dYblic#RA~k4JXz2M;MSniO=4(XP;cvQN!exxpi;5^4F7ksL5?&>D0GygWVo)< zz4fnNbR565r(ADR*dNYfy$^cwj-dhcKOU7sEl4$qRz>W6*qReUjH42)b;G4;o3SN^ zVIX5XoAmMaR!-W~LlTw3Q>?Mmmj_+vmuHo1;mMog&FXWYFgDh=Qz~&co!95LH=vG| zrAuOHl_!!o_M3BV4CcvuOYH>9cit~@lBq%R0Z~OBe zUZ*<7Q1keWVP~j{@4=|b79jHB%S~0#Ok)cw(|OZMVkg5?V@#J~sB#hmSc-NHfbg2^ z0MLu$R-qxAEv6yWCZl*q?*cSe3^BeOA6ywOJGFlLiQ{3kc+E5b{D1I`4ggqLVynLa zz~7LCXr+9GBt{mjJ>wJ`L5xm?Zu1QSGYl_oa7Eee3EDgHys4CY!&JtyN|zKKj0kDY zi*5kSTh$sg8hJ5h>EeaU(rxI`RUw1`yz^1fiS$AL#o5_ei$Fu1-#H{eJkL0G;qbCu zw=>25v8zkGS)Lk1nxlPY@$K#HD*38hZhQMUl5C-7^9^#8G%X4IofeURh{%m&%lL~} zqxO_8{9Orj$t8?d-iGpW&d8sT7SbyY;y6ww<5orARugZY?y3JRr2E&cxj&H>pH}yX z5x>3wK3DHh+Xr*l$X3G@%n0jC(tBevZvc1gcrI})f%QyFO*MJp#v>tN4h$fI0Eh(u z(yA%y{8!B+EG*w$(j^pqu0O>Ao%ec`HIch)_KDZ+;=WON&~mY;-&TupW^gPp zLv<*nu^75^ch_N@^Ot@qz}Eok(uc`3;}~CK&TZsYmz8PdwlN zq;Y3N3NK&*EVW3`jMr*apB6+Y!Nc+C05`T$g{O+WfSL#6V)_3Nm1b#5elA`kk>>u@v%0oAl0h2tRK2APWpFNmpiZ%JCX?Wa|04UL17rmSb{{x4N1$(qg6H6%m$~mshK0IKcFU z6Fk_O-)&z%JOnkbtbon988-_Q?xQ$dGWenpuibTTyvKEkf@aa%u1h9!g2DWErqtK= z^FJ_|@p}KbZ9HF3KnvjNr_l*LDwHS^zc~JoFLaIE=p{P2oN~J<;fd_M%*mZL;l1XL zE`G6o)YMD>ErDj13gO_b-QfQ1?7QabHqXgP?6`dOO~63ViBjJz$_OVaH)8(jaOjl& z!`*fvJ$=oGQvj&|FJR+;Y;$%3Ox6m4aBRWN?d|-?QF}L{**Oa#GzAZ29Q$HL^!4>ggKf;zYx`>)ci3=;csiRS$%)j8UD znx*k-tJ1wNTk=m4^T`OlNdTsncFTdlkKbxniPVf?LA$wcgLdfAm$5D{5RrQpWIE;& zf%lE+nSCYVi;FXkw!Hg}EvF;+6l;lGL7{hm4s7^ajrud!b%X111&KmawkzZ~d7%k& z5$j=H;^zTZ=*G9@xE<*DxGypBB<6NIpldQsW$h$)OWo$Aq8$j^5p7YGUELUSlu|ocwjD$zPP~x<0w&xVsdU8Ayb*oQJDrT@BXi{ znzf-@Hi@PN6V0=@h0fMV#&uYvEp9Z~oACL4o__jH??1K!ZB!A>t*!4ZNB6@Cr z;N^zn`iI|IzaQ*sKiRgomtCyog{{j~6)+8BLi#e2^uq&=6H!A~JkPxV8uV=y5<{*<2YJVq%;1m2a^t2VlY zjJ#cYXEbrWqml8lqst3v9-W)S^zZvbl%AA%184thl1JnsEvT*SZ~kT)B9V8GDSsu| z5K#z7b~1~7{%Zi@dNS6obH6VudqF~Y3%ahk(zJMZWL^OaypBk&^<=Em{PAY~u;CnVmSTBtd3MvhTnA%K_Q4#%4o4V#Fw!jL=SN6TRP`2sF zFq)RSTYk0VZ9kGzmz8omX=xLeEx>qg&@CILdd2Mn&@ubhEPMj_s6KDw?i4?PmbBN= zwXZB}jP-NSUn@Mh1;K}(Pu*_!7h1JDZx77b(>JPuB!mC*{9TNfM0M@do^jia;Vo1Ro9OOKT;c0%3*k3iXyv*&E35Yp+<1Kf0YPFdZWkx)Vj?)}h zb8qRuE?K&{u8fBhz%qtDz4K-#Mt`$>A@P8aa9QiPo)kS~7`|?DmbhHWPYmLpp2mNX zg0Rg|yjRjBCMCrOPiKInrmvNIQsra?k^j#69Zmf*!AM} zUex=q8+@5i4mMwH>N2?8?6mkx&+NaDD&ywd-UXz-^#_iJO^u_Qc|_o0B+IYM2etyu zP4_>4j*v^|zr75LfcA;|ikWo>`wd3+FI*b@Oj7BA`QDoPUKp;NzS?;B8tAZYf*z)T z`_2k!z*oG*WBa7kwT5Wz-wg0F>k(lAnnqcy?CY1-h)--Kchu20GPzU%|fNR0YG_DbNV_WXGg<55@ zI-nTj*bM(*Td$<*3L=fUT!*zW8vMtx8Cy`v6idS_+xTIA@7%prHJT356s8W+8PMG5 z0D5uTpiGA@kz%+^uY&DEvk=gojBW8^rqs~Hep9(K0U#l*xY`VO&J&sXMJH2sCfUX- z)enTh_W!v{6G^!&U;Gc{$^gvVt*UN?F2NtB=A|X`GRkYL1kjIo7Z@e#0pOQ%Q*&BB-u;DjqDXVtoWM=`#O6-fc=t~1a{@8I`wNa#-wrO8;;zJo}z7> zm8`u!p4>oB9lQIcl2ea^zj4xQz}7T45@*IrHE{@jvme<$}q z;Hy}YO{;+Rm#^1YWTMXt03s=vRRxO)Z;1ZRUzIWjXe5;_0?1T8%30nW=GtY~$?tgNj=pDL5BTEh2Sej<%cB87?WBd?6 zhf1#*QQ8sxoMXesPPM_WSfAukN-~laPME2l&6=>iCGe$neqAxknu6uGDWoo{S;wvq zh_GEf-9}SHCx#{mcD%fmTqu{?jD2R{ELl(;6z%AnOk&V!mU;U@+n#IJHF3dAUf{(d zI+gFwi)B}83JNWvLH>z<3WoF?(~irvLrRIDyUU=%%V;1OxcO0?=77(RAuW;gsB9lOY4}pMd!TYP;wNI3SI6Vj0R6lVpb& zM}Rs9NWw;X_kaERwYj;uvXYGuN@4)adn0Z=2{AEvEC!&$gnyns6bcOp5X+iKdI@?%&@T*BD1zQkCw9cMNGl`# z-=Hyn{``4XZZ4DTi&$PkLBW?V?F>^9Lg3sgZ-;-(b;hPObBglB_&A;|py(T;>0Vuq z{D9DmU|weytAU}aif5Y;AOGrjfDjd+h?L-N85vM~0X{w%Eb|YrnVkk`W@hoku+rSKopAYdDCyAnIJTe1auayP5v5b`?qJ;AiEY#uu+#&_HaXea zuViM2Sks!|AI&PqjDqhW_W`KjKEME#yRbk?8>?t5n9KtqOCPZaVKSht?kgw}D_q(xDi4d}2~vLxb>% zf*$IIL8n<>b~eN60>&_?tVfrC9wRe1H|U^sn?b40Su{ntk(?oEN)`n$OvlH^Wje|= zb8kph&d*Got2H^1KR6a0ms!=FCnb!Frc~$pd9G_B$GeafT;$Q0R*^ z_%p0)AWcw)0l5P(HGn~6Q2<0-to7no(Z}v&>~xSXqj|Bd1T?{)tnmRsq5flhTs02# zVY$Gm?eYRsS+*=|zY4VrA^m&|!s@Bz3U0;t6M{n#Hyr#h^wM%&+{L{Pm)+(>WtNZ| zyVQYDQA3sg#1AqU`M%XKy#Ffk5wESSI4@XS+ZDt0S`9M(OG#}iHDuriKZM(JiF+)5 zsP8VI&@9i?fZI_Znl6Ew>wUc1)@fuhStQTPmoHzvQu_mxt#Clw;Toi%L28Ekkrq8g zQk3ym8I`{OxccE#Wv?IvaVQiQm(W=8hJ2e~lx$@0=fX445sPVq(o4&eetf!F>1{d;J5cyyO74Gw&`qt(U` zfj|Hk89*h%iinAc0ZP-PX2#@F7OY-wLBTnruU1x8vLE=>vVfmwWYEUar6{X`H4d}^ zlDimLX#j`-?;%UGzuMV>h6Q+gdj|#OR>JBQfFmkqWVE)n5}yE|TZdInub>zT{161W z>>I$|YxWJa5f(%b0p=M@;LC4grsNE8f9XA;$?@@Va8C;r@x0()G0Y%hqYM5;8SsL$ ze-v-Qm{H)}QViXhx_Uwmt^Gy|dRuj}`}+h0fJ{3d@4N4VAEq8@69KeAu?;tWI2j}{ z7vtfPA`n&5CPofE*AJGPsVuNhK*$zOD^LgifMq{-?Kr+zHrMF-%%a?C5_Hsy-hC9o zb+-c$YeEgN>GJDBme}(0FL`ayfAUPtPjfAR9$w&((y3UgghXRUW!-;WX&Deql4*=%!idwG_vw^20 zEQSJwO9q{^GzJ5yqC(4DO|;>dHLpS<)xWwXg<CCOPUA?$0>qEi#SE56)^=F-%ueM^YCbzDyRM4ZgA4e%kw)qBAlWSoBP$up!DU-Q`%h;-rqw04t>{q z-LM1ylKO2iW@3*fC-1duDj7}I1gmWSsJZ2)A;z6Ojshd>24~T%3Gw^08EeL*8vFuJ zQ&*)38^g>39m*0x!GnNn_|4NU3yG`1?cBH7!8h+crsnOo?J^@5$xDG|;6!P?+dJR+ zpJc|e*7sx}X3$B~E4$9hH~}G~z6u*l`ofBZZzQK_G$R%5koJL(K(0%E&OI(891xzy zvO8^E!H*B%#x91{L~w;H^nL}I}h~T*}f+3+0sSscMG-d zWwN$?WMnXxHYJbjVjJP6&M=m`6^zbUMEd%DHOeQd;1xHD8Eyt8I@yfkMIBvm2k@#W zf6T9>Bf0;KGE?^W&19Wx`~R*Trh?nlCm=pi4#&u6J65yhX${sfyhyMTO_*8SS#eL^ z7t-4M$evsrX3a=I6Tsx3#0~n73th=i7AmIH52=Y;@W#eL7ONV$W}s2h_JlMk4-a_JFsl2oeCguzx$Nsg%yGveSL+dq+b(6=Bjy zsWaU@$9>C3;A3F>?=UsT-`tP=_*X=)(k5&LM$!Kb6wnpsSAA)Eq~>W;S76SAmMO|x zK(Xm*XLptO@F&se_8g>#DC&n%uX$4$P8cuvUPiV_l^zOG4RjY0yb(bH62aNU1t{kC zmz(i5O3Z2$3a7JN+Aw>z#V;96ALWpWFU0U8Es!@4#>!yEixm6jg)HUwSckDSIJmgE z_}8)H?awfb5#RY&*LEYful084Vioog$Uuu1Y4L$W@om#QAWL0M!52at{$9`l!WsIg zd;58R1e%#JjHt>g-j}VCTu>(xE)AkBSZY0^8wc{9{dlBhOh53l|M=!5Z2@Se%j2_a zBNie$r1R%Gh=N}`b8jX?Py z5kGB%L_sa-K&9}s7dbNWb9p+I9bo58-0YiR{U-`%&)73NJ|rP{==9lSyta@HiZreXYFwR0<$XdWz)dkd`c0@hV<JM0L~FH)p6b=tGLM^up(x&m*(rl85Yzgu+g z$PRCka9|FOm~(zS#M=7y(6q^?HuJBHH6OUBY21r{HllCe@vNfjya}ASS8-8uEGEIr z`<-=mHlZr;^RtXZw)D|D69xh!qp|L`q}vfMal=%A96SQJ{58?-4{eRWqj%IDfODh@ zIO=r`zRkK^yA8~n(y!7B6sMj#HNW}Zz4G?(DfsSCX6DUNl!Uaw#lz*}q{RJ<#NEN+ z!vUy!huRPP(PNgQ3k)h|Eo;*YMv{J*%*ez8#k8JYFhJttGWOZn*K0<{cwYG!0P9aYIeH+A;wt}{zlbLuUF*IvfDJBpi5(fKUp&CQoiW9S(n zACxx+@Y&fzwjj^LqN}O&QrT+`w~AEG2T|4v`F#KS>7@QV4zu{6h!Kxw$x|8l9q9Z) zP~~{*{*;pnsouChM3>9gLNa_$eS>E$xA(_jZg~93mvvrj;*!OgXTMI+mmvn(1l*L0 zvUItrzGBx5Ow2fHg~qr8aTH&WqX!|cxn6s$A*cVKfN&5Ks^?gtg#2#6xb*Lo1qx** zW*!m$->@o=yjj=fi=Lhkwr3TnQoa%JyJyk>qh{m6DS_Ixc-@uT!`U$UD1BC7KYK#C z0ydr`F_^yHKZgJ~`k)0dx!QjD;T`qKL`+O85Ksd`_-@YielG8RG4G?qKbddaVb@!L z^t!a~H%(7#>gqZ#yR<&eI#i_;)_Rloa?G_F^u{kpG!t}C0BGu0N~$?Tg~Bd(u5`lY0WH}M2o0S_uNM%2-t!fEv2aFyziDU_k1Z}zSA$<(Am&q+?+ zlT=WA_Eyd+mHffo;+_$-$?O*5{-4-W-3S`VluoTx|EXIVrLXL-iF6vtvHd&O>RWUF zoGR2B4E7N{>@P3y$9B&5Arxw9c*aGd`yBe=M06SV1EwYV&jjY>mcGcyKIB>|3pv^4?l>O>XP?+ zAGJ>X>^S|a{q}O`d>)+?P2w&H^Z#*lp7CsceHf1!BVzB^N=iz!F^bx&EgDMgz17}Z z>{Y8;d#l}=rD_wic2U%(R*h=y{oMcOg*QHUapykHIoI#HzL$5u`ts|(+#sgNkS8rB zrg~K|u}40VpBfQ&F{u~)Q4Rb=yMUJ*9=o^M0mvuGk37w50Ht!**n9WEmjxvaz{a;O zmcV^>Gj?}Tn;5v4D3)+^bp!YbnaHRRH@|f{?DbOFiMU&G(|z9-V2==L)t`EPxjm*j zHV&Q`wyys#3p0>OAuv?OzrQ-z3;yDV9jY?~dB|Y;`cHazehbEIUzd6k`1%>=2x;N%^Olv;r#6`*;>7#O9!l z{zy+Z@@%ejU) z3lBPdWaz(7I{nKV5r^C9r{Cv#L5c{YHF3I6p=OX^7I0j2-U;rx1IFRfmx5&ZdEo*b zCl5gFyVcV3ImBH@cQ?VKum224djxNhuvyH%0-bl$>q~{FnH|2ki(bBYa_8N#yWm?& z@@~b0I&HP81%wJZBM+zyD`O@9(Wxq)kn*e;aF@>nXJnORsFwova(>~8r@yntwhZF> zUdpZ0WFnMx30!$P_hi|!)Ef^+IYLkYw#Kya?4RTBH3UN>L?*GkHi z5sCWh>w;iNxakV!7D<#5r64(hNhItLt1vOqdhlp`>TWEsI|S!H^KoK3`t=eN=g769 zJ5Do+77`B8WSxUS@EYUWrN&X2dUxEXv?tsdLmasB*ns)5Yp-{5P=?Ljrp%1){4PH_i%2y<+}_U~pX zm~!bK-GW=ojW^rLkhZwvT1Oy*ySvYJfy`FMW!UhDnTyMVQ;|Bv*3reuj9a7`khAMp z{`1N`)8o`s3uM#StsPPH@WbEiuwFb>}uD0y0x?g0~(Um)l z9zwWS@qs6{u;oU7fp6YP?-e(dD}P5wiCxF?U7fNw=sWAruHfM3m;q z6%o%9k_}KbH?JIVFv+$-hvaN{8=Yj^LA`w;TPsIqoA@*=gZRzOLXy#Llr#|vxD_Nr zxPogV&M&b+hF%#&x|>GFkH*l6*EESzB=fRj(WVsCDThQ}2l1m7ui%^JnJ-3GR%G3M z24arycTr=;DV=x^0Ai4VSAYXrXPWc@bvPQS+m-#0M;eeAfB z*!=nWZaI$F24$VBlYDdOwyV3j6wVsoOG;ude^8-!aZ+VjH$$KBwQLuju4L zX1G@$rHj~n?|UBrv_SUC(r z@u`e-A`9$hYpGkj%)AIIh}nQW9~E!W@3I=vbZKwsN$bFj+Lt_MzsJvz*P}_;#a^fZ zCvjk|zc2X@-~C+dQo_{|W}t-TBk`{mFm#bs&Khk9$#-;Z#f)Sj9}#qcjc^oYe`I7_ z>>Up1pV{y2??0(OBC7%P=L5mJx}yx6l@oxS^0P&B^`z1N!QMuiK{<{4c}=d$$~XR) z#tMEC-q?LtIp#9aEaSXHXKGr|=6d4m=g-@@#+YFyUk{oSOqOVbxZ!R`Bh%)|;y%cC zBcUbl0qG_=nf|Q>x#{h+iEZ$?N#NKMaBiB9am&AN^mep5)2O~iysgarppLsY!qhW1 z1`Sq2bo3{~0|WYB>jCpyJDE>JZc1FtjZ=u@yg;P5@#;1cyH@3!easfH^ej8;@jz!Qmu?dXt}7(i2AS&il(~&;ndALPZXm(2DLY`2x&=x)?O| zI}!fGwEUur#qks?v{YpLJ*nWE1wV;rw6KLlH%19>41M|WN6SpsQ+i>Y$Dk*HXb;sA zmpuwCGGoCe1OxM%usIrB*&2i;qZGLUj{j(XR~S-_fslu{_Ka`A%Rt6Yl zCE$Vcv(D#GJUq!NG8Co^hdvxSF2^7`dXDIGVq|MZN<%`G!0~bRrmX;ilL>oB=dhG8 zg=#fcFkkV71xiu=`z6?p@S+n6Ue^lG_82!CU5j3I@UXSv44HT47^>rUVISf|H;5cRWG={>$PlwE7-hDjNdc+E1^T0U^Ok?OUBB zpd3m{UjM6deF$o^A}w^~re1k+NV3v~yia6o%d~>A%I8z@>SpWqUoJi)KP*LjA$XJ1 zVe#vbn?6xabX82Hrz;M5-@~)Wus?}ykoCMRxtC>q~&QZ2f-CYUWi3LiUQ zePePF>$R1m#VyC64^E$Y^Jn{Lz^dk5$a?++W-Q8_7!lXSK@(@Hr%Q)mj*QhOi@?Rl z!IAd!UnQMsbc)Z@6v7?HVW2lwA(eb24g7c^ruZHaZu8rG#&<(W+?}#7}0;6v^qKdGUk#hK3*D6E8 zHLo_}DM#kU#Z27(I!?&ZGhMH>o*2MmN;fe=hP`_xJL9P-JotXXfL75kd0c zhS+A6oPv1EWK6eg6CKU$=ui<$91stSBdCoB<{Mq7yD0L4+Xp!GGQps?5RIM{x}E@{NZd-zLKFsy-^PV)`o)Q`b~Tk|$PdPMBcmZmjbQJ_ zX=NR@8E-tB2DPVSYg1IbI9X+-;-0kB+L5Nxx$H48iEoxhP)9|CMb$ded@Z~SeUicK z6rCr&&i?TwX8c7^XsyusjSDPw3tcYM1(G@pNy~oty|mLcdvsJ?2TE7j@RRWp8}u*b zd_Y--6WnwCn#qAV6x6SG@cwMC{!>Dyputc6Voe)PS7_z?DKcz*aL4#uObV}%bW{Q5 zn*=m4n)&`*XI8g-?lUaXs~r8GV*Byr`Q8xDz4s)=ldrr*>-1n!Z%mVxYO#Aet0qqB zg^u$T6}R?YTUWH8+^v+sUvotL`ZAUFx%?C)5e$*8#b)|Q3Kt*aQaE7^h@=Lm@{|2+ zCXlowi^2*5w_Fe(tq%cY4+9MLUe6frzMg907YGx@a779EQniB`bCpeMY)-6?*V=8z zgqRGJT_=3mB|fP3S!Z8v^Z0qZYbnpmw@x|xa;B}` zG`%`*wduGNM4d=>el*RyAv3HD`v>ncu{7h7Y{^wL$})px??~+UH@k&S)AN=a$2V4W ztmGgg?fY0{;Y2-e7F+Pb{D(C z^9Hg)<}B|-d36@zHa-z=h@)fp3lhIA`MmA+1Zs-s2UnTI7Cd_PvA@Xqr$R%mT=R}n z-gkvYqS)>NJTy%>HvPp!;@yJpt^p1lG~?j*;z@)IsDxnXq4%FnNmlaw{W+{B_jbpX zw3sVCB

^?Eu3*73`_!E&gTEJZ4Ir5W0^7(4+md_Yyn#+AxAWY zUFjE{wtp40ob)77@-{3a^^X`mxT6g=%PQt<^#j|2ZZ}NaUsQc7p23+$g~6htSQU`? zv7u(^gX)2k@Xg7AUor;cWPj?%I2n?bPw6x$c0|S^#b59L`Rg;P`O`B$bI3PSjf+W9 zD)GX1Xm{i4a(psuGNr*$)`f?+T%UlnzD#SPY`t$ryzQoPzI6WWPwipz`=t&`-#$#L zly)7RJn`r3TG1DD|G(>$Oml(41+=c!a{ys|(MH(YcWWag6{$)56_)w%WQay&d>(27 z!}zWSd^y2>t_(Xx&uDgFk9na8h?>!;m2rZJAq|tx;KlXt@!oLGXe;nb_6{1```LJv*kxMfO&u&hAVie zbI{=D*ZFrGg{DD}HfQ`fA_~EIVc;tc&S!}-X}k1UIy&M-J^{;O?99u^8<@QI1{Qqe zXQlwLn1-NVwxMGG=e=yB&NrWn?W$Fral5;56WDP0*Jht~hTHim=7}=M3B;G?%9V#F zu}O^euSAQ&yiSc947Fw(A3y!B`)axU#(~o^vqUfTgwuQOK_{UYhG0$l=DtxSZ~~fq zVPQsR;JsS7=rd%XbN!)StB{BHu!t>WePTXN2~YZ#vLVo5d&IdrHiW?^ak#UQlRCv` z&F;h2#I(s@zlJyg{is5}dv;Rd8cOU$sfCCvZEfWl<`!?TdYTJ#+fcuIjJ0blH~~$u zii}m1ylJ`|{Zd={*7~Qs>v4GvMKmPkA9-9{sd$V?UI<4APa_| zJL6u!PTZa*Vfj-_H==Xj#Im+k==w1-s6k+iVVd;F?y~<({;i7!UOb^f`)iNc1Ve@D z*p@})VArN6`KYs>zP2{@XS2R;c8d=ak+~L~;D1led_bg+6>yOaJr;@D&R=_# zCt(1aN+>O4!6RtBO4Hf-^_@W}yr9~ETRX&&S&|$pgADh9y?mL(?bC*)iRKKIJ(T1o zhJh2o@r1y+GpdT4ig+AlNt|)dunA1+J}i1ihD(|nR?)p4vF?G&Y%mbq+Qqe{2HDb8 z?0I`xo6=K_6y7ePng>(% zO&6YWMb^=UPb9D3{^NS8$&zxQa7RN*5*Cc6rh9Y+;FI)eDBp}3W z5>(WRe~SLOGqn8pMxuM}6BFKm+Gv0K9kox}iXM>t(47@$%7^dGO(vM&g_1H({f_SM zbTwsgf#p77kaUC0>=}i;v-772nMoG_SF$-R*WjK;62Jinp%Y2?|_6fJW{Qg(|^7Csbz!E@I z>qyQ-{Y7DCQyVy!&}MU0Y)_)|q+(MnkYvYX9*#_!%Qv~37hBaeH)I&W_o}HO(lRWM z&4E{PmrCmvdo<7oA8k7mE7&*#=TlyWP;Z zZF_CvsNiHj9r$?yOzvx=Aye@y?5U$#%hl&S{P`cYKM(zGY$&CYO=;=qmDGo2iVMn# zE@dBY?@tCwwQ3~`;<+H=&V2M)^lT?3uVN-V7f~Zh^*a*Yo~^zzi;YGwR;7X(*xOIq z1v}AwCtup*ae~YowWa|Bm2v;qN~=D!7AMX3F^NRNUlPX7>Tj-ceQ@*Hhsw0NA3Z{A z+cM$fxUtr$zKdxY1Kplcf-vjmqVI$K!#AAkB@k3;m#z_MC#?!(=`q zoW!qr!ohTp8%4H?^k4}KJ0t{w`x6VbfV)z=)G;()Uy+^W@|S4m(d-?*jK!pgN6*NY z_NUT|I+GwT>&VBxkP|{!XB|=sr#}QnsM8mNEt{U00jJ9IP@i4#1qObu3Nu>~E1TZ_ zWFWa3%@xbvZ3|Tt{(Um}ifx7a?SQ4Lk92BX4dPJJT&QPF%nwrc#*`u!S6zWUT9fYR zQF;c>n5heO`-R%xf?Mj-;6_J-lCS0&uVqZVSeM(to{TB$`7FmLToVKa*lx*hw5Tk& z_5?hvR42>ks#DfFi0PL?z83JV?;9k&m^8WMJIeLW7!+V6dc_y5kiEkw3FxTnAc7$! zZ~DHzu#9fTQx1%7xfn?Pp^D1V`_9To_i0D~&1c>gm918!4i6CNJx5?i>$^`!#c~&- zGF*|cs%EA~pMAS}`Br#K^;3(5nF{i5XcC_g_Vpbi6IjCF-L=VkJ+(#Oh5dDU*s*b> zO=nWWsvK<~6AW$X=zhD*-K<=w5P4=NIcjP1|E{5 zDHCRA7NU3sBCdh)s%Y4}S=`cUaQC7M0WmN^!jvX=cAxjGfATM(8wahWr0Aw&uVEyQ z4<*%M1bBZ7qA26pslqtB8dt?Y_mHT&8#az_4$s(BN56JO;N#+-p<<_UHx!C z5AU~{Ij5F|^Wozp08jrDhtHEs|K;7o>*xAG6MvT1dR%`IOP(QO6+L)reE9`z&ToxX z)EIbdNifCm-h$Op?JIK96+N%H!`Do&Gi`zeIVp&M(VM%$z%XI?pi;w)Xo1^v`!YuuGK+Ol33o6G&A@K_c7) z#)7SYAY}d0_Q&+3JE0mTBm2YM{&LbbhTYVaSUxa`=KQ9>fe;ven=;^sQ7A$jyM=Xt zON_ek?vPVe#ZIB^y%c`0AB4&#^U|29Goe7LVkQK2qU4*!mJG-Fwl0um3`x|5d{l3d za!opThyxa3#=#UZ1OC>xxv#6c!oxE>k~P=ZV9o1qTb_U&D=5NqqrS=wv6IsgdJ^e} z2WmA|p`9qGit+rp3gAhD-^)GpJ&23+WRnW#Ql1N7+#s>$9hdGt_-PkdGYO4UHS$&` zV8E9>tRiXF2hkI)OyoUGynf?*i~PlOEwqUZ9%tW>P`|TYxms4%q1U3W35Ajw*eR?H z6VQJ=jCw9(zFdDk_=1AW!klnJ{4!-wVi`X#_;Js-y3ZC-_erEL82fn8eC!iAsj~IN z+Oi^vcQ^O7_zr#2vJ#vRwjNo*o)BsGcH<(LQzNPb_GXC{L;Az4sc8sEYG8M5(kLZm zSH7HnLS4-B2WD&dxIx?EmW)Bt*ElLHT*kE4Ihbu+@xIv#K3sy8_5o!ty19sazEenuK67PV7}&a zv^8}}j6JO}IYT7sGl5#zx)2J|4@75fhJ*?2tyD({fYgTT&IZFask++gIF1YMzkBbV zjeF|S=|T%J*3_(&h2T((?Ynmm!)Hh}@YUAGIkYN)cQbU>3_XVS+14myLXhIM5nV8j z|0NHMe!bp`D^~G4(3~xSO;IdVlQ98LTTwE^4=)CaMCj7d5a>mI?0hIBgp;bsj`3qD zK;{O1muj97b3S{15b(sfdiLGxw+a^lkrm&k?JZq##w4TUqq=aUq+5+qSrm$gk_hS% zLS4f5B*32vGZgvz=PtSJ=Etw{9Hs4kayfg>Ikh_P)e{iS2cF+*>1vjw=RZ+`v4*Hp*qGMYNE*e zUHJQ&Cd`C}Sn1`%dV~XY=4k9|ZF&aeKK@{OK=8blgqZoHEw-l@K#lJvU63`HUR|sa6hX3@}M1jmSQYFd9~?qT`T?8t_O=W+iC(vZ;H4ih zRr6z_iC2Kcwgy1*hRke%TM5S@1^)3#q2^}L<;#ncYTjqx?5DoUk6GNw8oJ+0$P2gQ zWHM5xB*8}uq03GEmf&MTVz=iOri|yUXseo!HNS(xt@N6! zCQ)r(`_CSC?(MiX{>cxzg*-eOutxK*Q&RqgzKd-za3-z#W~my>DdK3nI-&2}^75PY z&h_pm7dTqLprImYXxVHw;l4wz$J|nyA|fxRVa&7OjW3%%R}S) zSXM!sL%3^vNR)-{rKwNAmPFYMB}u`nNH`}=x6B_m9;1f|3C6$N4llZhfo-qy0eBT# zV8tuOfUhP`fkv2nv9jbO?Vj(v=t7DyDM?0t+7gQYNCly|(7|azAKn~-%Tg&~5E5jd zsy67Ly2CPA?7L&v6%Z?V39pa@ULN_*ah;Kwo&s0*?6V_h^N*%M{7fvw*_KIT2S7+_ZU1ep`0>(?At7WNlVK zq2S?BXsl?X5_smXno18n*g9xqo0E*a_sfj}KU|K1N$z1x4DqbUn(M$r1xi*{H5TO; z+c$U)>@s;&%;DG-#ek!EbXJn(uU254h{{e5&E>oeuM(JIKD=^uk!B(r9tkONabdg0 z=}=q0&PmFu!HG4eVc8-0u$gaf$}sTH%@181YfY@2Xl_QC8CfNuRk=cNJX7=9=4izG zUuY+r7)CiLR1FWUCD}#}RfKdv(RB(sOmO-%*PkMhQ9`rer}|P^>@NqAD;bf-|IXj; z0b8PH_rk&gf9rBX_wSbtOt}FtUZ|{n=mtBHEh@V6G8e5M79wW|BftDQin+!(yL#iixH_}}$g1V8bzS8{@sS$=yLIa@WIiZW-X0bT9t`@>r@`N~E~}{MO|Dr_K`^Y^x!bz{)1r+HO{N@& zLa2>$hOR8JGGY+zh?%G*DHoW%@T^F4%j!u8eUeJ2EAWX;bBJmPtYyA7)oVJ}%w`A^ z2-!d)L(Ov^yBf~5hY_Z$5-KOMQ^})3ZRtaCxT%q9Q{35NBzfSBVYgu!jqwSbJhl^Y zh=s4628{wvHrQBj%@s|<4LstI!oxD$ArMw(W@0hUrzdOZcO@UqbO;{#?cO(wF)We; zH#1T#3yA_@L(OC~R*tZB1-pk=bB*|>V1R+mPWfEZ&SWO|ekA~70a68i{{CZTY4}X0 z;#4OFe_U{b#4vW62^K$gWjpeW-WoU3m1w)kRI-XwPxmt#gGREwN_wj9@k?y ziMGeR)sy`DST>9qM_R-(xtf7f#nfEP6aP*(BEpHqHp4p~C)P0eIFn2Dn<{26$7WUB zH}=BUi~hjlzjiyv4s5uXQ8))v!%FDLGP1t7VAf|9Rva>+Y~l)x{$>Hh6Yz(6!y_D& z1ViSW_X5uRJ)BLBPQJ{exIn`hs-aO%QeFZ7e*Rei56RgR7E>uj)@%hF;@S&O+0WK7 z$^+)96{OFV!T2N|c+?^GG7-$-$L@!ITXXDz*0^VU2nbxk`2LMW#$fDw=iso!jT@PR z&^QS@I`W060M@p)8b`~AMEEO|tAE$mNLPK_+JC?Sf&&&O) zV}+U+E9{oM2x&F_d}D#GA@l(#J-u^tgyPBeZ~ZF9jg!I&%tB{Ksp}qvE)+r*+dW8J zgA$_)5^QcZmB@!5;uccY;Hr{JvHAJ24*^N)gq&IT;%tSxyXmQBVrbK;X0fRp&0mfN=;1)m7CCG{6M!>@l_6^tdIZ5|lrY z*VRe@b)@cN{dL`R;{}(#HW)Tfs5TE=lWuEM#LgxAHdoI=*VCMDXQ42{zsNmS%T!Rl zrjj2eE`PvZliENU2m!_m?&myE1r`lqsQH zu)YPD81#`lMO#u9^1%o7MTzeo3KmF&55DUAEJ2xd+&pBBAOE zHllK}vm$VkOhThDGFv`4rV#M8*@xn)Qlkjuqbk_x6gdNdw)mYyxT0%nV;6a~zRhzHz-Ti%u`MhqZmtpVkJnE3JEn|``duC7rM9N2 z7<0+rF7iffs2@7UZ_SUL&#WH63AB`yIRD~vGlqa9S%E3DP}Nt}rN@4$Gr}45m47)w z3=Ex}AaVr#dfV>`P6{oWcH`a?f1&zldzM94g>F;ZDW)O z)ap*=O0x7(7)OaXD>Xc{L!pC-ewVFX%9sM(GGm{1^Nct47%Nak z5&AF>)^hq6o1P=v(70d}vudh`WPv2Iv&tjG(GW_uHP;i*>LL(f+5X)Ae2dQYlD9@h zp`jwjbF~-e+O$E^z$eP!p*$f?q0Sx6w1>Z!pnkbIvvwtosS=EmK~Ue|{n&hPdM`~R z63jLC=BVeT2Twejs3LMJSL4c$vBLx;Hs2@O5g(z)O;x6q_EMXP?A^1&D~{*3&s<<0 zUfsz20eGs*8on)4Bu*kYf90fWy=#^M0w`YzJX=keutlS2?)(7|fri4@CPO;l=m%=? zGB-speSN={wo4ibYN?Fd^zH1Mg{*`J-7@$^)rtW=C8DBvAy3FOVWTt7kcH)=ONo4O z3Po=J&cC)lTsO3v&fQ9@(pQD=lbzWMT>~MVpWzT}l40!+lT?#;Rxy3Gj#;{Mr|%pl zQBVI!={S8Rhd{ z-=1ZB-Lm0(=#H>*l8lGI9QdDpy%D|iClT$5lrFu3@|!Hf^xw_g=kDw*A|Y0 zE{})BV|YJu=!XzZcYENWU)NE-T{u^?Qc`B$H7>r{V@hWa8ct;ht$FQ+`4lY57Qdj= z?OKh-Ob=~2&M7cY7NY#H*5UQj+fjG0>l#r2fV8^2S|R0t$yI@t5_54Nf46_*V;m)h z(L@Cl;IicCJF+k9JEatHYlly>VOZ)Ha<3+G!VMM0!Y3#pP%~2`4m(|?k*Bsy>Uqjb zv2#QK>#u+L2#5IuU0fUj?}fi{e4LqtlS(hx=;%#y;c` zUjIn)y1%rvOKh82rj9y0I!=&)EW9YX4;e%ibXU%0OkJF*7H0qGCZsekl3SJnT?s_! zdrj&V(A-pJ3_LXlo6ukp=5X@;SK*F*nKpNpFAX3tUzGiLSb>kLQZDWU9J zkBsu|qPBdhXMT8m(x3LoS$q6#S2$04t=PfUoGujX9ZcDrU`ml1p~5gh)aWq16u?XB zB2(G^o;C4xtX#*&1E`{_v(J-WH#$Y39bd^Km@PQXC_I3Yx}HKITzt_%S?@1y4R~Rb!ZQTUkD0DZHOlEeZ~7M~JSHS}U3_%Y-43 zh4M3KXG`bydoC4eFCa(@p(CNszhmmT=2$9#ZY`z`;tq# z_IndG>gc0%`YSsT751E+g*j8UDi>$5x*RYd)HGg=tEUqJVtl*V_n> zRvYhzr#Ns$I_VR;g}XpVV)syy4JOu0;gQ||Z5b2Y!xhGDCUJ>zQ;=yt6!klrcVdy$ zh+sozW_&*W!WYw*`ap#n4L1#dRgffvndQ%QRvobm0~hB%%Jm_Z(h7nBxc2uP7Gv?i zD`G1b8$nbNz&c0MFc3zD*yQ!n>R>iFh=vA*xs?aUH7X10sUHMCe>u@Y{5j$lCtHlr zNVzz)%WUswah&)9c*n3vTxb@+O+pYZ!}`d8mRukoaTfU%(dE|ly9vLT>z{@JTz zymS_cu6;Z>$p zwc$^bix7H2Ki5^o^NXUP(v>GSD)13x7Z#yp+ET}A6qIr}|PAeSkcP!J>1#y)ys zVT)fLW5rvig%3~4ijNF=Mo`RoNm%P{0|Yh5a@0298r&NNwwSMf`v@t{=eW+70?Xb} z5Sib{i|uRYMGA)`oB6a2(5_Ij3v)bG@hU8$HfUTqJc4g_5l8nT#W3C-EG$O*{kjvA zx5WMsbw|2 z)ME*fS#Hd^Pxp8H1u~CNK8)b1Ke#nO0NKh2!OLSaqVd}6+6H;;%b7@fkj?B;=+^ay zk3~hGN_dj_Io`^&;i`qrkVbZ0BOdB`cqKiI&i4iMRRbp*WswPxZUQ&;yQ&T;uS2BY z)#_O%bJuCkfaQd(m}75XAjWk>cwvy3#=YjgWg7av#T8WR8z?GHKt$>s=M}|34}dRz z%|nofalyZke5m(&OF%lN;Px3)kkO29EEIe30nY3$qeA}$T1~$xm%2p zl=xjJHMPkADFA>4@P#2*pEl$9Mk~EjKY%%%QCXE;CH!v4DtF7IE{vLN78ryd*bNKj z-+lT6?jP?n6{N;EaY2?3$VV(>-c3;ikP5CSc7fA$Nin_zVg&q?l5^74a;z_}3w z(ZIEUn+mBmj*=_bNUdY4)s3~T zi{#L^mV%AQUUa^)@;iebO2SD-c)E=xa?6~T)pK$s8|CQnF$=Uh9#R)HC4lK8!jz(z zuN7yU#yltf(>z|OyO^jXtnHLA97{8Vv0|Alv46igYE=1UTDWs^Vo;O`*r*>uhX8+~ z|Cvq~(pDsmI)BY|cvy*t_tOId{px#jjc5c(DzPAWoT7Ky?WII(=n6f4%a^EjOHnPx zNXVqpSCZ6{)!CsCov8=>G*H21M5*1-`h}HX-{XXo|0t_X{12!=G$aHXG&ADAO!46K zwSf{^1o7gyL6F2y3)s9~UPGJT8T+(7(0T*UTd}fjU4;&)4UghNn=f6uoNTV3>#795 zb=DySVZW`?0GRuF6P?jL?DnWYh#S%aFF;ClL~dHhljcM`Kq^U7ahgw@bM$aPuP?4_ zG6zpO-^3p;ubTaRcMpDmR1LyjSZjzVJ1PMB^`2$@ikL!??XyD^a1qz?I9ctE8=tW2 zSZxeYo{0@5xn_K!>9yBMtFu;V5>dekQ>u@7M!U$8v2-x~<>kMuFCN$GX^fX#v#CYQ z*;VosslQJ`UXBWYQD?^cY*}4B+zQY+&V)RWVnHc0sPRwtv`!|y(KjB6d*~4#$)7*U z$~W@2Tf7MVsAKDdzJuqxJZHMNI1S`pb|13S(c#FHYG>=vh_kKTQyTqO!R=b-p_V;23wqHd69lcd>=n|9yiiQSu-8-61)EHO&%Md#UZY z=+NOf&#mAp@`+h?GP;if3S82v!q&&%_Y6Zew}AWmf=e$dc|_0ubfDv+N=Dfx#Yzz* zG$4b_Tc_VlZem@&qnRxQuX^ZK!EV#B8~2sV#KkD{0a`EN@rL^EFAHmJ zcQ{&^Ak*~poCv}2E;Ii>v~@E>*+0n)AY20vzud9g3ulXk>f#n<3C$*U$!-Mx`HMzD zEFSM;j)tGbeJ>*aGaK-vEK38y#i%U8ztd-*mG6qU%RT#RSxt^9L|5Lg)%DTVpRv#N z#BDMc1R~U51(?+=>~U|3`aE^OP4Acg9Q!cQn>gfrmlc;WJACt1h{Fxz{kxEbV4$|? zZr{0-kqMl@UJo?xfh?)}6XxaRA!8{y@Yf98FW7ojnT%M{GlhbV0^iOsb4*K$IIbxx z+r=u`(?RiGD%z-4PS3rptyB?+y72XpL~AP)ZDct^+hqFYNM--?{XRpfRonS3k^(sq zAi)mQbBv5JskCq98iC?cqH3==9Tqo-VFE%Y+}FD=fKs8I;(M`$N=iyUjRZ6%-%V)j zoCQluiGnko+}?b3WZpvC{27ZuJ;<$^TZhfGZqRj92t;k*;^3{^&fN9&Kt61`MHR#e zD2tVDlA9*0mc^!I2`X4A!CrZaa8_K&Ydaw&}nR z7^60+ls+0B;K`0ZE3eqzQF4~d2G>C$|PBUAkKMZ!*ZSnCN1mm-iLUa=CoeqrQyyUjSmVwmyZ-WWJp| zO}S03^-U0r_dR$LIP3-n^74OW-P8TM>r_3~yyhlOR}8}xs_Ol4oIlw6@JjI$C@#yS za}VL?m%ruU1dIov>Um3iFxOvWNh3gG-AAkzhs=^XsgLKPJUu)}2F_$^T4l`qS%bO@St~tp&yNp9_znk5Hh< zP-!A;=y&8<_KO%+pnm(%`gPE1UE9g8q{LN5l9Ki9lAY{wf(D?i>@%ljKq{pHxU5e; z*~jB+len6^==eM@DcN6D0w+BF&?p-=IdFylX*eY8XA3>~HIXTxvfp35*eZY0GM!9G zSp!^f8b^>&3hRD9dEih?L6y9bR(uCoz(PcS1b>9rJS7H-VH+( z%|Y~j2(~x^z~hU6rB4Ttg!PX*M(rl)^0D@Y{*EbIJ2@=@A!o0p*iU%&D#A>K=&wFyT5_rI565BT@%{H*M zJg2r35$rg?1qr?wP`+Jh-gUZO2Y$0UaR1;x5E3LpZo}n*$0{8WX{*w=6%;&hbr zp4P$^fRVXrlce@G0?Q(T2fN<82T@=XNt8S6+%ouCSL{DMRs^%@U!N*JbqbNmj|sZ> zb#gh$Og~-C;=|0&HcD9;Xf#WX8^wVRi8}{U~!mKHids1a@{g~AOUK0=v7Qx?>y4YCIGC#AS^bB%V%^(fuXaPeitMy8QRZ= z*NZV5FRLe)vh7%Bu@-YcF_{B~iM~`0t%_F&p8Yv$$v{EH6boRcngbk6#aVr1T8`9l z%0R#lsj?Zqjxjqo3DQpa0dZnEiYpXDB5R<7fuQRc{J-?~JHcp~qXy^1%mjmP07q1l zrXka-{ed-CRdOyq%{)bzE94==w(@7{cVSz(wO3azOTL2SrQpMCvB)DYPA1tp=2f^Z zlZ1dmIi^YgkR0Hhjg;}`^Qp(mON(JdFJMf0`U{e@0c(1@DvU0x^iK3uZ z0Gy~-GW5(PowYVc^N(0{CK}^MO1 zbE{h<6P+vAMG`#)=G(_Bu78n1yygWZW?GIRJ zCc~?kqsZCz4sKHbGo?~dZSY;lMpA=S1lFuho`f(gwITA7Gr%imj?Of>WF<9XLSu>_ zI3^Iun7S^iF)W4WgR*43=?jTh_YzodFtCIkK zWa*{1K7Gg)z1!q1vG)BPCPz6t41X#G*~-}+=|Kxy5M`QCY&+{sMccwp6(qS>mgq|` zltZ{Jdu*WYdZMUrsV#&{IZ3a3g`(mf1p1@8UEHnj27io;3E=iMQj%z6xI4A^P%*l? zbPRn>J3O-@U#$N%zB--lP*3)znPffeP|OAR#-+$Q1PI%d^&weJ=>e(^hwMy6_v)GB)N|9 z6^USL^{LI6?a3Gwu8a{)SG~`&^XhnzEz_xS>Hbfc4~HxhYv1wlKRv!q(AEc$Q^u4K zLy>D5#boBep6ZLAGwlLI-B(LzL7*%VN8H2Y#DHH*{vBtf!Ix>Giyluv|KZuFKx&f} z(kWF2C+dFlHJPy)lw z>ugXx^Q3S)z8BI7>M=!w@cZw-ACk&T98imc|0e3qdPThMu5hTw4|()n$%Y1EiC_&PK=YWogkr?Gp%aLw;~!k@^Y%x;3#M zD6M%_`5ZQ`g^>RN27B~J^Glp>Zd?Qdd<*KHe`ks3bHS%L5pPsiu*hxYI^sM+T zJCQ=G`&Ss~t$wZf0zmPXo2+Ge{J7EysOp^qJ^)5 zk5JOEZ@!Il<)@XR_#a8v9Y|&WzK@lWkgRmIZ!X7Pp($46)v4hs#W!b1%=g z#m@)>T2FdgeJ~<3ao+J!3~n;I4Pq|wmQK8b}0 z!4~KGCK$a%fEyb`NtvdY@Pc;?{2G`z%CbrfxmFANJKM7w@uh4UxuO_;9Jft6tj@G_ zBI(6!Oo!&1mN%%539S~(6Ay){Zqi3{M)2Riuf;_ppWNa_=jN_?>v>4y~-LKmsb*zS-D>TTYnpa>!H8>w=|Xwkq0nB`$}fi@Z>i z6KvnW>p3@9YNQH0-ptCoozlnF3=Iqnz;cX4a%>Nx^T5rF5rWp>ivmnV!Tqf15V~&| z1nK2%H&I<4U)`3!%lGYml{|iovXWAsI>V4PGo2z>8oD-2*g|@r7Q`TE=Bcx2r;D;K zrsaE`9Biu>QAK{%P0mP9hj+5j6Qm*mcSf*G?1I$7)zs9JVMYuZyULuIY|gqOvnj5OZ(l`C0%du^!k_OUFs8x)_p_4q|YnN3QJ-4 zl{TEY`yRm?Q-HHfJpHrA9EZV}JH^xxVoBfpTMbm~wkJtsh1X`>bKMK|K~LxoEmHd5lw}T=#RyJe*Ln5Z40v) zv#6+~6raln-qAf47v7l|@NP9}?bV#Kv%pEuM%)A$duc>**xqd1_{>zO(s>-RHS`_bxX#W$vW6^pEx7 zVRhjQ2o>O{GvL;N?2ULLjMJ*Cx2Y-4BNU~YmYbPbU=(DsgXuqf!T=NA6!@EHX(9WM zi{_qEBE=F~nVEw5ui)E$Yk_Bz|KKzHCkd0c3k$3gFcBL>{tyil%)-K7E4cJzFEVX2 z)6>DEX*{uQfxhf_MN74;&EG6W{tl}*552k+RI#`|SzD@B`r;zv2A)26Mv2s@ zWWe_jCXW}&e(xTJgoJ>hU$GKW#QmctXMAe$?_HO~#AlV)BR_K)iaV|Awx}|fzV5LA zw?$1YxbzU@X2Sd=Ml$=N0d^Iho}BEhScjk?OFd;2CA9)KrasGk&+c^2(DSs>aVgV^ zaFKv}=tk_@e0g?0Dw1u{EO@Eq`cxQqJ`P9s@{@+4B}-B)`ho$@@waEI%O@1dhxv8S;5eq}Rfwk-hpH zNoibb``)LdmAv9v$_2z2h=38!C=q&_>8R^zFAlnTNrEz`nSrPsZmSAB@rObB^LL~`OAtSBI79M>(+Y+GimH;#R7+xBmehczud3O|{TIq)y*BV6-%QpteeIKe*U z&D7QOMhE3q@n`!DsaldEBR6x!Vj0CMN5l%0M~htkqzh*icjU5qGr)fCm1*^W459nL zEBR@AANQ*}@7DTmHx4posXjsk^hghTOmlQ1+Q7~(=ls^@bfe*zO!)i)U?Itt5Ze%$1G8p&$(DLFN<8-4Jr?T8mKL0p;b1FFC`Z38 zs>;|W9C8m1M@za(!~ILno)ohAV2OJ5W#qk(qp0D5CFfVRpHmf3lm|M6I_+a%Z*(mj z-<`X62fTzm{t~-DnvQ_dF9z8dBp>O>pr!zQ5=vg}wPZF$^y9FwFuTbwt#HT>SyLs! z<%E+B@dEwuVN7-hT2mfut?5EntuFo+W#z9GeNKSLoRS|-*b34_5-4W44$HrEfRh(v z#`hk!R`Bx29=Bi@2h;NB$8hrGQcxQ$&JZ6oD1Q#TuLX;`piN3f-4#$GNAZhN% zszr|4(5^)&4kC3~_SP-2u_g4tzVAA6^=n1b^D%Dugt(&Y>}*@XNCs97xi^b-7Mx2B zB^zkuV5O*ddjEnVUncF6mn+7(MLfM{{b%)Mo}xMvr~J6vikYEn9^imXMFPcc!{+#S z3iep`Ao{T^=F65;f%-sMRj(zem+a=L2VY+(Gv9Js_&?J431^iI5aN;=nnIUL@Mj#Dcu)?74~f4t_1V38>EYyr#Fo>|5~ z3(_3^zkhYp#SedJbeQ6dns|HDGmOd!A;}h1^^(EfnJA@0?21V2XEP+mva;U%nlJxk z=J9U4&})8-)3p6^!`j2HikU{pPRpGMf5w~!VGR3G_XZ9YXj=~=bea|*W_<8&@7WyT z`JqPE(YTV3@1CsqQ4zNBA`L@J$8ax)ws|Guj?u#h^=pur&2+T1RYQV+(8vN6VVgk$ zT7VLtB6t~uTo>QG(+jeu0uO-!m%V#~M4jRP#_bpbunkjI4+%Y{iJw3J_UxtQORmld z75DS?Pd{eF$OV0C2yOi`j(;mrB)2;Ojjb#;dL^XJVVZ;sZ|=pt%DqT#dSRA@*=v;2 z)B}!zr>M2T=SYv=@>cF!;|jIRCeZYE`l#O9y1EJ zj5ggLMTqVUSd$dJ(*LvFzQ%?|qEqKje-qzvFGjqDtJv4iurA3WD|NE_jy#Re$z7z6|914l?8cxh~g;gIa znJo5>C{=%zQOEkRuQqgq5m&`!lBUeaTvI?EP+Zpa7v_!{{@X@6QWLB}fq?e*)73 z=2>+0LX5~g$uf7oOKJJ<$6Y2L@Wc&xUK>kAL=*;YZCz&H*4j6xo9_+!xT-nzKfNZU zc8Ny2*78o5Ck>@<=UvYmYgfg0AT-M+4N|%D9|WApU}^wn*>IQdvQK+@fxVbN$?0YM z%)W>w!%Zht{JHNNg{DK?C0pEdY~1pFC9N3sb=w_b`Vz9dk1rgQWFxUDBYCm|Jg0bFBlJE%;NzJ`EJCd9zaD2n-_$Gi&eu zBgvN190}8+luP(>CxRco5R$@=>hzW|vJ#Z-0@>4{J$kRCGwC-Pkoy$HG`L$aIczv| z=qlVO!WU25-B>7184Fi7*lapGJBy3W)>r0k^j4gze#L>P$^Xn;aULb0NJ+El+q$~& zX}GWZKtLlK8=K}aY3Lu)nLl_4ZIkOMHB9pLzmF**+sXarGW@*xGyZ)P|5ni?IyhM~ zjTMEw2`ZT=8?u@kn$RKZf1*j#ZL(yPAr{zP#(Cmv8{35&4M+4SXRzeEVqU?g8D3AV z-`3!D)Pq^KCJjfbqP_fDxzt_UIqBTZHRHwnkim4zp7ZO$2UhPV zLtbo#&Ldr)%^fml;9j}WFuk}P$E=<8K0F7GL}eL|^#| z(zi$J(iKOtu9uh5q#E>bLD{jqXfg!KgG=I8^E|e8;Ym6U`N|`|HXFL84_>`?MhWl3 z#$u1LVLkGyS}U%?sfz3S9|v~mY$c%e9TB*TBqMJd9o(3SexBevoah+y05R+G(wFYj z4V(b|E4G9vV$^jXS|29o2i;F)27_c9vsW4z$r5p-?)H)_jGQrBowWa)>-DprxaDTZ zq^R`E{QACVtYh8?r)ZA_9;TdY4W~@E9tPnqJLWKWz`l_}w9uZ0Gsj0N%{u3WjwDu_ zN{)7clg-jHBx2jUxuv3F!E0tt{!Q;vbaZsPZ5J1UkQrAFK@xy(^5^K}#8hd7@hsNL zYM!{D{S5Eh^Orw@f-Y0Ux($fWipM<1|DBc1@7LSgs~Ou50o!%=bPK}lGM7KWS4l~N zwaAVUXOf(pN8|L;k|n2uY_-0jp`l&1e$cJxi9}7Uo7;lu7!9tZXCgsQ#Y_)sP45Vh zE@s;WG8_Ftn3awhzi*%FS6i<8F`+-i?6hw@~t6%r)+gG$aP#Wn9?m|6Dhi@N8 ztu??5tjO;7(=sEP4&P8yx-6V1w2gZe{$kON^zjw7T|FoO3vZ zGR1M&pFHY`9R4I;XPY${M^XUwC)vh-o|y%f>G;Oc(KJ)CA?8=Kg{kX!AZ=Xh2IrTe z8Z`efDPE>fc^RV714|Kdt>nzkI)*&W?uo7^Z5uA{5lb3^S)L;Dmz3zw4q9TV%p`K38GT49!`I>RrE z#oIJH!&N_|;OA_BrC9GU&QwO~-)o`%H-0XhinpG(HR*mh$`EuFG0I!G%T;&dMxClF z!tVTxjrQT}zFV^hU%6pQRZkS?{&LOB5KWRTdKRb>6yFFMf zG1|7<=GI>GPB#c-1Kpc@-ur;ljvmL@O&;%K3j%2vMLj8cf5cb8!3DWYuOhqw81md= zCSKM5RO5f{S1fe)w9;$VcR&7p@pm_0c-f$KYI`vQM`W+lozDwopQ`_NeR3v<{Flj6 zkW1uuUt(SANgY3)V*J-}$`A>QulA?#kc3FW3^D^xQDe7{w5 zIiCcomdYM&3?b8ku=Dvoo6#dnRv5> z@bQ=V&;A`>V&BF2d|dvqhST||_HtlZ^wxXGee8JMtSgm_??M$W4au8@bUIX)_V$}s z^SE4*g*q%51hGK$%sr5M|89S!(%*{zI#-oKM;$W};DDMOmC%84zPdiR`-=A+?!J<; zvU+>3r5=ob-jx4Fi~w9=5lxAXXC`uSaS3`^;fx66xU@YpcTC!;sHE675Im5{dxDun zA?6-Xpg6cYk1o|Qe*Dz-@|flH>7_P2YbOIiWT-u@E5pBaQv;oehkt_K-9QkLh?BPr z@iUTyFwVQ1?1+j8a~gOPqed>gp5DosOtjc_=1$9BHBufe;}DV04g5=Jr6l=B_w_HQ zZgeNFCQ0noCqdauc#qDh2X+yUa7^5v=8C|6lU+S*n`0DoN+>bMk{axM{h8M8W@G)m z!Pf}>*<;4n3KZdwJ`mi>(448|Y4m+xu6td}5eg>_t^0{Rq-P{6UPgK9*^(*G(|)YF z4zK?0-26=W2>alTb<6pz*AZ8whv5=haQksClq`h?rPcaMYE%htJ@V9HW-<3kJ=w(%y8WdF_y|5b=c!2=TwWl$r{7bG4pb|_V<+#t6>*VW)mUeSTBsTDKhgE zH>H`~R&&1ZR>1O0Y!H`JGds`ST?84TU~LX9J9E%D{jDZ*4#F%eBW>jMd zSmy2?ic)tpsTYabZeTN>{D!{{6+#BWu#m^O68u;Q?b@+yX%1tULr>N~gwCW$V)bh9 z{A{S;_ZpU~gttk1@T>ISts)S;AKXo+M+Cs_I231A7Tf=?g%&cbQ>1SE_d22Jz&jJO zD?0j#_RiHrq=>;7n3-utL6?ap8@@OTjT^1pm<_Np zf-pHLF6XYTCqMpq+g(KOwEF^?&}dX6j$zh?Pg+`4x z<z+Cxs&v1V1Eu!cjMZr|=(7jNZewGH~?9BNs+N@zWZnVc!yh18mq z55`(r#Ssvy4d=TW-)6(+moJx+#ysroqRSo)9UTd4Ri^;-_sSX^F@P(KfJE+suW!Gv zx)SZO8+=P%V|L#ql^Nv2SGXJJZlL z2&x|4xM^6V2@k;N6w7!7D)+x~-S>ehIEb8RxsMJ&VpQv+v=l(Aw+DY`(!)Yk4hrqU zPANg^?sp@rsth8eU`+B%)2d(p(+{ZBON7$$wLXl#EUC_wIB9drT!fFsV3t0<^&&H@ zowD_?-ysu}c37}?cgZ?NBHf<)o$Y3cuf9*b`1U!pq+mQ7F1-Np=v@z%(_Q`)6>Lsa z9$WIfNVQ1=5GdjO^OE}C;O&1p;L!rOr)MTp*#=X@*1ZTaGD1?kG-71if82POfS~=3 zpTZnhIcTyc0{*aLwkKaKzCnd=MH{P872}w2Fi?X%8Q?Yv9ygOMJLh?+b09qs0=z43 z=F}w4ajLGv21FUI+Ro|pDL(Eo`m!mXqNKo`PTrs=&q?u`jp91POzGqk+&L2B#=$R#6^I5o- zg}zrQ1V~GwJmTPAt;6|ttiR8});xq`*x23W>D>ts%Kz2<`hfsW{v|HFW=7F@*Kgii zQ_B+kS4u7;3FC=AmP&E-*$MG;yy?BlUIsB^0NtS;bHvN_4GgI3F{@EQz~_GzS22s* zU`txucLgBlzCKm8EO;=#E_GC_Lkbs^Wl03|jEo>;U}pik2!gi?RijfCpFVpQ@#~Wq zk__&Yhso_bKAi#0FJglO>bWsaF8A)0XPCk*UCbyNvIdYH3xR|uJt}@VHa{3E2xB=eTA};d$EQSB*vR^n4c^oSd3^Z)PxZQLZJtX3tz+ z+V$$V|Ad38aN%q6n>R&rc#l4e{@GMq_$xc?-ONb+C7Qx0Aez%5!BX$eojWPzPKeMf z)9sh%-48StHtb)>YsJ!P>;w?fnspBHl6oHx{h4*{y*uxC5OJpjf#|!ofSzeRoooew zW8wk=z?u74IbdXHD7sOlaXxn5kgr~Z7m&k&^RsZ;{>Pdu!g$EKYHW>$q&hF}^W)rM ze;FqvhL;q7)id(~F`)A-0|5e4Cw%AtgG#$b|6GV&_B5GUaeXF!#wS3}96=G@cEoO8 zT{neJcO#2Z>=>t0RDR@5nv-rPhgZ=*iT#o(DBb^d~_#4d`k`X;u;wDvjEDqg2s^BV{QzXa=XUKjpE{;#sy7Tw@<9Z zHOR*gY4W*x{}fCPQt{&=fa{#L-=92_9jeD3k3UdgUlFDA;>|w&4}ti(;a;sT6Fl8H zeB}pK0$B_ycFHLosN$+BDj%228aT$MocrGtCu$g^dBuXeww^;qm6n!ft6%<+Rx^|9 zbzbVNy)sIvh9@-aWYzkg1nHySK~5Z==~2shfNSl_csb<@-G-N=4z-i7er;G4`{yf; ztQ+r<(F+YiWC6k;-~UDbJz9?xcc%_7AZ(hsB= zb!lvMMe2q|g~@xV+!=XCTX}IFY3-R`5!;6Zz{szE%#ZGM^6Dm2%WMPF?RS1W>kr{; z5Zh7RUOor=`8q(Ur)kg6#xyc_ED-43zIzvN+f#liq1lt@rKKbY7=~bg79iez4||PU ztOhe-htNT6!fr|6oehzFu;uTmoc~pkk}3j%=!AL2$H~db+&su~!dl?x+8)HEjj!zi zmajBoU}Q8iH5K(ZHvvUW5(4O1C$D6~;G~L86$wBFT0_+h`hr78|Pur7U zxpFw}IUEng;g$b!GVqN1Xv z=8Kbo0R5=I#h;<-EjCY`iwIv zLYj5ltL^*F4v>Y9!-3KU(7|v_qv*BWBq2FtSL*_rOk~;XA$@dlaYRPJh)uF3B>jy2 z_`&N_HL{8n^(-4%1%Mws;_K>Q{EN@8%PDW?)yi)V0!+}uXprlnc4i#ch~vZmZTJhYKAYEDB0Rfi$d3r{vQo0(hw5KCtvH^AL3aZC>&Ue)dUQ zM_U{Af}_e%f(7kVN^<}$VXaPRR!vk_#)UI>go#DyoEE!P|1qyK?6>HPWfs5`5Y0V zgFgpa(-uRj){^A?fLd1?-%h_)JK2@H=_q z<=$(5=pP(}(yt-<8Aar0X*Dh?5_m#S6@l8GB31MH#w|b}4cA)x1JvAs1BPSNFxu1) z$_HD)#chalhqn!p#F6#;*acatX|W1<6?JvibdQBIYPi-Q$-7WTbdZOKr?j+`aPk1O z4h7Q#q*qp166^P9{(Zaj3Rk-G=RpZ}>A#6Gw18-nj9E98I~3r6gFt`xXdh^ik~Q{3 zd8Lt^!xR70F_na#@7J7G=HqL%_+8cG?ri$R| z_0FV?e!M-;q@xoDV5~LK>6HW&Y#E-SF|xM|hKXp~K08bbnkfQeA6FNBwX1SqT!>50 zpqjXZFyHMNr=k4soE=A&&P>7EjaTdGvK9l>(mpKH@kv;=(FI*iG^VvS66|QA_yLBGkv9ZUY4cG5v(|W2ObYV7Yn1Gq=4BzTn-VackOE@ zyBkZ^0AR@wD4kn7MSa-Q(%U=x9(J1+R8z4;Jb82bveGRpon4dS5 zQB$>pp$Er}N!D`s!R$U>zH>eD8PVWw-4s_Z47&p7tN*pamOb&ROuVI@Xs1nDD1D$l zu=gct_O;LHF1g3xNj0=!ZzTfs77yDmXD|OSa1UwuAg`pm6ro0e|C$ct1h)r?$=<&@ z%DA8B7cQSr1+pTyaQF1W9B`CnXo{M8eBg}zQAJzpzAn1B7^F5BC*5S3{&qM9>|I@{lD_UX zf%A@*mJ{L18@iI)7$?+W-&j5Xlr&uQL?A`kAP= zpQkdryd{NE;G&NfjtRQKHtdEZ6zQT$*jbEWhgIQUyU7AmJR)w@p;J7 z($ZSV3x}=MuU~&cNiz+3vKNt6^q&Z3Vz);iOT6_M^uh&CD?f3}7l=CyoC3qh!M*M91V#4iWYZ4s#m1@1_MKm3amY$7c9v-!x@1H;1sPg6U9{P`hh%EwwU00MW>D*;w zg$ODW8;-+N9ooAotm&&7VzA(fKD*=$37O^PdKO6xovG1ppWr7U7OvC@n~91eqxUk5FgK?r`@s@a>HmlLIAtczk0VBd+#ZoiJre~ zEP7Q@^ZHfOp`dYp>_AZc_A(sojgePgzCd}|!Vnz@x#K>Ww%twaQPq0~?6E=~(T80S zWU1YBfQnM{y;r6z?d8!)8p&NbIX3q&P6reA#8p>e)Y)Cl2Z6(?qycM3xNDaDojZaD z4`SRruifxVwtUfSkLFTQW=A{@nuXkQP0srwA_TnD$d<25J0KRr#c~JsK-9T_ud}nQ zvok5;&)yN<&8i8#$vA?c67RMbvXZ>M#`Kz@*Scy{?{jG?3|r{UR6nQ9o`Ixljd^n- zm-}Y8BAkCTRrJRxWd0i^`i~#fq#dko!6X2qFjw9@YVo)DZyN`V=*?OfZmM7tOgsBB zxZ7m+0FY}!?4&u})3~V1 zT)n0wXv7@)vCzrmNqI~64!M!m?Mkj*-aa-(T>3t-PTo^-heOjgyK195huO?a!^}^z zRhbn{YN4{@ppq-zz5lj9FYjith{uP~ugkkAX1pk3?l-+CpyP}MnggfD8UYq`d*qUgTR~QWI4g>oxD)!v=#62AxM8p#NcIx7@rAbZ8c0^ zaLU2k!IPuNUP^S@Jz4*={B|J;ic5)uJ2<1E6qa8qY~wheZ&{IzxfN+&8QjfK4vT2W zx6yn>(}%U8y98K78GXgF7brl5Zy1c-ERu?rCnk(2S~u2H_&(fc?;%?@c`LB2He2ae zD65H@!2!;0z8YUl(MP#CWzj+G&+5ppn9f}v!M8WPOxx2vfDQRmCxmWU`!> z?xmeV+cn)Y=G?3Dog_=B_eGqYW{H+w?EM_#gH?i|(!7ZiZIKlr9ca|aVm-TG`6PcaoneP6B*p)JVjWr;EL1i<)L zJ&Ym%xC%1sjyg*{fHyRW8UcbgE0?px1N0aaz`}n)E*%|xrPwltx$|#7!$NBmNiQNWw3(XEaGi7~q_3aZ-Y78#rl9beLT0-(%DPl@CpIdIBJh}a7}z^1;$;jlwwX1QZAXHS(gdX8F`4KZWE8twB&B zWrUicSM9hTy>IS+Pw?OOuoB}WIZ{n2k4med`bK2lvb^SXV4mP#M%c!>Eipu7i_ZU< z4gKND)h#!N-~}hEl;3SWQt$wvI;fujg(^UgdapEs!WZ())ZuKhG}l|o3(&K9;JLnrhDTw ztg5-Ce4Au=({$P&VK-Se(8`}K-dA(31r*M1UA1e;rbZjqh~3bg3_p=zL@Y^!3955Q@drIumwS%k6EMRY=B zX$}XG0Gp{OlI(KeBUZo|I(9|1ezV3bqU3Wba7xzHn+05uw}=9ni8P7V;d`9D&(rcz z@|sc4)4n(%d-M`5^-AEV$xKm8e->$kXKWP8zf^AqAuG4~cN07H)H-5k&O z!}sft2b3e}cm2#ymaw{dvmQvR$g#_7ZtBsZuCx9f&4h>`J7mEdInq9yXlx8{+U(aU z$t`k+#>dCs1&*=Qy(~7mtTl{6lyQAf%LrT01_*oBd5z_@`wM4b!*wZn)rPY3U&FqRwP~eis=5XY22*>1d zkX&-CQGl;*aNk#g0!Ds@-<)kgjl8i}wcoUCn5}vM zm0|MYI0RhRw*0Fu4bO1|qU{0{)S|NV^4hDJvbXKQ2Ze80rX1H&JH~^BYW$%ZB-_ji+UoanNJpK2? ze>>6`(RM}1XGmbr`21*W(ECT_MZr$oKNAgH8!$#QkT?&+e|L-zNsw1|zP#{95R4Zk zUVn7+DJ~lVHtV_)<_J6&G#58_NlA&il5w88o~|x4%k+jjjDi#{K$<;3Z~-N2LAO4w z9RZN16{0=3tF5C`RIvh1&R3t>{e(xfIF=C6|I`Hrqk>(Vjf@Qq^You@xW9n^e)fq0 z?P&5&HFNF~#^v4hFJi4lNB*xIPFk>_-GX%6y-}UmnBNHI3}y3;WjsPMq_FEj2n1ax z=@@y`(3f7iu~vy4zPdAiM^RoPd(X*xva;?78PxjZ9}i*4G-&HXQs35z=Z^ zb~mQ8J3H&;Pz||>x4aDE+=&g07xG>jw%wFx?Ro%|LmE4AhryWJWKEF)L%xl=8rHqB zxL7^=$QLH?^)@k+&iMb!oe-S};-(b4i>i(B#6JC~r7FzX*x>=U{}HF+UO~Q6|n@CNSQN zrR(CsXS%6Kj3Vq^H-KLvF_`cFa4Xk(>Tf z>*v2|t`M*M`%sHLZs$844}W3ZTg;)rpX$S_^r9_~ig(FtCMZKECJ$&R1Wa6%>)QTD zeo9{vQgpWQ*HpxxcXVT!k3O39BTDW^{mtjgog};MRMo~KTcv?#+vgxz1rw}7mR4E~ ziRDwuV}^VLat0?EG$Nh9?`>N>T+|#W_cP9~u?{pvb2aYGAM?ex5t=5#w0g^C!#?Z) z^`&0@&sBvBvUsj~(Yt>iKYsk{-8?@(Z@@ifE4X{ul*0j3NsS#3H+Kb>h=2f0B|#ku3+uC) zhK5GT8skHs@rx0#>oRJBf`Y;sF*zJCp_sgJ)}}sa<2u4cxUK8!yLe`Ny7O8?g(>25 z!}KPwHq1n?0z`Zc$}IIjBL1cqMsM{$vC9ubqbNEEFhbn27wAs_T)vpWqkjKk?dZ#w z&_(pMYuDgnIytG0?LmJjDEPy^7KjQ9z0A~9oB*)>RW1lp1X2&EqL@XP=%NeNGctn1 z4Y~>-A5tSQi;eCZ&PXdKr=i^@z!U-0=c1Y1a7QEI1^AR)yu4CPQ(%vEMvn262Na3D z_y@!X4;L3a)}W;YHvu3ZV7@qF0sc%?vl_d>{{EhdC`B38tLs2>G?s`63#ZAU1cij4 zY-Ej3Nl%|*Ew8SwRxY?FARxfcA9#WLzu32zpa4AK@%3ofT$gb)KqXpZ7aJXYlQJB9 z25M?(Vllwb2OcuqxB-`V+>1XI1P4SYK@pKV7ZbWb|G{*ht?kE=)sLS(v9uz}04~@2 z0pp#0eTqezKp7=ldU<;bA6UaMDF}e=?V(7t77!K^Dk%Q5v$KQ8pJ(&)>sOLaUQn$i zpkR^^%*G-=Ly_+22ZElAj3uUVd;W!V|vIXKMaFHUYQt7nlvjPT(x^4 zlrDxIKxqi{!`PIoFvz!{r{{bP;7`WfZPP+;5(Iq8Z$U|LdQqgP$K`NUA~}+^QSVm-v?&xbwTaGOS!&Dve5W zzpggKB-U9CGR7=|x&w)k$cM1ea$RXZwUlNf2lzaRJ#%Kn+V~{bJOb`2!UfC@g<&TYxrXUO{Sp%&X5bTvuDLdrRKkk&QS|3)6c5mums{upkI+4I7H|V>J7C2H&J%;_whr17&C@% zl|{p%9X)?NsxQ z9F*Z(O^~pAJ3_Q&ySpN)AXt^7s{-(llC>StSpNmDOv}_$oo1IoF%JMj|3^~vQMBt+WFk-SNY zvuN7eGzwjuV|WJJw{nIGZPHRm6sTlKHQHSXD!2%}y5sEw=%5X4myO5)dV*{NUlsT+SWh z=&e}!)UH19>OhN%pwh=PGmJ73C9kD*u_|_ajgu`gPFqu{r4k60nYO zzJ)FyzEfSLfkulGA4df02sJ9?1Ab~=N|F%O>oszuB7c3w-$kA*)L%PJ=mS{+cWkAx`+T}AVD=|O#(wk6mzS=Xg ztA;m(@v_0y1eAP2Fq3=&s@M6QU$&|ouox*E5i4`;hcd*AM8_hs`GMx%Kt_Bv!S>&G zE3gI7>UnyZ=TnyKW{A~!@Hd)(e#eAz?P97@FWGl?8O`m8C3s89 zUHeEXF#4($Nz79#iH7zVTGyDjEMIIphq`>)H^Y`mn?C38Zo31R;&?9G+@Yg<@+5N^ z50*F%N2#$?O(85C;L+O|DapcyC`^{Ssg52vlHleC#*3$Pm4yXx4>&klzCqm{=d{#& zyk}0Aj6-23K3_jq-{$Fx8M0fLOYoUe55>O$avN-*80P`SA zm!|iSFsAT4?f0A3lWG{^S_Vax|JEvKBo2A}wvEBpXtMw5vJ8*+JmJHwmdiX%TYIJc z&1(%~#{Q>!W3yZ3vA!U2IZj0p7i75qd;w!r%MbVKFQ%dr`k#t#hpQkEGqtJ$z+GCY zh}aF@%iQi`n8$r|uoOEV<{`mJoIz$0I!CQTrWgvKQwugWHa4+u4#bO@D}*Zakx6PY zf9I*M3}W%vH@u70ez^ zMk7`3H~Z@oQUpa8^J*Y|n5$g&&a5pf1FQarW$Eo&Ore`64^GDXC0KDjWr`#|Yn{7! zd$W$e8u*dBdFo5WA7-{vT=sN6B%Sa2mrU1UtC&va2&e6>Jkk5mqAle_avKaC$c0oEW;*FvE1eil%p%{<6+v_vEW^_j$7tsVQ-oXh-RAAc1(be@r#D)jP zFub+o2;Jn>hMKj_xx@U+eh{Bz$5a=y;U(Hj_B5RNYn0*<=&*I^l2!p`y6+3_b&R=VQ z5$`v>S2CTHMsQm@n8|)56V%nwAuxO1#g=*$54k(f11>G_U47NJo3M6NJL`9x_r~Z5 zW)%;N&z3GBQqq3z!8W;4Gud*?bnZ0ynbug8pMT-_OorA_fUkTl`AJKWP4k^np9g1$ zZ)g7I=jG*HTr4>A9%7qzDg1kPMN1Oh8|~8AF&nR0{vmScXN?oBOthKAWHjZc5X``I z2IS(@x(dTob|LF3C^1w=)8hzAub(5C>u9nZ{h6KQ+H|)c^)L1}!#^+3jZwv7TK`qQ z-E8%r2O%OQ;@v3ujQ$+aiqoyT_}Kk78p!we=}vCfwSq7rtx6k_!$FzPg^x%eB%6w| zzU6Ce{VL+`$_fX|qUJrlT`?G51dbSQ)T^je3OPiI<5szLbe{&Oi^x;a&gBWu?Z8VbcWO|I3G}tVjHzvkd z+U-iFckSd&*WHzd7>W+ejm3jD3EiZW2w#}-FqHjHN4yAHY?SQ%3I2Uw3K?GH60P`f zrdKAH!+BBU%;;dnM@zj7Wm&PGfIVVBw>MEUE%Fmr*R6JPW6wKf5U0I4bJs!J;9F=V^39qiYzGDh6Gh!Tgfn~| zs%T{l?w=kWdI84;#fc`h{?)14mKNotHtwuJjir^3V{4$}Kvi?Z3u_gFzYfUWZ4zX5 zkgkp11yeJr7N>tp{Hmr%J9U#owkg}UN!YaJtlx zIC$e@>^a|%rk0o*yUCUjO>vjkigi1m>XiV6S;wFp21NGPtLm6Un$dFeZ+&N*=|Qa; z+95Mrg!i8^ezO&S*C941R5Yb3`lw63?JphV68Lzj6407<%E~5GB=uM%Y)$i}Dw@@I zw_@sN;vixcsUl^vh=j#Vc%vshDqorr`~ACS`130ECvm5LQ$PNe z|BkV0VIUHhsjY^PcpHe@yy6K0#I(JKuWv>d(WlbVYxa+$9|r*lSPULHpVhVSf7+*A z&=R4oL)NMp|7<6Lrh*BGKi!}#D`YnusRAu~@kb@tGmJ_#?*0RA;->r7gS)?Lry2B~ z1N6Ktcq!mILr@P_ieO48P2_bv9|Cal;$7u`tQ8kPT4;nUH|=nw>|x6Z{F8V6>I2O( zkbxD_VHT)jt!9e;rCRWVSoHsBI`4R@-~ay~TXvF=St?07No5=}2_Z7x6d9$ESqRy) z2&D*xjEtkK%HAibWRxUPND`8jko9|v~?#$K!E-ZkWoO6z9va zPG6q7quf^=bHBL1r9hBCRH9k3>-4;)IeCF}_NI)GkkAhL{++j9QNx&_C7Gg|ht1Yv zpEGL3%YrV`te7L4LAwCUQ`6`dSRR@-jlwCN$Ry}z#AD~{o5EtUi6tClAT922Y~Ln} zj6N|xyX37tc&jbawe>Hk)Fvm6JfR(4^w#*sII$u7Q%K3i%r~8Xm6h|2GOAzgN|n?T zKSb7OZhRFQ%uz-sloAX^BIKl#U)?9J#C7w=g-+0P&;}iLJD_`SZ#wb$ zHLvy!&H4gXzvU9-4UXN6e{$j@^X4?O@3B+MYx?IxRAycT_UmyCo*tkT5!HG^YDnxD z-B2)xTQnt-^EPQMp>+fjujC686D7YzqBgHQ0CjMM$1~D4;J&V%FH0~3hAFJnmR3Y^ zJVp~HzvM6v+%wJ1Gi)t-=HY52Kl4JiKuz8J^T4yeH(5LuGMl=kwb%Zh`bmRnc zX3HTpqH6I_(A##I+Wc=LS|@G4y}iFPmbjKgM8B5l)aLsH@3bvPZ`ts@>9IHG{lu3x zx<5R7Sk6qWZ?48o?oH_jEt=UR-t^FsV_% zTD;EyLYOH5bWn?XS>DT7@}5wmrqV{b`uZc42_m=kdOXzwLlN3PALLfcrTlB~({s9o z!FxFlVb*(UT)Em6yIG^{%#GDn8g&8%X}0ozua8OORD6C2rI_buEpeorgXxyhhD%L_ zN$b_QS$Hl%P2AMt9xpDbGv2&!ZP-G3VZv%)F50TU+p&9ZE1ZRuv#;--m*FR%P3)rV z=e;!BY9*(nlto5>oC&#&jy~JWLK7&}IO*J|$&aay7i9QoLyV zFrKKgkVc7u*YPH?kUWmnS!h|!ICTm78tl8M*ugp&L@K!0g{>a{V!1IPd$$BuVJT{oj$!)L`39AUt&(M4S^EW;b4>N zxR8_mUL@yW$@P0X`V__Bu%AJfd%z(A<@ zw}0XS&g!PJTo5c8Qu&}kGBsE&|KdA0KCbhm_xZgl8@>yvo*y!fL}EWtS?=vfgIg%+ znIl5HqiI(QC#qsJhXF}>Dz067X6knO$|5XM6Z_U@*U?}MoG-8%p_;=#mBqYDlSxK< z<;4qmt*hr?(Gm&ua0xeU9(@$4CKWUP`5e&koGQUYRDvZAj*i81t$|V-$cpCnsH# zZV*nKJN(cvXO6f|iGTHNQ~II?0VYelEECaZeuqpHE;Gf3x&|(rE`FArkFfbzHngmG zN&YN>;K#h@rY>+8oNUBU`gMsYb>{ik=VZj~PZPN4Z7yAk@9a}cI!vt*C9m!wKp-|-1BEc0`9c>>I%2C@E4t5r1RQOqsS|9Ve$}?;~;Uq8=E#5UtFj-yD`7N z`pdjDnSY)XH(c@0Rr#d%ClzK4lv?z@I=-{#;ew5%)Gs#gQ=YvQXA!V;{B4atf>X6i z-%khN(dA%gA6j-P>bbg5J>U=Va`H>TP$OUsE@OO?WywT0LQCM1ndO}V5E!mbNb&Q&r z=FA@asMbE%X_#H`F1D@)V`F%p?kQqR260saa);~uNw7< z%7j%NKdxoZ#_Z&dQGoA|pOuITSGV!>+&ng3RfhybK{uCLjBbh4*6SL&T_o>m>+-&s zW?QLNT$aF`BPF=nIKnV*ESX_;bGKd>_jUT5I&&*4)Et5>&gj|~7M@Dn9M^Iy9|35c zhX(Dap}>KQdgoFOb7D4G@|}{$4=%@RHhd{Ld-v^wuRp~JtrDcd>V5&SdG{yIK#d{D74M#Zfk4vQ;yRqAdj!DE+5o8QiXeDp)qWi zLpR+@;6~Q#f<836MA|vp@`-e98BJB};fz^Vd(GQk?rr%y4?IE-&0|waf17z^o$t z>L85B8AV4&OXwaoL@oQEFTw@{yP$l8DN0Muw;*f$KG@k4dGhMDQ1Tm_rq7;Li z`{axuD70L?e0fu37!2#N-lH%N<5I(O9);uRT-*5rsrr3owa8?YlmzSAjKXxkq=W~d z!8qSo0g{$;A8hPP*y^uq(C#?^-0mH-@;+)w_p6UkTvmpvuO!47Q5>6`E{E1qY{@tp z-H!e~F!KLn3o^;ye1xy6+SrahHa%|0qsE05`{Ba}!ItaIw$DyE<5^5DmAZ%%(%D&$ zr`b)ezXMSoU>jhdsd$`m-+k+X|2j@_M3Uy#iyl3q-$T1Fex_(wRHj5!1)ZDVg_NAa zi6y@}(*gtsTHNuEm+5Y!h9*{4NS~P1xX9h(pg+bUgpV#{QP+)sU>blibWCo8LZGUy3kJ+1VITkIAOM2qmqz+sOQPiGJOrYA!E9PR&5e7VJ)-2FN(aK#GF1~8mfOGd_9j&H5AV5~23R!r74l}rGa)yaV{DsNW-@X-klU(=|C`1~W@ZkzH z_n8Ur|I_6R%Al8OMcuRlizQM{c_*z}${pEl%qN-rnQW*3^C*GV$>(^$KnCuxZ*SXGU1dX zRf`P$L2|K_LcR#NOYkoXMy^Ukg}8Rdiw|9Airs0*{Nq!Z5KDMSyWugK2MCm%+8bdr zebGCy=?kfYqM~N=^(7d*KVFym@%WELWnP!|-|gEf)!% zCjAf_QKOGOM5DzJQ|Y^ksT?itd^dp5dvG>wbpFD3IA_=SiQz)q#Ii4%n~Lri46Ofj zJpQ@61?mS6h2TV50?Q!-@pHTmLkj9PUxUG$RgH0{*|yhWtc6@V>QkX!!Xy=SO+?Hn zZ=A-6#_~NlhjM~{(ChN&xg@IFSGHev!-fpKW+fICmYei)tjH`wmLoG=(tInn*P@5U zDA;=cHU(=iplgJG?^AkDZy~h!ENC}r7|zMQGWWeqO~~Dui91Dqr~iAy!+jpHOk%jB z$WM}eNk}q&H%s{0vW2f}xGhZkiyXea_rPYh?H{}8s#XQKrPyz5If5)l)(^K$F9HRq zm`*o^;>-7UzfZQ#Df@X=t7l==dTi*s;3H;1QY?LBIHir2ENWvN9dX||k)2WY_KBE^|l2_PzbCo6?iu^|Q6jlFH z0AITgh|z(S?}q`kyAt@fD_t3rH{LcpA9nBGo(4}3tSI*W{(3zJ%}rgZC(BSKEH<(3 z`%PMpRb1OdKxRyI`j!?CLHSR~i8Rp9Q2qGSO}Q3b{zzl}VXlA68oM8(=DRwwOpy=! z^RINbhp^l`<{$!#AE>CQ0lqdrH%-C20dzLFb;y;bCp+Gn(qeFRD(OSpyf zvDxwKJt@YLM^6&J!zF~179Zljv`-nUd>K5UviiI6Ho8GLsW5^^T%HZODJ+Qm{QRWn zYF$*a4B%~7ZgT2IE@k z5t+zxs?ojw<3~Od?t6$qNCtuCl|+n^d}QtCcb_c#I8y?S2jkmm`nmG4+k+F(M}pJA z+B^8wb+KTEr)H1nnhJ;Y2ZseOZo9^=Ql~X!G=q~caH(~mW~uF_mIlqoKG*tHVB7-6 zj$4J?ObqcacDvXy$2^gcPyXam&Sd^Pv;Lp} zySnSKg6>R~3&89ft927P$2{^*zQmt5@mxxQi=2bNN6~He&g~L=y3zQ?>BOT3>;(gR z+e+TFhed80Sfo2Q07qU;E+YDZEevaYPdWl#GpQ_Qbm=QHj0$gA0VVH`-QC)A9v&;b z9d+Murz@kv=irv8UH&{(hcnJT{^an~Wae^;CrVk_NNtKEsLZ6d30jn;m|rG2f^uRXJczHzR`v|#@Ae|29Iiu@P? z9`{Xtt!|mmnIIWZXtNB(>OY-O$HLkq=<7S7S6WN+{I*k)H#O;k%cnL=~Soqh=MM4=~ zKFS)UX*o~k{i!NR(q$dLHES9k?u1t zN&`Fw?q8)jke1Y)S~@!o0)^hvlv7t`;Bsf@O^GPu2fW(x2-CJ^xEg@`zTGo*>8fp{YzE5!2^@yJy!r49b`uNhr~%_3$dJejpcBQv@f zB5auIB<*wKSS(+<_Yd<9YbL312)7I>pCaW;M1}p_q^~fkBSIS1wQe@!RkyMBdj3{D zy{LjI2E#`$!vF!4KJ7^Hcg=eDV)%Wo|KfR~T_1TA79qWUM*C@UlKM4&U#SugemE`_{Z%Ur5-7bhMNi8|l6=rq| z>Dv~rvU<2`(J}rJk++)`OyV`}YkT@+6U&t*NM1f4!#xDmxjPvI44;V}*{jVONeMgZ zn>y32U@kQ)3xy~tC z%(bCJ(RM+E&UIxy>mGZ9kMeyIQxxqE+CWSnsb;=4iP#aMvOkZZk&vKc*jo=O@^uZZ zvLJ!GlMS`lDn2dc{o;p%NM%)oo7T@jPtt;S2a9Xa$tuYcLH>J*a- zwKk=!4329z2LygjZRxh%bw{2MG!ZgMsNX*NnA*a>Cy=(FK_g3N!F27NuR-k!4LBau z5RX9-VjRqJ;~olTXFaB~{RgZpEX2iP_ChjKdW!owq~vCA#DFX=)}GmiKXg#s>_8yu zZQ8T%_DzsF9=`R8pqak%OW6mnc>4L`N1LuUxN@pLW8k?9=ciu9=CVF#a!sXFtL>OC z_Y3E~oY(hGOC}VblGp&wH|`zeH_x7(ieVXaPB~xH1DvVxu|lXZ2gg!!sDDY{4gN&e zQ`-_b?{{gV*lttq8q`D{m83J&NWxexK;{w{>Bq2<9XV;_` z_`zCyckPnD+qJoi@djI-Ek3X@o2m!>Vj5g>d+|#2nyS0-01{tf3(QxxQHh#i4oaDC ze`xPY)lg5d;gfvl-FZJX^qOgQhudm|_AqZZ{ zUSy@%*X?sz(E8XC;wIDr%NRDNykMI?FLCZ$cKoboUzQ5SXd(t`d3cdsu(?-;ZIj=H zn_vDD@#m!F+`2>JU_)7>CzIbV$T_fU=A=}Ej(`Uze#-zrAA#!ul6OD{slzm9?FRNl zyU&5^tHuo+U9wcxL%y+AUPlKAbUu=EPyQDKe1>%D3)Gp~TIjXGgKl6ajU!}XAW&sP zQ-f?9kV{t@Tvy_U8ys@gq?uBMJ+3y)DP~OnzH8i9b|mBGupAOcHc9Yzbjkjk%u$JN zjLw&*B`EFS232--9blt;o13`uWhwgY+y1WfH4hUL1~smuGnk&Gt(;E1snlKUDFl2Z zsf7p``7u{3MU!Ty=HROnXEhA@DtTD+DiH<*&J8dEG+H7sX5^0%0(b?=#Z8@-jT5E95%UVu^=9*4d!$O1X z0|N$a!a$@-@z(lLpX(axiT|pe(e%JIz^KHuxjW|AC@XOK)GvOGwp#wwbH*QDeGB*I z*;xf)KWa-A(CFjqYg6lWZ|x`F#xoyn68t!iV)Gq-0^tp3k^uNTlPhzXHwMZO6R-b#jEQti0Yo5%)h7clZ zobWjT#2bC>w;bJgS<}!&D+K|M5INi%@OPfyAfy0ZFWy@PO^csjZE99^tL=;7{U2*t zjp@7^!ZvOV@atg;>Iht$L+_QZD1_nGuln@>LdDYed+6m&RSd@hgUvvo^2fHYEEN>` zUY6lsZ`xAWqF-3Q_V?}EnV#BT&zLx1S3g|eTw0U)68GA^fl?>PGm*udgOvnjgNi2f z-K&3qza@pb+9yzKc^$WZA}t-fKEIdHIiFZ&GyFpxw%)HyW0T?Anu4Z6rf7a%o7r|~1qYJ#uhU;(B;@GOOpGp+_8h>26cI|Iqx93(u9g5fbag(C8{?WCgT+dzoCoXz>fGDs_ zDpmioVErc7b=^Vz!K12y%MJbje{m`F9n{NWkzgV;(KF^al%3j3zOyq-=Gd2fzjmsM znIcHQpss9vIWQSFnTG!MPmgl%@x-(Az-NI3bZw^soH;EH+48YIZ}!_vfyTbV0s)41 zcy`om`tOKHgRIj{jm1ftyk|k9Fe!7I1>qDXpWi}|y{AXkb)5H2CB{N}!-2uQ+ym3@ zp+guMwRsr5NaObo{QJ<=w!Jm}^gl!ZAB6wIuY^ar4=jMm1MnVf`1|&8Cb^%EPpQsn zAT}%noo(<^Y>C`4>;K1$O;)LbQ}0sk8L+QDuT{bD?}zzF)FB@A(0SG{=(fyQvUXT# zia;>@rYN*IH_XN?Z41?P*y?UUu;x1xA1i8r=K-#7Dr+Dnrp;I~;P2RSh#Q6`AZH;D6iRnw{Z2i`p%kmJ?Z*JT17*&+C-b$$(b0aE=BW8R>S^y|KyTo{5)09ijW{`58tL02wcqN}X-Gk0^jPHwWhet$894iWsrv zRUMUSJ&KMp8zmduM2V;2yQOw39y%0p1l?=Ue{R3BPeGN=AHgpqG~!cdCY6#SFDsi( z^*}s4xmzFxl4_49y20R3{k`q@On_?T8ln@|hj$i+*O$iP+|a<~j3ji^M)6ss4!j*G zld1~QZ4Jslng30WLkH3q?nBo#LfVx&V9SweMU80L!ylo&^EMj=L%%G04Ab48Mr9({ za5b&GvhunwV4I-F^uCjAQ_l#}9h&wV=r|_>rpiH$c=7D$z=N`~7^3FfcjokIR%_GM z*BIo4pDfn2$CGp$P#5j^*CVB5@-;SO8v)g$*a)7fw7?{z+poeXHYO%pV>F3SE;byw z8viQWAw^;b_)C}dx?shi%`uqN>%uQ~O+?OWNsFu`RRQi$-! zw7AQC!c&{_9iBfODQlJ;J6lU__!^I`>QY%Z+i}#fB$SyJU}8_(`CGg3ve@Q0AS@vn z1-C3abutfLdiy;IIz>IL=VV^;aB!{zn(+A1BMjU)&P9KpR!~^RF6I2TTdLG_Kyd=D zOgPSThyFI}y()c#_rgb5x97S&tG^sy9 zShCYKIcanWPBOBjOt=gA*)u;W-ax0%i_25{V~6SW*GvnZ zf#P=BrE*7l2Nf}Ll{z!{^Hji=j?c_eU*+1%oO?XYFp5rirn_!xtg3g4WepxxZwh-4 zYUKXy(+e<`ytTK!$*8&Mz2iYPHZbadZ_8lBv?UAaVgFMm2)hFYwsGiPUeBJ?vd*94 zEcdurf=8z7mhafOT3gmxdd!M? zC_}i!kdy{*x?OGF@XMN7 zyutw!lcUY-E`J`IvJUmr7`e&bK2_fXycp{BH;+0e<`Kg7I)jpBAHM zMmfiA7)xGm7H|A6^*d$&rSwe5se6n*u;!0Yqrn82B2KUgt+DoPa!j6z;9fuT3O{jr zKIR;_l$RWlRnHTn`Z7};pJDu7i+ja|p^cc=fS=aG{hPhojOb}8-nU2dC%}v4R9GMt znHGpywz&UI#sk-=>yw7N7ablx1rB8@J1ElucO>wXh(Jh|P-QC%dR>ODFm339lJ6$H zsvC8Ov}q6H0)S`wu_(VayoA_9nq{=d$XS3*=s>uj1e(IzGFJeK-NKS-!O_ zJ{x;$@TXzjE#T>8{jji6iRsH@PX5<8Y)`wUEROGCA*jg{7#G_X$H<}G%=-%(&WUzV_1=8l1oVM= zXk_(e?50o>o@19_pEkfAl(xUc+)YZyxj^(2@j2D%R++X()Y1#df5aCcWGP zZb>^b%Y^UR9AP7?H$m+-r|w?rC|BWe4JEGrINjjhIAgtFvBcl;lKUZ#5;DJ=CK3Hz%PlYD40tG5J;Gb;>}Daiy5V82 z^1&;DCE|xa|L#8jWZ}i(x}BnO{iA+wGT?$_m}OB)UzrJ)2hsEc2^KjbI4%QtQLs=cx%iHOqMyp@Dq7K6eQvo*$3V-`nCTd_;t{v(Y1t+(Dr-gUiIVWu~?w_ zbHUb4q)sW%acb;D;5bs})4kj(I^yxUsbxjIPS9bGf={&Ws?9B{ciV31;XZ$dN(#4wi7_n{U8yvzJ#T5lDlc)SJsp;l8xwE*E!eUetjjT{<90{1D(GIomdG;3Hy?QObfzaraaIvDer5}^vjfBe3bxggF^K7b+bI2 ztovc2d!nd*=Kke&-xmkg?ECnG!Wk+S&iwFH>q46E`_U~sm@Y>84d9V5-e6K}?1h9x z298eJt=gw@yKdPfKWFQdaY;e7A@0*}&ws3w*MwG4{f~3sA5z@-%cCzVhUHRn*eeb_ zptClWeBD#eQO8*#ac7U)+oNuu9O9}d_gwy-s{K+kPCiVEa9Odh3OO3iaMdia`Ri3` zp&tFwkmOn_RWO0%l?)qYj`VZ}fi6Li;x+wA*6WJz=xIz%AG{4RU~XU%2@4)j=8woI z{p-yt*3ZH0OdxRaN=CxRCYM z47`)s|7=9LOoYqf5SF~4xRdZehqtns_oGDGnzVXyUnr?lV)Vld?S%@eNi43_Lshg?lr$L zjob5%0P<#@@VbU!f4Qs3=3zjffnm(d41fo9lcVC~DS*SpUvLtNh4*hB{y(6E7+eW| zetlN_(Zv|lFgIA$P)v*uzGR1vY*u~2YSH@q7QiBCq*9m&uD)IO5X|-Kb1q-1OV<*Y zuKj(|dquV*)Qws2O2Zb_rfWA^UJ~?+1OFlKL_X50%trjE8muZl;L?1|b@6g#fA0mA zk^!1&3}B!68oe(fE1|0nAPN=fr=fWEGEPPYXQl2>KQu(q!5IzrdG^)XFR*NNzxEKk z0L-m;;i;~tXq7&=(bN!E@daLUld2a~;euY7P>d^#r?1v2G-6?n7Q=)HT$DE$@k;OA zd+Xeux3bQ#OHHeu`kqIjF=qE*_Yw_l$~wIA@Z=6Hv86b07zl3NCV{@$Mu`lBPtP+$ zFVb=@bR0QBcT2R9;f0z(N$kNet-(5E8M?-9)|67-rpOx0P*;A$fNa}?zZ|bLb$58R zxMTbHz`dOxUH?p-!Qm>GC%xI~^Jjbal0BN$x-)})1qeaI4KhPtD1(jqd~3(nU#ye= ztNjkU-pk|*X|GCu4y?2d)WGVeFnnYCi8z>+R98h0H`om|Xdfs}VT!gse4}MJj7A^N zm1HvJFkwX#Zh{i=S$adee5!tr<3Y)=R0%3ygoovp0Y*66WNtGfb!>kS&(bg5DwEKJ z)PXliAFM5Yek#QcY_VYxw-6bTpb2Z(|3Ofc zyCDy(xcPY?)SbMZI$Qx!lYDv#!HUhp1R{W6-XGpzvX3ojzvs_KQ2QEG55D@T`0U1g zmpKUD!hDP#tTNh!Ene1N;`VS&L~M0|`AEsldVPHS-Sg**w^)lOet|}>H09GFvSKp8 zBAg^o=wXV|Yn&rV*6&HXmjBwTvhet3&1*3*=w!^$VfcGKW5ZmHo_o!Uh+fJ|udS`y zcoY-TMVj>)_C2upj&;a1*wo&TBs}(EiP}SiUvj#n9y&zNt6k}(Bl^<-Sx+Hj_&49&A7u_0qqZ(3aWfjVeTWqt1U3FV~@U;yne9#Hu%(9!sL*(Qrt=h``wPW?zY`a6AbLTb?g z>Qz-}l9wyP|m* zuL|U6)DDh{hh1Y}sFK^Xc$zh~sm*>Ml1PGaE&1FZ(Wb$ikj~A?x)GAsVJ-7*3}yt+j=Z6x4nD! zF7?V6pXSZ~$;HlnD6dPfNun}42bS8e2&YF_9niDpo2Kz#Z&z;3c$tehhm3-P&W>Q< zcQ9QuKKGb*JoAD4SEVE+CB4$kU|L`&U3{a0|9XB96cO3==x8!pxRM6LVpn1N2=lT%4$>ZqN0u@xJTD-0Z4 zfTp~Hfk_B(3S8It0$O<;NI_N|4^{gU2&pHpReWG z4WmZMeA|?``&X`?7bIYCbXX|{*;jn9=y~Y-o)WoNJs~SIwlE-N3$KW%xat!oG9zvM zWNh29z#CP+-yNq8-$=<>%HZ_>;c(*WClHMC(U(4IxQ1ctONJ78qdqOab~d`kuF^~v z0$#GdflWoY&6|Uxu%Re`@?irvFFdMT=YJu`gI!3R6|+i_-A{T=J62U!T&!x#sE|s9 zRFT_9<=Q`e5-@Qo1at!`w)&^J8u&sHB$N#EskDH0xr4eD!&jUC0i^>xoMNxc>{7mC zOn74nWkGGQ;i>gHvX?*4?!Ii*Nv)8Pbe&Al8KEvV{tNeKV2Du?`5SKAUZpo!%+#SSBTzjo=S=1X?dkYQEH4LqFb zX77`xXOGRl#-~DgxCel*@r|o#7y1r$XZMxqc~2f<3uPI6Q7ZLiF<+cITqj1vPXC81 zkqwErW+Ge61^3AEyUtg4gQr~e=hMZa+CQJB0v4*T^t-q0E$_dCM-ZaI<_l>)-?2D` zBUS0(4APFDs2aIs&eM!^1$7t?I*tYs{Rj!Xwf~K0q?yOF+i@ObY7CGi-Z&@Yj!hMa z>O?y#3O(OA?RWOB9o!)BGM(jQqhS1QOSsZ);vq(D+K+ELJGnZaIUNGBM7v=hbakrK zyzLf#*lcMx07vfK%ks6`a@4&eO-a^mR}fMSWZ&n6VjxGFo*lyT&F~9#0KzFO-E#lB zdfGf^5=W4kA^aTlu`d%#4KW&h@MYSvh2i6u9p$Ub->GD%&z?QNd&5y;rM$i}NA=72 zDX(+&U<75R#OofAr9h(nNgW9yCwQK3r*IrZs4L)P0XZAJ2F%f7&b_**d+l($7g8NM zpl4sk^>oX6vz5j0vT}?2yzeR|1I#nP?nz-kTr9OGYMVen&F#tlwx3!Z81}+3$)Wwd z?wpMFGoc6bz~zc(L($*du$6$}H;`5`v1A&l2zTQ{L7n5nYe$;p_AX~iNJcfm#$hnj zf=NbvQyzo)Cm1O7JLuh;%Tt{X?^V3UUbiyay03m^)~kAztDu=m(DEC=_*9kkC+VwH z4M2PP?bolE02Z~fBrVB?j#xvB5p_1Cggd|#h{;stojR+oyp&uC~o*gkY1eQD+2 z*mzKSPhe=sa0qJ})WDnX+RKDG=C1hK5s2b-R$a0}Eg39Dj)(45>}E@QN{wBF63+&r zSWvixcBbymBc79mJrmSCd7ojvv26*T$wPt^8w7ih*B?IWkwwyn%zU!P6CIsyhS2lL zTNyR7M47ECtxq+$$BDUF1@7TtfeqMK^^Q47IPhAP02}3FSsU>U&}M7|uEt<_0fY>o z%Oq0Yw!r1`sZag=W@nTI@9&bXnKlPxfYoX?e#_UTL0Hs$S0}92aTXEdFf@Gq{|rm#uavkvfTlW*KJ0K?G_x0HP)OrXSE$sz`Ch4}G#qi=B_!;ZHX)5h6L5 zps6Ei@{80Z3tch)_4DotPjaVNU)e=8!%VYu&R`l--YlbIN?B#4_U5++RIh?U_bKE9 zLai=pl73whlsoj(iqPiklrYl#C;o0tm`zeiNeK=^AJBwtl8*25KlALb*2VL<<-J&g z8)^hGEj00oy+8Sk* z$;zp^?dE%DHqx9BE-~}lZ@CF(Kn52fQuNS`m7(b1!reE$@k$>nrn9#lw5gm;?&zb{&*U{ zYe$0>&nWI?UMX3~I<`-*>nWU**XQPz?d1L14hCW*yNSsv;B9|Wr}`C0_p7kx{wS4( z9lF->?x_o*7sId%+?hk*BwxD~=eIbrZ+*R8W!;ZZ@w2oQUT$0<;_&%V3 zHGG^VO;e~vG3w7OY%ouHR~7=-UxTAB_hzcE{P4BEXb0vv*nW_=?7`cHWT0kN-4yzs z9dpDTu{L~=B@%ROXd^FGI`w%;-=!FYgpl=IT^d{i=#EgRhRn?ML}{M@tB*k4+D-ZNJyX z0$`a}zVS((9r)!&Lqs1ucgw*~ppbe6oLfVmtNax_M(sS5|XUCJT0!K2<@uEn-A znL9znR_2H@SNu2luKoNI!4|RlAvZn@2?EI8w&W$1IefGV6j!9exWWSFFAL@xOZucm z9xy5DVW%`?-^?T3(xOjBVN50iOsO3Y%Kmk+&-%GtPKPk%4!--hnMC)orMfs>^plaI zpz8gUB6wQb&ZAc~&L^z|m+J^XHBH*^caq63Y)f$N0-K*smxCcf(syr*=cn(!g8@U= zG@lnSctE(rUUJ52d2E|)?Frkq!jXrQX-@frGM$c%_7QaU`Q(YJHq(Oid;Viz64EfFXYEX_iDdVh=#GxLbLM5v6QLr0^<+oat9^ZF%;t9a^!0r) zmEfUnh>5M?1x0lNgnuX_zMqYyb4T_;zu(BUZ3S zZ*q=#QP0MJA5|{!vfXXv6aR>r8P=ITXFpj7S%DvX$4gw2LNjmo+tF>%0 zAP2|}ybdqe7krhnqs=w`Uchq8B<^ihAGivc`UjTAxz`J;P!gvx)JmZcN9@enC#OSu z5?I=e%lp`q)EQo!D4Byd-X~2W3XtfcqQe60KP@JWuC|s!NfLbn<)*WlgBn88Hypj? zQfcvGMLXa9( z#d=hT6-;JHlqVy*2f5agHW|tkVo!0ENk>(vy4z50-4tZ*swx5F!wvJ%r-tM>Tyh&_ z_VKV#VRI>MUY(e zK&Uj$KcqybPAhjPX{VbY#l4|)@jnUP{=+w=r7z#{DZY>yAzEL1Ceo%%!1ueMV%~w| zH~sx-n7G$lBH|CZwGvl5*Z<3WWTdx2X!@;z>}S)awCO}N-eJ5~V?YWKnSsM~bE&7- zW<=HlVUJ521(SpDb*2u}xNvuY3V2KFoS)wctJPvt_?9!?uXfzkI=T4$Uj4=f23b9@ zKUTjixCDMb&~X*reSWG(cUbOewA_gwI!&E!vBQ`X;w{=*D%gVE@FI8p_p52IONY`| zuPql@sVgwV@ZPvlmcG8?ZR}-<>Fa5uC6Pe}sTh?fcZpaP~1LAsW>pZ@!g~d_q%2xHIr_{@Q0)Wr69>=O3>Q;X(j!*4{J7XCz?Kt z9)@Lkj8z!gmVyy8iI<&5SDX504cC7<*+@=%GYEaS$=%&sQ3+F8pKB{$pm^e zm>(BU+_!3R7uNNm4OkAZ443v2_M0@TddZWrANZz}re<#9e9)yAarmN7d`|mTJxU^( z&PLhwLX;3bwsJxBur=ALyIl82=>E;uXvdWKjNsPQK>&VF>!o6P$PQC9$>X`vWhy^b zK92pcl4_B37Jb^_eog4>+Kr)k8l#U(iOzy!wzPa|T(POU?_6Hz=v2A>V;_zt9CHih zXW41q9lhqOS{}&6{AYY^Y2IJtE}s{Gc{(?2_91+`PW9BBRbeUnzJ2{%FEhbRDu|PfuHa(vMPrs|xy8ZF zZpTMYr~L>CbQ$ww{k&E`J|(z*om}}a{`nE&JeNmcWMgFZRzI92Z%?lUrIJ2cIghPu ze5dp$i?DH)IlR)2McB#vbbxZA^jf`{)C=4FhCHGA!Z-3G4JsqKnnU-aL_h`)@^Vnj zs^I}nwes}dV{-K^UO@Ocq8xM=Py6v2D*MA%Z;DD3%Zmh{5 zQ`RyU;G$h87OwCO`4BpJdhcpf7O0i5-InK+i#)qrUunn3*~mi(qhPdLwR7Pb@`mSgJXp1j$FenP(jDX&Ta_2==yPT0rg6e z?*4**ZY~<#3_7Ty@Be+^Qv3wPhJu`~H){3GdyzZ2H{=J-TngrjPrw1ZP^e zR?0+`@vVZ!FFN7HlxLa@MMtz~Hh7W-jKqBCS}3Xv91T~6nQ5=le&iUHd+kntkYruj zdMt35TB>}9o*j7CV)j0&qB6j|(OB|F!0b2mo-dZUSav@NGu-<7*;9q~w~P+wOm)@6~PcU+wRQcZbh5qFKKL%-m0OVpXhWqVDdxjj=Dw{!(OeRUw7g zccb|r;hZk#;4)ExO+!r?*bifeiyMy~O;B({fldqu7&rI+C6-quM-1;EhQMt)`akgZ zPBgURh-&l5I|LQ7b8>F~e#qJ24tNzw0i3}B!&?U`&?${uUB#d_YENx#0MGbbhf1Kc^Q29IT$b6g zrI}izQkU%VlTfi6JG_U>ofRdLHa2L21MjC(y6GvgqhFgCa32jw;|j)QP) zyZrv2j?<4}K0Ng}^0VQUOdDOHgizRG(5Sd^Vk;3I8B|AFj&$2CBxgSx*gg2II4=;&F43s|7c^I$rkTLKLLlAZp zm-7A91SF$)W$c>-nF`q#_BM=N6{}gTo|wvL3=t*GKH!soqu;-SqX9Fr@r@@^ifG!~FCIH~ z%z=eMhXpF>EyUvI;u1UVccS!-JR!oavDn6m-b1KvL1{h4^>&;q9eg9;wvA@EDy`{-Va)F$G#AIcWt27 z%r50rX{)J%OomK^m~3c~ezf)x+$pujDWA$}pFYL)PCM`H+S0ML8x^&-E#p~eSBc+U zgK3eE@zp7~kVY=~fME5!Z_4Kjz|F|P$au>1In z^~=zUX`SXD@=;}M^7r-`tlLXU?+Ph2K zFW@#cusuf|RQ0herg<~dK=C1g%}vt6aYj2zhJ1uCn;0s2;&N;#G~=*KDOuMy?U>9| z-PGBPR<$mG(dg15{da}Fq;Q;bT3NjVYRZ}$Z2s^hx%XJ2qnjFdrPpqK;D>$e^RWdp z2W5h{D9h)!8)NOh1^+*i&I26l^$+7#NMt4}BuP>sAv5K*LJ>kJvdhfgrL4*f+0jt; zmK9M6NyxE6lB|S~&HsM;cb)4x=X9Cx`};k==lS0E{kglca^aQBD3mt z-@4(uYAo-FJOiBXb{t=cB|bZNI61vO-8{DXIYgc!a^bxgcWX?_ds|W!1BUxQzTYcb z`tZp|<>sX*aiQ}*yk0_aC1Fh06j*g5;{6)b4q5xot#gvA6u)VR@Dk_ItV`7Rxsbk{ zGP{oFnYI7=fY0{#<6F1Q9uXVu8tvL53*L*=7Z+-q3&t!FF=F^bvu7Eua)19c3rXRG zR**6to7bSUT-r61{{>Ro(A7U05ITuOkvx4hRfJ6x49p5iOQG!u-_+^^as#>XZZQR? zZzOR`@MGf(T!?(4-uA?yDj%#C>eeCpDrYXwV_ZgLJ>(Gs?oPwb`vgt5*N9mnTnpAn zhC{pc!32%?6AxqRA6YUjziTOewj-COwV1nbrSqy$1$_OnLQkWjFeTT`xL<49F0Pra zV{5=0R49lNJKYl?Bo(qyH|?Ir-Y|9R*5m4s^JG})tPuVdmL-xw-E2yXr&9lh z?-zkK{tt(ccKGi=#5;35(fRLRqdAeiY!xwTg%)*w1<-!(9>%GXdF8^4E$&}M(*@EB z7(;N)?CwJbaa4-wE-2twpeW5gPo9>dINwX2PoFm3^?qK#bsZZdpxHYo7fn1MvzU0u z!9SHn*gx~@)RzUmaI)9*5#&0mvg^Vzn}OL^cb;-Plgmtk29&rn#|k$i&%S@jyLk&J zOl!mV@J%NvjhJUVoQ)#7O~z^CFE2b8G^x&fzT>QIAs2uoBC>(@vJQ>|mh&(jtN<4K zP%Dd_GVO0^7V$d=)a&z*+~~QRzCppqIOjL#SI~3d*6S5KPU%SkF$ue(YFnSzVZ$JB zjQ>Y1{>_(%@m^dV@jn};@eY`Ck*pm_q)VGeSJpeJ5msyTg^i}FQW#Jg(_dTdopBf6 zS1@usAtzu#GJu&z4XKXHXd0PS!2W-wyWlq>?g48F0jmjfRD)6xkV}|V&Ha={k1E-3X3~21k-c@Ktz>Bff4}~U9O~Y_ zd;IHMN2LE0Iu>`3q`0)ahT8WNj=t`Bu4$2j`U(yo7G!@Um_7P-P-ew)o1Oa7XuQI3 z-@YhIQol4>^0cO#T+QcvcVO?3c2v>&N`}K(agiYj_|Lp@ssQ~e-(Z!7&W5wml)Xii^lDg{OQ~EwSVF+ zyO&qJR=UP zZi>7tQ~vM2K(j!zdhz|-lY2jX5d45T|^8|f_z5|p_#pFVyxMgYsve<8WznK_2lw|PS|KTq1kyam9-){GdvxEw-% zd_h(LNcwxIh;R^Qy{PBr?7Tm=IU?dGPE5Si85ac8+tDlogapzB4iz$iVP^K6zB~r3 zTvUN02k%mRRVm{r>JYH!fBTBG{u{Fw<Z6Wct(?aye^kPw?N|f{S1R77uQ$w#k4!;s&>!GfdUh7}; zp27pAtgTE34dag{GJ!|9*Liqn6b0#`>0sIHKwkD`kIk*n_m(e7b&LbG43yH&rYT=? zQ&P6DP_iJ(50h1&geubakB|r^#KtYTnIve*Cr2g?&9D2)>-*8*M$Ab~YqP?>O;X{- zB#DzrY5&}*3kOrCc7;e*M1kq7zD}rrL6}JWg)*H^! zZ~l7ESwOU$3gP4AuqVvFSN*%{`uL~RH&^H8n|K1?ibhTn9ekL9)%n1&M!JHU z*P+>EpwJrjUh$ppP~oXvo66DbTKvj5;J$C%b*M{FJxkYjJ}l@`&GgTwhkZ{xwlxCNl!L?Er47tfO#5L$-iQU$2qUx9q$G2fjgy);{r&v`g$v^Y zj}wVd4WYl)`PHSR6ju=;(~yk>+YD!dT_>k(rA?s1p|v0(3~OTRJ9F*$v9=x;WF(A_ z9SLs`_+r5(B^A<%wU?!cely+vocN_!GWTDHH!l60KngL8_#%+@zl(e5BP74@?60^nsMR7G`nD{So^Mg9}Y(g@^8y$KcAnJX_b`n zmFH<}naR#ZkU}6tp>FtiIA6|>*EZrwtz(ZKvl605HY3`{N4?VY7&#Rg4E8PK^zN~d z8?!DIaqWY9+PL9eX{iWi!hFIj*00xhw<`0K7~UECrn`LG>t?V51GVpu13ahhPI#qZ zXpf3w5C8GuLFc9`CzClMsJhVDgi}CfN8qpmQW-hjWPwCdDm@w=@Yl4BPA1E% zADYC@91U9RK8K2~QRT3lh#37lGteZ|?bQ^qFE@o-kpa(oHltfdFP9hgPr`)~ty<_( z8PjaR&t%3o=U*3q`iYN1cUqW^NvG?4us8H;eG?zc{ZXlq+tr4d(Bo{ZBls{|^IZ|? z4;R4J%m;SAGo8>M5V>z!kR^ySq3Prw|GMh~w-4pQ3y~4ReKdg>_pJ7)>o+OtutBh9 zVf96-FF20#94}Dp=TL&@7?N-m1KHV~wMH*>|JpFS&zbM|^y=NFw%6r2bB1hlp zu@JzcLKtGeUXcMZPW!?GrqIyM@kZ`&$&-d+40ukFK6rPztjfMsL0r((p^Lp05yd~Mb5NrmTx|7te<@NPV|B}AN zP(JUl7yfkxwwMYbhD&Z5`NBY0Ae>LM>zYrcF)N{?5tw^Y9{v{MFWUPnk(A6_&2TLW&xi=eA6yWLl0E{A!)zm;o>% zO_|f3t)lu&Cqv`1xB4ZBSs78ovjKjYHs=xVQDo5K2c(qP`d$3o7OgE<4adD&iy?3e zx86zk%h<=+kD_hLAVoB*5*XwT?a$;_LzM(S<=dj3L)rQ{?7>KzguXy(50tPsk8P9q zRXc2S8Sc<2d3kKa@Z+f6D5Cm9%fhO9keh>piO?b5W>>bhD~zv3ww<|2f`M?p7l%p~ z0y}^BkTlqeVM;6n(Eo6$%t;f4)G$G#!;yjw=g2g;$WO<+MWLL;rl(6Cu_NFoTI~lU z!sK?;Ur85KWI&6aJy*-9z)`5rQW3i-gSfSaiwA@5m*{R{FYCTd@lK-!Yge#^p9>1F?wSkC1}zkcT`f9 zWsg!OXQyIj&1SNy0D&Zey!DLuZS!|>iw5&moEs{r%}(R8^$jpI35`}zIvIF)ZC9bWOH-#RS|4Pk^B>3!(tBYYtpq}eY;!nG;G6_;qIo*8J!DgtPBxrxQQIMBs7=Kb) z8ci#hDdB!x^~Ec&>8}u{j?S%x4Pa{kW*c9?yUY~!D*v1($hVmtHh^T68NmT*v4RFG zjjoimG;U5#h?2+(hjxlX^^J}j^YV9dgF!G`92LO$wjyl&1?EvNu>K-)-Z2rePev*N zE}5Ydt>#6&Hzanx%AZ!!mE|tzwnJbfV)Xul2LPVnE53TA`cYi-roDYzIktzvAt94V zB1~a#&5Il=z+Rk?^Oogy1lrwPlsgiS+(D81up+G|<^nT>0rR28y7F=XHKtWFI0BIm zLVz22#&_mL7X()}{nv2o#e{=Ph`%sChNxYtj7Jr2wAD@jf15Q(Y{*|8MlfmY6c_A1 zMtPm;jM~`BC2BBf|KPDnQqF!*BiJZ{gk3WnY;645J<~{)c2XA$Giy_%@GCJ;Bza|A zu#rfHM*z;XP#Ov{R9%+a^MxDNA5e*iQXw26B<%jDkV9ote!@sg>GAvPBqO07#+EXp zecYhLG?s=c6^u&3stfc6`kOc&;7so;e|{^Umm1{yMOCpxoPAmAw(KTn zm})lYxJrbNq-s^?bJEziZcq{Ey9xy z?1Y;wHv9ub*dFr30WeXe_55Cb6v8|h4*#>%;2Yp^lX2mNX#uk1xD)d`VT9UAoViEy zzXSkJC=OQdKN?EN?Dx2VN}YNZnSRKw#}aUL0+cD<#vFzhkqG{^G<>V^y_Z+}x$Vyd zFX__gM)bXfVIjhvzP7EnstR!`Ae$dO;TWtmxM7@M$2P>{v& zTCf^7 zgU7F4#Q^-!0HyYQ&=9yb@b^=y%pL+R81ZKY!pce=_8fuW&gnRJ5ccBw0SMae!RV`h znb)<=EwG0F<#V&M)8Gf@o`VSJrNroz3H_0v4a>>+0#+Ui3xxS#LBk79(3XABGrQc} z^hbCN--n=$_Z>?%p7ZmbHMqRg@jc!7wC_kVg`q9fqz9M8Ts@A zBrQidyUeA6$F6+H!BeHk02$$5!*&1sh5@U`x1aS)zs&Y-+UMarvg?Y$<#VK1H$DSp zF`rYnmcz65Ib_3s8=}q3KLqBo&$gMDmtoA?j*bo>j4#9Mr3rnbjq4C`S4;xuP*#^A zxT$gK=Vj+7j#l{2g&)a(%%S+j?7jtR%d4I}r=oWK%0hv6g@{9l7jBhLZ+GVE*DZCS zYu(-5)sQ+Il6`wLqOA1m!GuAlnK-GT*Y^XNDHItdIx_|x7Gw(q!v(`jL)5k~5*xbM z4vQVEn+iuAd^>waD01Z{r#<#j(4 zXvw1Bldh4sCSvrn=p}6yl>HG2zH4xio~}Q9EK`tXq|APRTuZB-S3BtAQ*tgwLKX#0 z)kIZ(c6O#9XJ}?RtcMvF9;Kx{|9NmRz@+C&2sRYwt8OqzwSgmiRRp{2=eDx#00b`X zW+GIbbd>#( zRmou_1qE;Oe(q#;YAqJm^g)|xx#O@rXc;-oyFF8N9aLsA8~2<`62hz=06u?2;)ok-8L<8ilLSmujtq1vOFb(x22S+%S3 z;pF!Tf25|Q?Z?>9$WKW{&rRVpa(6Gy?bBVHEZpDIc&RP=!x+NjkrK`pVkA(xoLQjxtTUy6>Juo2{QMG(9SD#WQS^$sN3TG1@sN#;o?BSqxO#kPxa=@hsE6P zBuL~6xBA!B>Ad8UalZkdvBjjE{KW>9ih-q2zTeS@Myig5$yHv*OB}jS(>7>@j>*#S zzo{v^T2n9vBThSSKt#xb;Vj|qB)t?o@7?KYyAVJm6ax$ zfmP&PXQG14W@8wuz8fDOZX=hcHY0Zm52O<~Py2mIT^Wp=M=F&g?0O<>8vINw^8&X1 z7nN#vI~B`u?=K)=KizE6dCtpkUuMnCFsiTXHcmgW*2VdLB7=di;stWMA$ z{^PDw!`UmQ`h~ZaQ{Qf`ZILolm3G45v_yN5$fJ<}K5gzHMU>vi3tr=t|HF$-32G{u7+&yBbYmlEN%0%YBp7}Bi`b1*=?Xql5gejk&|Ty(o>q*AX6L5( zoyWUmx1dui@onG3C_4IMruSaZU9VL<<#}6(n#^)9~|%<^CqsJw=}38r!%VURU_Xak%c$5H&$#IW+~@6 zmH5NIm)j4Zi*dqcdStLL%am$`SNo95jkAA0Qa1A!x+eWO^C?Kl{_0QOJ3XrtWp~`8 z1i77#9w_&qCnK#t`Khj#s$(yM+_#`Rw4FZY%4c{Vrx~n;Szli|+KSLg{(o5{!x%sEsl3rP^)$eR-WoPt^%@7$~2?^TQ|2@9!jRLh;x0D{lvK zNjc4HO4c^HjS-!M`#3l<1?dj%B%5$w-+b+CHx~Rvg)D#9tHKej`+py=_lZVj48Iqu zP2r2BZ6j&?U3z0@)`bO`Jk#2}$rfd!g7neN=e!5X*(Gf1=W~x!{s)^$x3KiMc6(5hVJNYv?x2>N<5$ zuXFg9O$CU_Zpf^Kb)EGp)^L|T*ks>VeD^_m-UmVbH#->!DRu?-oL{7mN;NrC@^DfF zk#-q-+?dp6IG9kGe6pD|u3zln`4BL0m%JI*OZo5H;1U)-DoHow8E@)$OjTC!WtcHF{8k6Xop-EQ*HJN8; zJ1cKDQw%lV)0&Nx;5k`aXxV*-gDk{>K6uA1g)yu9n_=lkFyAwN=*ms((P($|iheDmy(v@y8+g>*{Je8;@5@3e z<}Y~j%#OF99`tjYWo3`>t`6P}m?SUmPIi8=ePwc`GMBXHZnn#>mi;OFXxp}B9#|~r zrqo?*l5v{sb9D09RB%f1SD$_KiDRoml7h6Rbnf%{X~pE7TSOP&GXw1_f-KlxBz~Q@ z7E8R=6j7vuKCID+jwiZ}Zm}U|YzX?v&zt@Ji7ijSj}MGON!6>PAEFQ792ZxLvmyQz zHJIjfM{u2Lpm3`+mUsCyd_*VY3N+h>^N-+5{=gDJ6RJHOHBM*J{Thnb{ zW|#Q(*)3=^P$hJ4u75(<7<5=uhtZ#r@J!CQC$7B9pDu23BfB~vk@KYiQ;xho_DQ~5 zW?Bd1UR&QznlhTgIJOC=EwTAR+XA<06aBD#x}3>jjVgD#PH0h{jlPhd(e=hw^~kA< zc1zpS9ARfj^H`M_^LGuZzFzn1o}-Pm7nPO$7)7s}njAKQ+S5$IV)fq(>zmNAHU8A# z2)Vu~zx1$w1EjT*9!-s#>K2C2eH`?cILmMJfko5H#!Kicw%nEDvGFA)L@h%^n*|bs zDxma>rgK~8*G-(?Em`b^iO(y zOU_5@NjIjOiUzNTXBP6Mk0>wX9zP1y9rEJ$2+evwn-so^E(q$ta%Jf54*}Gh&0usgQ1yAG)bE)YmgL1va zd&@QV6-n}(q^d7wpgA-D#{G@6pNYcVT~2=^wvVb)C^1^h(T=X&JMKtZa##@Iux6oe zv{#X*NiuMhpMo21&deEHM*+sh&Z1TeCYnXKDC3BHr z+L3G5)ag>vGV!$EOT#7X_M2~DXqUA3`H`C`-1h%6P4epE)Gy*oy!q?q##?&vj~`ei zta+{2vzj6mbJbt0`O25aePR3gs;I@yTx$iNd$u8Q@!%h2UWTikJn^fUH@TWbUdE1< zytH5%pLx?jrgheH_Qs3tnUbiW;THopjwPenImhT2(vnw~-m3O286Rs3cWQDbst!?S zk0o4-`$`hHe`M7+O^=kE1cR~uU{QA9#nRhPNRPhCby265St3{vq)KY_g+|5_7$2lX zyxl4Z-nOT|RaekI(Zi8jD$*#Y_VTXAZ%mD(F(Gcyve%L1DSK`Un48(y>oXbk>Oa7@ zX(V8xpR3MF_Ch}=SL`EBycrj+#`O*p&Fs?5A?Q*-8J|`Aa|`x1iGB{PvkpzT0sao2 z5tm81VtMhuMrW>Ij}8C2biFE60!NG%y1d^*KtZBfv0mM9)@}0}HO7ip^*-#lzjnH_ zP#C=C9yddKNwt_qaZ%*_YUXi9m~P?N8}sEb_3crq+!#*Yd!OCd1SobN`+AXMPQj-s zfTpygfUfEC{g}Moc=52NQG748s*8NlQI?5%-j)1npuub0zrYtgx4xO%LKT)N1@0Qn z`r@Ma*#=Cl>hq7zf&`lL_B|lH)D#Ac^3u}Tf;6$*PiZSo1^yyX2_zlmIz`h(**-bw z&^xQ3_kR21$&LK!-D%^VX-JGZr@UD>xET{cMj|om*YkK`j4b7YQ1>G}_X>|W%dy?W zclk{+BqTOgI*R&i{R5Z3^ezq!AM)nT{a_uMM49txG*eJ4*GQ1fuXi_Bh~0`))|b-M zj0}Gi>*+n9qb1D$n|@LT91iEZ`)wi}VdBF&sHHG#oc(S0-O4?wP{hd}b}QJ8j3N zERk#};8Mn6>PedMS>|9R#Af2TcgnTuxvg8Z9|>TRnX2RKrtK5UnkKUyeVEXZq7`CP zD!tvK>H#U0ltRdvQm*QU$3`%ph%^Sq-LyH^BI#KBoCM)5mEunO8=fi zl9fQL*)y7jRoNVZB<&MSeWu-lclX^aJJi)Y#txEKcaY#agJwn4R-I?hmO6>ZV?)S{~NT@Ro~olursVZtChwU3^L;R!_;aS zf(N()rx!-1j?^y{-jHTUSI_$XQGCPoVR{<3hY7Z8R6I4?V+r)Ly+xZ5GOrikGM^uE z?V?-qmSLlf+3o#3><~;HuPZAniK?>C-(O(r?Q_*S6>*bmx0BWk%azSOX8~`sBkzB{ z_L}}LkM@!s__2GYu=NgpPy#_D$K@ERU4dcGm>dLj_{c9AkKY!8GsLZUs2mg?J9;TR z0ShBEMc8D~oog^t276-qo{_Dq3?(O|yjebZvuz}7yq&Fbc+$~Z;fSF3Ceiac36*zy zZE$$AcGvN*yB>E&*CS&g(Tw7b63`w0%7H~4)|-mJ)*D?f=&c9VAp{*2(Y~F zO+Hcdzs+3qpqh1XdTo5#Z8)*u02^zYoXn?)lF?r)2e~m(Aory)-uveCW9!#t5B=4c zw%pjZ6z^dqjZ7!;t&A%vxjec2FSq*RL&~z|Ksisq#(du83b;%QFpFCMrP!{B!C0ii z*2?nh<@U<2N&L;+z5o1zllizuy=#Xs703jd09_?jUy6itjF0(P9s%RRBv(4nEQxx@ z{&P+yq2Cq5j&L3Sc7hr`F_?asg#PuG)9j*tWt&uHq}e6jFim!%;US!!XKi6C0gJ7#swbKi5UpZ3$u z>4y8myEe^ZUaib>h+5m!@IHQA8*!}`Ot5ooIY!*LvbrWQOI8AiAFzEgDQd3MnAQbW z=i^wh^RcDjjQc>&?ab*l${KDb%`RDQxf<6e=Tk%fVMZXu!(TKS^W4@n)AAo;lG~JQ zVgv%)p@ZQo8W;I?f|+rrhnd`@2Vx`K;G1cIK9=^O*V1k{lKAtPlASp@8G0*->p)G= z$(tBVg7?q?_+_oSJ3c>>9V8te@Y(;pYDL=GuGzw_uiuXZU-S`|QN z{QjH%!{o1Tz0k@!L5)G=1wS?v1B7w>U<<@fp+Ld3ZWtV=Aim{YVSna!=P&U#$!6^1 zZ*em*`s`i%x~gibsDT_suscSm!v4tfWm3*_1b5hotJ>HP{3mW7buDdoM%Uh=Xa8Um zyWMHqB{+M)=z=2;Ml}QO&vX(IA~*Dk^QHF8(?5{rXfWNw7j84p ze=}+iw^PG84b;jma@sD%G7(6q=9wtgY#0ite50=2@wo5<@3vpwf1ODMT!ajoI{LfW z-oI?y%JW7&sZJ?N$8On?L)7V>wpZ-+6x$;Yr&CGsZ(Xvnr#mtP>&~;h19l@MQhu#> zrXt{mjSY1@+UkJRO7}zX9Ba1ZSEpLR@Ii-VVss_1VyFrm@#B7zj&zM{4~AFxNz%se zz5Ct`+wyp-N5p-8hgTz%7!%sR|MSO1B6$zReiTSHshRYIgoLOiV+BRP);|FJZL5;! zw&<0|K&!YIw-5@%_`=&?&-woPz0T6rFd^bSm}P31xAqpxf@l3fHPEn=o+B(-HZWr$ z*1WK>vEd8IeuXF;F@Ff)o4@GBr9@9b7M=;mkvg%?<(~}H@z7U(7?Im!38PZSn8MhC zMHLjbJI@`5Y;@{WWpOd2WNN&<2WQY!dV`J9&3S3n_tt6N*yan;kY*$`Z`#_vG%djV z6p{++_bOos3R7w*&V8anJPrW2kv5J&H84B8+Q?bCx|a6ckl$Jv#gL7N;Kbke_Zy+U zbAXjBUobp7`<=P7y}f2^Q?cSoR=D}W1Ko;Em0?Kf)pd23N|Un114>y<4w*lb+a!BeqLj}_YUQ! z(@UWcHc+&Sx0!+rCp>UF(0w*s<(8A3trEcnMFKrxG5mbtQqQN?@bf`Gl;z$_5KYzU zlK->v)uCcC$;!+Ou&JH58EEdpi5=1--iELEQkO!JnlfnB*Wcd$K~)2Ob0|f~y>|$b zUb%evCb9c&ib&Uhog~b(UpP}RxN!s*VzY&-NmkDHckkY*KRa^-KbP$xoza8lyDYV7 zUBH=?``ddBy>wPq$}vwh1WL-wt)S4Kl$3;ogNqymZg^4jbCh(^=LZC8~(1W#`_Q}AE&b9BV{P?~FJ+aMCd8y$@ zfNUQK&@5Q~c4eyeoi3kM@N&jBGdML&ALZM5bVg~Q9PboBh`qh&JM;Th)CB2+pJ{&8 z7aE&!6vUjCHwVJyGiQ_;{%M(utG+>8(1ZN_UmG7|u6G+d(CgO?gYn3qu|vXLURMGXH`=oDf*{mbzVL@SMFx15hG(K@ z5AAA19te!`&*Er88e{kwwS&!Y4Yn2wzKN2U1mqdb3z;AlNT*`DIBEc70c;4NVzfbz zV0eSpAWDMw5m#QDEbG0Uw+pdC2(qYNO0c^b={I?KytJypt2(;*`T%p2@P46};e2TS zv)iTRE6{qzvvwcjKNFiove(A^q0ElnyL*3gmKJg~+T__L#qg(=jsg z8-_drSL0i^c(%~oyO-ha`0+~KQ(|&*(q~@RjY?U#X>{>hrw7J19cp#qWq4cy!$!2K z60s!jSJg3-r_uMa1=R&v_^dC+TY9UUt8d;ZqFK^2jaCUL7owt~)G2f5A4v2_dY<@P z4NrbKlOEVEY9NEVne?l|dg09FB9BdFRu?11E#re^c<|YX!^(o@xrvDv!mZiaOxpR= zfC-Zwqb8Gk#lxRHJd5~iXO?U`va3CAa+#+0t}QYWwHjQG4i!jFVLh)KMk@wB?aY3) z7>)kxG2r+Q>1GWAVe6kz%E#+@Xl80{V&yXDDBL(zw^3_oxSvpRR(d z_ygIz?0E(p!ZI;KZaBw77k*0x7n>;U*_fwSMUZYT>Eq8-(&ply8cVnm(KL#~Z?SN! zDDRcemtQJE4C1Gx_}X}`e&_MZr}382#m~id;)n?TdxzT~<|29eO-)TOT|%Hs{oZlT z)AoES%WRkutSLEX&CqW9;pLKk_}jEU{#ekYxZ*AQoKqD01inG$k_>wDSTLM_il~PH zZ9Xbx6paNG_dLOe2JEFLszEAWf)nA^CYKsdxhWOlvoDj9@ToXgKZmex6ZCNR@52q} z?!6)feGs2L?j?4HpXBXjn)=@Iebf}FU_Srw7nNwdMiP}Q*X}xb;+hH1TByjD6%P}^g5GFiA zP!`R09w$|ZzQ#TGMh>5<9=+nlr=p{&^}~h z8S1K%o|heMMQ<}8o5Rp{Zgy!(vIlR@HPVxzG4=;F`O2S!ZU2GtNpK;5Khzi?^M0l? z;WHd$nKAIrt*2+%{@!8dngv{NxjLrpouB>+M99$XYy9+Q<0F51w@Y1PMJ4Hn z)~G#Srh9AmR@AH?t*@NUul}>ziDtvT6Ppp~8!yjfIQ{A^A{IE5hVSMY@_<90?yRIH zI1C`{+*ZF#L3V4Qeb`U-iDG8SljmjqGZ*`s`pIYu0_oeI42D~ldHl@xJam1;Lu@AZ zW4%n`IBj!4laXIJUlXZKnHg6d&EBE!jDsRtI)NkiPi(k;rJDPEmfmmd$d$_9RnvgJ z3+muFIa@iNrm*(p|NjsGf=V%3K3*t^9m<>u&052pvA5T*7mPoxpJf>;2p_LT137Io(++gW99>YvHThC z*@K~$Ye*Z#I*PP%_$2sea@QKkFlu-0DO`PGln02P7yI30SyvMU??pl8gV)&JM;}np z@@B7Pm)RF+|3><#(C?2kpL!fT+-HYwPCiQ@{Ve|ASpO`RN>r2m|DVnE=<{ zwB7Z1OGqfcT8nH0Oa=qkwY)iJubL*XtzOrin zCz?|dYMWG*e?nUsk1h?>~-A(@VYI&TisjnQH4f(lS^ffTS-&5jCdX74l8xrKV z>SwopBfa7`3xf>~v^yz-+N{notXvgYA)duy)=RnRu5I378>ihZ-mO6yPS#4s)$1<9 zojzCeGLJD#iOtIPf)4GGQ#c7*TCTi&$i}RPGwjzH_t<8B=7v3ljE@!qz4-5(Pa4I- z7ll}JTKqs@$&E<6bs8w~P`$x=3$ec08g zZa;usWSn}@A6fXFa(y$@SBd@v%<()6N$7V>?r}Pi;Igvtd-;JhM-)aThl)i0I$DISyiN+-xkz_OPcQK?j)N_? z?6)rHolMjywqusW99g9IlSCdr3r$f=R)$ND#?w@H={-A!eZTnEDPXKJ)p(d}{}r-? zU%{4*rXMbrV z6{qj-HLM*a0eG|o>ZBXBt42*ksn?LoS!cc{t}?9OtKO`v$*z=CR;?~_d<)3 zrw4@-9|M1$duEdaQ;i%dHxgG9>o}v18TZ;xw=^Eo z=DgialDjxCfAJvUQS)b~ERpITxg!C|I?c(76~>3hJ5LF$pyaR7$m+|t2(`tNsaoJA z=}QK{Y6Oej^@^%Q3_LClFbUj&S!F7tsby|2&5dFk&909`E>=WbE0U=O>|1bO4jwl& z1B*gKDdOqi^wHOS=_8{h^7BZ;9>GFK( zd@yLC-2P&P2K0*4m${-k&Akn(BTme`X)KFH3KPJN>IG~^xHYoDT3?bf*e!(TUvA_wi1_ zgs;6!T1Vd)(Ao8Uq9|)0zoshHzK~0uz#nE`sXRAZLnd$id;fNKx)eB}B@TUZ%8qh4 z#a9nny`wSd0cR!F(+B_KHa5xlNodGan3bJX1k(ViuyH@_R=GCtt@B3p1#P0dI3ep1=&DZ+ZUjQ`9R+y0V_Ey;p)$|q~Gk`L`C zT~0Ig83#5Oe=KxK&(3~sE7m~`<^RP*O>?i|JB5Ec!`$qbu*0^B9SJ}?g?`03td>^f zIUB(cMW^;0>{hpqQ^h$}Fg*9zBl!5w?vQD@*^z!p22x|f0)hO}zZ4;J+gyBSf#l9UQrB#9Q9ZKo_X?n6-*C7f|a&OxIxbGUhGZXODg?)aKkN zo5LZG=Lw9Oxr;Ecm>rz^zb%h;ub;{#5)X;WSz506L5(|`8d>$~yGq`D`b;7+e(Fe0 zd&XQ8AaY<{zh=E^A z+4q^+Cf+A>a{$z$&+d*nOKh{q!*)!>`4{a#Nb6S=Cl2)YK85R(wrhGKB7M(aSYgY{ z-&43f>G)t(l1tqqjw;el0&@v#$Ta0q@8f6y(41CM^$M;N+u z{wpQHz%Zxk7P>Bs@`{BrV5uZ7{S=OwsXOh6CUG2v&EmeoRBpI>IoRwWewn)s{WI<* zeHLeAnw+ua=XLOy2t)FLlg?~YEU&|a)%A((YD+?&aajED%&{P5IhXOlCeB168Tg^gnkWFeQ< z-)!nwgQc~1cgPitoB_Juj@BxG-{0_O!1nC+0wB*MbE&hw+pSl&I>E_Y4A|g7UH9&p z#_jd(tqstux6`*5w#VUGopF`UDzk^pESmJetbsm17c2Y0)Z&)BC-6%f9R8k>kpb&} z)iLI2PHGB(GP(4Z1F(a;s;7+V^O=E)l9&pjONN`ieF5=H+4d5#4VbEPxgTCUq@mx= zZrn2nPYv@I7}7^1JV?i4I6DH^x`nisxZo$BtvY6(r^4g6h<(=)xXthda5i z2XON})xxWL8yA_gIos6_6#h9SJDa?71X{iMtMP;q^>TQ_X?K{i5E?}=ZYZ}eaq(8t zw$RH7H$LXA*kUngXumMpq)rtP)OC6#G5IT6AMhqEtZrO9FXy&0H-hg?U7x-+nO>Fe zJnTpB|Baky11FQD*Q?_JTjL0JX8o=lS2XFVL$ZtnBPZb1HHO?wnnnUI{-usP&NBD? z9yR4i6$yoQuA4N}(B_*7FuVkNNn$ny>AIO5{d3|93hm)@?$x!$#SQA5n4#z|gI4mX zy}hKR@G@4pM>k;BWpmH6FEiS;Ygg_CoH$GD7?t!~4NhIgHYQj7$)D5Xl~hs38wr&MaqD>(r)VHr1vZhjp1S^x>3baob%aN6pq*D76u_D`!F+E>RGpaAl3dJ?2F`bthGHS~L8aQ*^6Nx%Nb=PE}`tL6B2YN5+dZtfd zJNU6{dI_yOf6^L6?(65g%XVR;%B4ATkKDN4jgj`W*O5L-91%RvoMNU^n3;j?iiBt5 z+d2`|{;93aYar52GU+*Nl!WLC;Unqou`~{4Q%U9dG5l(o44M*`#cBOMOH5Y%?nAos zgLmo*BfG?`E;?|p^c*FT=dswC-JYC>-DcwPp!F^h+T%seu0LdT1cM zOLOu@aQSeM1D)Kj#f>~rXy@OE+>iZ!Fv9+7wTf=t@G`J^r(QM6c}p@76pn;5MK+;q z*4~GFPSIO3H&k*}aVPt)A{n9uHT~f=tmhc2k*ht=y7QuEy!V^Sim!Z(JK1-{dS=yb z)v;<1Ny^!9%4aTDJ?u~`@Ie|TmLjs5GqCQHx|gS<(|ht`)VBr0za)Azswlza$y>8~ zE>`^m<)<6GEycoh!#uQ<$l^WjP_JceqEzT7@ofyJtrFuly{)XI76YdsrN{R$C9h7; z?=C(meWxETufK_N;j$*z{*oFRv;Oa!b%|fOPN+#te?@o2`q>02sw8lDAbEW_ld@u! zGE<|At8`b8kq1Nc&R=_8iAdEGL_!lxqiJpyxD73AsrM|=QN<``_$?&>T{X_molK$F z`*`BtHhSofiA(dIYWEq>m64DDXc4tIuVH*kZ05!xxVK5(9b{OXHP`94zR^oXYzJ(m zZ+j2eSEK@yjvn{ahy%V;7t<-eEvy3Woe#>%_Nu~m$nb3S+SGLxLdKR6#_OOiI;S9) z;!v@dl@;|ployDz=59Or^z+&efxvbw17N$L=d!Ue>d(%|{yv^Cx3OP1-;Dp(Rn_Fw zJjvuut&l0ZqsNb1mjF75Xjn;KN_u*a_)(^?IoY~buMCbQ946>@m7*AgegzuDOWn-K z|JRX(Lb^_5TAu5hTMeix(P5-`GrJ5K4l7D)F(%9Q1NiMAO~WM8Qi8rpV%g+{>cO=1 zbc`qGJyXWLW*>kHi77f_^^i7%yD<8c^#=J21a(G0PDjS1@Jzyt6GeGMIl`%K_==$+ z&?wCoG2^SK%v62?pN|w5icRs7WyF1#$|o`ePa4MS5gXg!7enPOnjw3ZP#xe>siB13 zH-5OJJo|xvNtUp{z4r)JS)odN2>rUb0ZaOR)LD_i#`~Xc!iP$0b1R{^7ON7Rj*(fw zlvM}$iZ~5{uopTn-#QzMunDu!01yy}O9I~kzp{qF4n=f&wpfV8TN3!99V&blC$u%Y zUN9V=Pqad4E|TjMnwTV=?&lZbEx?Emm0kA9bGzY@))(jKCWe0Y(A*`);P{H}hIYDb z!esW=AdUE@oJhRSuLjF21O@|PrXlD9ZB~|sNDSRH@$m9GmEO**t*e$6|GH}ulW)8T z-t(f=HN&$!GVZxulb%XmTnGBlLyh={a|-O_-j#c)DL``Q>_oHyo{vny{M=l;(!{_8 z98HZPe9@u#(=fLE4Mol#1_u6XC`ZRnld5GMCD`MJcM@92Go3qo>v2ipQsrmL6x0`r zL+8@$GPa7yh9o@c_LdWbfJnHw@c8A~+T6TT;<{0S-d$IN@f-cUw(DbbPrJCftf z)?_Yf7=I!WP*dJY=mv?;HM|lLcn}5j0dd**HW3$Tk8hS-5~^R9QXi`jS`H!hG*s7Gq=W9 zab_xJgxoy4d@u9Wt5*}GS(7J|+d#@U2W*lBsq2HuZ87;!i%}IXYjMElnd*C@F0NFs z*#-7}M4#L%SEcMlGI{pff##K>oF|`L*1gqkg>+*a@n;589MfH1kT!77z#(?i4=>ceWhz>BkwUX~`<=lZPH) zZ`~;JLg3V?>!r=u%-?05fW_^g8qqG`mfj8r3*sI>S2njU2>#d?dLUXAZv#j3HQc-p zmDnFB;|W5Hh!C`G$}#CL7N!|R?%bp&OweU}4`Ds6;&m21-8e1AfmC}1g9@#Kl0y<&k$RA78k=GMiOJpNTH{<^Hs%v z`O|41L|u$)o+VfsYO|O#wlC-^rUqio#q=QeaY3|iXQxj+)jQefy&K3} zD`hH=3lmD)+5_QM@tcl}UTKHEDLovX&8p*T4^)cazayo*3uqHrD|hLAYaFBr{4WP) zVggWTNP0*PuYzm@f>Mlx4#wH}5=3J-Y$w$=R3!wh)Q*fD4H0fFWi549&99lX|MrNN zI>?0}6V1xULgVE^cprw}$X}6x+VNikd9g$={MPBfG))8v)gMJHy;VD}$mld{+B@KI zoA-hkZ(CG(vIgwr5j7}VYP+h`D z{iC3Z8L$cs`W<1{7FD~i?Py=fxWEv&fAxvFu-)D_q~E^v3Xx^LLN7M6GG&7^SCYL5 z+b{QOZfh0-;)D6s^X0d%8B8KNr52-=BxsQ&a94-)sm{y3zCO?#W)wN1z~JrLj&zMrLVvdp*5FPcW50vVoqRO9&3U z{@XvT^+u0}SALGf)O+XIvx0FVV&;SXK9%~+N3TyT)`_uA#k7)^~!GNl{hF!1^Q+@-=&QY90 za(Lr6@x%p%WZ;Lvp!c-nM)wszP~#kbNG$_A$Z(Lfxmcs;X#TVlmzJJ?u#a)e)d2>A z0V};!d0uL{?08SY9?dR;v9zcWg)y&BoD+UW|7dpQcH23||LrmRncN?Xr`cTRZOLDVTehjPr2In3##FgMXR{-2b8lPB&f79ZtN~HFY~(71QN`4e7Shw^jLF zse@K_5{?&~JFgg|FS@<`c667}7)8zD@^m4(5`O?Xa%!k|e+EE~mU3eFaoSsNqfTWx zub);+3d>(JhS&1W6Qc|~r(AxW-}(_?>N68Z;_!O)C-SrKku5K+e}cv$h&1tH!W4>+7eQqIGV@ofJGyiiZ(^X}lYtQ1{n1XOUN(XpktarUK+`*M zhK`pzvWxD+&nJJ^H*vhhi0B28fO=({+9m1z+}s?nct|ZVH`ouPrKUa*%f5IDV;?v^ zDar)H@$tHZh-M0e@$haN98Lt(T0ipAwW8j)Zz*D1{4mYM#5en^HL1+dcG^ghVCk{% z&R=~Bx@KONc_bM_lRhntT8B500lo$K%jFJ({zG2X!mS85b36cAa=os4D*H%Q zmNoXEIo(VIQ8rpXZ~;}by4L^B+%&1^wu_dVI~H`_l5^CTyMXIW(GwE-o_9Y==gJD+ z7SyRd8fv(st?{?w$Q5Q7AS75OW;|)mK7N!=|L909^U))J4&RE{ zKSH}BZA5~KVdnj3iTayPCNG5eC-dGN)2eA#I7=6hr=OlWC?-?=PR6>_NHc95C7Kfx z6KDhDrXlSJVjU<)sJQaxxE4v7(M)O857O3#NdMb6Xfi~UcGJs;&TD6w%$o0t{aF)l zDVqBfX<5O$aXbIQ@AlF_qtHWopMF^vwguuGmIR=SGG(8)sh}O@aNPZ!BP{k-mRt4% z&xL=tsc05xu&=?kHkMl6cB(#_G%RH7^>fmI*56+eZC4Je*>OQi2P0jZ#VZXg&Vn>adrJGIeAx!&b?(irrZKlEKYM>i8x9{m!z0C{@NsCE z2g2Ww+*T~+54Vt)SE8EZSJQ%oHmgbd?w|A~J=kg)?wLb&G*<|Pv#Ib%VU8B+0UK)B zM6IbKQO{U?jIot4YohKn4%YaRxk-F9^+5$(hp%&JH8xLutQ{SXq*r91Jh7KT5metf zNv@`7=0bL>(=292FS?^td6NlJw6~@c%cqME3%(aAi7YHh3IrdR=jqkQFDaNC4EGd1 zkxa>D9n5KLqd9rDA5x88c8ra+!HJG?dx4oppb(sc82EKEy5fZRmjJ8XyS`~$WFV+A z8ShOU{=WKe~E}#S*4> z`*X?yFAk~^%i4J!Xa22lY4>6DZhy1N@ex0bXj<@|LX z=<-`#P`lv+^PIv9>*6)^kCW&?n>G;a8dRujCkapkle-(nY={HWRuz^!EL}H6WWx8l zw;5nTMxnH{=^-d%)ox@j6=q$vNUI?LE)ANMM7RLGUD;}Z1XLHGoe^~f60Wrf6}|NP zK8Xt4>|A>5I=k7gOG`R#oCZz##02gpMQ(fV@&IsYiqkd8IilPnwxuNjyunVlz2Q$%D@M76@ z7Xu@0ZQ$ltfszL1R&SIu(IQGvF6IyAF)*6Fau=E~4h3;gswl205xN>l(5-0zmQ91(>uOR#o*lNpuT#R zEOopUw+aX6DSZu(wwbf!d~(V-qn16_UaGh8pOTi7keJvQ9BhLlL3f*R5hQTe zmm6Zxg6P*^DW-p~g2mL*tX3XSJ;~&AEzZmpy`EsOVg}V(umjhOk`=BbPx^^2!80|U z@^T&}9#u_OV9l@mE`;>E-HZZXq`M5CCmWa^Otm4FI--MtYs;-$=O*bcP_qpp9_24Z zD_Y2%j{JuY)&E(hC@uetf=bFk)Y;tE>S$BHIc>kku)3YI1(p;v-vX2)=|B50CxX2{sSRrAdW#ETTctnABeFkSNHx$M(|33=_`;Ab}d621Qr?G1D|en;{4gZLn{ep=x0Kp?$lnq zfBEv|GnIWXdjye7k@93TG?rxB(D1&8hP3IutXdXeLCvtqr~CzS>w5K2Fiop;5nuUR00uZBDYJd?l*mqZY`8WL9aAC=%&Rn*`!-JP=?gB#y6OIIiByoo@ zNFYBP#HX+PZ4$<60RdyHw8Q?|46?Lh&H5);R0C78q0B z4h+bFuk^)>7nQ?}nZ+&Oe#g27sK^Rbfk8*mGh$0NP1Z`B*wlHF0bmcHgP?`B3mF|7 z>p*D|f^@t=oc|Fz%^ z%I=_mtyG=7|M3=a)W)%Bgk> z|Fa{d2mo1!QEAa0NHV91argpqmS{dQZV3xh>AM?95+e=O1GVa2r1$0}wA$zipu4|Yl1NrygL`vz z!Ak9*-v$EGc=TP^j0fMGUtlf?-E#C&Z-ZWNn$+*$8 zYVW42<-%GHXpr54@AAiYdRdWSORff9VovwB=l&r012c3LaM?izv|`0mj`Z)_qxfAItdb5x2D8uYC`(pVyf>K60l*8Wx&Z%# z=6353$lN{#X=#K3VAo`He`;i;udlDC*LFNE0?ObH!+Vm4GH`7*356bBQuQ#5T% zz$nr+TXRa^V3s`PH8-#pNvz5j_OK->MN z0h!fz=iwku%YqeVH(Yyg z>jB~xD$G(9Lb3wB`3LK^sAl4)Z{L){G*QQ?l+YbKX{eqrw<5w6DE#8q zTnyp|Oc`w?ZdT4YJ3a0}00z|9!eRpqo-#8su130UUQ9rK*gSu^11X&uE>iE)<8_Jy zpg)OV!iH?;pvY+;2*=MEY;{lv!LB^0_1&PW@0P=g7@a4_#>!BjR~TV{a)YyMlc8Ce3DSpbOp>s;(;bf;#sZ(HUU(V~ zvjo)(+Y<*!XW-XapuL@;49Gnx?D5Adp0&Fer$vCxF=c;SAWK0i8-J(oGUc5R%YDsh zwjyIaUCTs$<*ZTJ>^xd9%@K*);AOLMb9yB@MsgZg9+woFd|*CNdhtQ~o8eT`dV=iPA^dFmfB;eU5r ziPOF&7(|V|fPQOoeFUHdmDzIp4kHL}|pAh3*fe<7I zAX?Y0O2pLH)*kxn^q@J;w`gc-Z~tw}Z@s|I#RcXd#@s9;PT)SvAp4-$|9_Lj<_pkY z6tIAqh`Bky{QxEYNPSA`1D5H37@t08O=aaIFUFu_9JLsLk&(y&>HNuade8+258Rj2 zTM-G$MZnTZ2ux1j|GCu60{94OyDAGo2q@4Sfe;S-8)ulMqhlQyPpmoSY)%7i$u9~* zIT(YNXO2HuJb@GesYIFvtalW#-%r5=x;P%{(X;@x32|%e92}tY|G=kzQUC=YF))+? z)ffUOF#kTxZX!#;dIjiO(E||^f1TzxkH6IeN)#aG1LW9`|Hn#+jNdko)gtpjno#^8 z)gZtReS61q=%N4r$E&YjMFNFCKpOlnBM0@ws}k@WL`4Q|EJw!=f)-?7!JB;1eZt(T zRRQcL@n&F_3>xM<50Y@84J9;RI0K6EKZ+^Z<18`7@sc zv{r*5HrS+lF*erLE<4%=lT8v$T7aS_U8Q)+3N-e0vrX$1VIvj|;CYg!1bdn~vI5s8 z27!%9%0=&Vv^0|u-<=XJJWYXvqazt4oPpD;&E8rK1PkB;vn7KO7l?|=0MS}t{LexR zc83vQ-~hge+0)QaU=)!)`F<1VSag%8f7lB(5h<$DB zz>Nina>Y+#6$AsL?XfK8&s)DKA^M%+n3NjlV1uzQWx+@ z?eht>nqQZZ+DCoPCJ-?%0w{@Z4Lv~`F#$aBS&4E^8=s`#1}h(vk-S~6=3^u;so$IHCI=#4A+@C zFWv4->@T=a0d}tg64=Q6pa1$+A88o`q}+Dh)!bhLO}RT5pp}=q95t}dx*DDOqDn=` z7miuHQTrtIIx10i>$Mg36!0=6UFFG0+$P<;z(z!}p%SKq#!)Wu>f|U!>|gx_rZ;us zzaTBPD5-Q-DD%o@s9puLuAUtuSjuVeoemxTF^2m!P;*|NUkG*lZP z`3kisI)flJ0+1kwcrb$`Mc9ZUCr3%-Fj0|VgM=)anq`y7NX#`OeHGZyQ~yk$Qls;Pn~W_5t=NFzk590t?9ib42=QdBdC7 zh=O>KT*n^GD4WRmt5xqmRH+BYgGUY^WCYQI6`sDI8^?*J<0T*2Ay`@sdoaNU z(J|}6cLSov0HNH&tWFzvg5*B4zaW-V=j?xCgO#PosCemxux^z7IQuFr0^Ff#{6k=d zhl4JX9p}!(K(g!VkGS-q=8Yr)OF;XxK}XTJDH^YCv6XxYx&mM(eu6P6rAmOBvxy0) zq`(hCDHTp|Fve{shWyIFQ7A4mJG&?u7ZusCJF@xL!8!O!!~0w~UcLcFGh&5LTuFxr z#COI>O3-@HWmSLE546NN2oG7cPPFV`R{FhXNdeIXTgh#-(%?j+2cLj(ZIAI}CMDtD z&=c$PDGqP8WC|*((_Rn&|C;{lO8=4tpokg&scQo!~>7KLDMU! zs@z7D+6dgHv!L%WNgf=MB~2J?df=#{smTR@G5YDi83qj7iZ$Qg!$Agdy3$iQSv2H8 zU=REJnHUG%S3DQkP;5U}i5?R|QQHlGn#^vYpYKReOh%H(lnni!_At)zXPR#T56T;G zHjdC-J5mjwVx9`i25cH@OS4ngV3HwWco40EWq0#<6*q_$EM{EFgU9WREB#N^;$}F? znD?1~$~nORtSGJc5@3F26Z>y-?>GGcxErL$QIeDAAKX0TSs&c~Ik3)n3`KR@ojaWM zzny)sQiK|bAr|jx@wy_kVV9zbeHTq# z|9o4Uo1w;_>6|&y2PW76ud;rHoF{Vwkc(uWI^OY&SsVlCrJ3{tbzVnD2v{HlB&O3o zBLEg*KHw#_M!$=#t?ltaron7>Zth#nWKB9G;i!jBfs%7!hy0&EOoi2272v54{s)!n z%eKXR5MMqzI=VrsRH!|^yUU)`fxaAScHBTGa(0VB8<8OQ`MJ;4v`3OAE~tDkLZvU8 z{I7W}Pa<=J01npodSU6#;ePKJ$n35|f$l(s$rm_U;$vb|6Iy0$eDAN9?m@1EtlRBF z!qG#}??d=R0n3upNEg!N-Wl-vV8Gy>hHi^Z$vE>UKFAn-vL(U!bfe?O%Q|xGkIj}z`-Q(WCM1m8&k-1OKxQMNc}@}sOtLH;*y(@_uMZPiLTJ~NgeL;>@&jb9DHWo zHpDkUK<47lyW6h&^MxjdE^y47o2ihS`P|z}^HhU}u^GU<1K7F*)6D%tQosPI(al4I z2f!8hgD77B`s2PLPC-@WOl@7PrfuiwbJ!BrCk>LEPmVnvpT;=z2bB1_cY3hpsQeuV^vhAP4M%5WKgYnGTAAadO3(4M7u>rKLUVg( zNMAYIrEGf#v-P`Xevl{84|s`-j0OOh{V>?ltx5sR;vJQju}Bv~3Vyx^f5ZYMmxHi5 zLEF%>WcmZ6wUyOYGzh*1KN#W&BnT)WC%J&vX>+mZ-)amyJ1vop#NSLN%Y|UCRO)G7 zPg`KJ^vJRN>qZtfW%DIXW3NcWS}SVKF$*xPlv!c;=5^$TY^x%%aACz-;X{xdKYN@R z|LehHwr@u2cGkns ziJzH{7KlGy340H0(-c=PI}8D<18gk0t6MgJK(i(64)KnztjSfB2T6^%g*jK21r;;H9$n@pDDq(%;eq6W@WOk}7wLCZf%Z1eOHa6P4Kx^p%t)MR6 z@jv{tk2@_y52P;w>}SRC5=az*46m4*-hfgD$cocg(n0t&UU>S+KcKh)234n@{N%1R zvrivKe_R@t?-{S@UmyQCSC#hv^&W)nlWhwNYr#+Ha8o zy&RO{kHo-{>YVP5CCTGx7RJqdfoC9y2B*Hxict$uCM{ioIf$ocm z`Za(w@n`;MusII5&H$R9WVTTdAkrNdi3>8Lf!BFP&KpSFoVrwS{K4pJhLSsX0$2o? zuzoh~nbwu=2vfG9AQm~Py@>y^mursF3?#)nCl~ug1>{oI2CIxhoE zGMnLoqine!qvm+ORaiEUAweZ45e(FUe-!cqe5kAVSL@!GT90pu-0_+S2CN+`&9fr| z%FLB-+#0$o&_X|Sq-Roc?3|WHM{z3T7lBRn2WKee3LuLoGBU{nfTkALFa*eV9)M2u zV1s9ev!em@Z`;j1sNP`J(5$lo$M&CnZFx=y|}20-NUP_ z1>t$m#XEZpfEEwu;6i873mK5ND(n3KA;6+D=B!X|??aj~XrzhOeVN(78FVb2V1Kmx z0P>N4@jv6CvKF9{U4+L_T^r^>v(4-@s{`odz-Z)Q_E7fvS(`I6en+?$ZZ_VPZyHmM zXR%I2$L&Hfn0kO zpqH@XOUn}9IRzY`N}o8e8L84L^WFO&*=EJ3-QorI*;7`vJODi(oZK|%GX>ttRrAj} z9aI|hmrIR}B+EF_YtKz%;*5P@yn1zZ?@tgbO*QW0VORb=2$VaT8%Rc&74agczI$Ji zy3Ne!c6P;VQsy^Mn1UF#Z>M@Z>7Ut09AQjzpuh#L0wRZq0n;M&NRv3qg;u3Xb?gqY zakhp;6ECU&V8T;g2Wm6TWK6eNSKtEBB>n9|D0(k>aYRYgGvU9Msjc6AqGiPqxMtJ6 zFzPD${$eTO@Z2oVA_dpB*$}7&%w10fg@uP}6D$F=o0{6fX&2-HJdmq;fNB?*F~`_0 zS^z0mYeRt`1ZbwUzITG(0k{?D_TeO}f(y(%5J2P}MWQ|(A6z>Gm%8jSSbaOZ1$YHg z`+jn?bvug%_)omt=2e?}jEuFhvHneIbi9*l*;3}VYiJKyGAwq@m<(?4_ua3A6&!L? zQn?Q%Zj{G2;#&3?eVabEe`ELL&E^uSF=Ab21D z4UCSUW$~@5#BKqI2F*1!KF#icj85(rI2tz)&$HDKNIuNU$CTDf#b}f40NJ6t{7u0B zq(AfeX&~JKfS9KrK&R$^EvIq-KAeFNiVVY$k-6zOm;YnxakGhaLrJr z_;oFTC-EX@1*{3QgoaPN_FktO13G+|3Ad-%y5K8dGD+6$anx^HWEwF=3%+_O&{Gc} zjIe%Fj;{N?_47NBFSyY7I-F+}VyH$t0ylsn1D-o(`b*d#T5u0A1~G*IwIy)AK>!)D z;?)cgZIReO5*cxEai2MglxAR6h>JxP;NasoV0D1miOq+>5U}BdfyZ43E66}IOO|4f zT16nZpY}eAlm)i2`hD0S__uYf>g$5`E-oM0SWqFF(~~=aYqTTWF$e?qNQXX*TXV&I zXMn`drhW+wkH{ zf4PQaMxWU?4XPt5#gjdZB|YMwthD|Sc**}@3X~_w{8`lDkT$wLX3m5tw*Kdpn?F?d z$YZ4L3POpt(^_ppmLrFaG?q`NqLMcoT#z??2*f{ne{k4Y=72R5-TWjm<}p&`0o(BfCT1Ud6lO!2wC>1? z#-2d<>$8zu$}#mitt0!cn7}TtDv2e;3-3p7*iJE6o!oIWc{=YutzECdm8UsG~_jd^_u8w|{h*+;3`LwMsIF zT4zZXqVc_&WJjMAj9a{RBF%G&vZ&H-96LI^)Ijgtpf0KNLr%YfsANHoPf3T+C?PkP}$k+Qu^IJ z!IgmG@=BZ5!Iz^-hgfgR?D4y}Q^IgqRS;=MDb5LG6|7qg%dwW~A<;I5D%Kh3cgu$wb5SllATJrOcV$h5d ze#L#1%q<|V?u`J$JJ11g0dBU+^e@2w@-Hs80ZqGVI&&SqD+7^bk1Q?7en?ebXNulC zft4M)%KRA_6<7j4HQZ|Dkx9kV=NSe<9Uni#SSiZKY&DT)dAb< zpB>6+w>=w}IkFe{I%3cuP*T?U(KhOASK~P`75eh8(-ygbee_X+#D&Zxf$a5|FU(~4y z(Y}anQ$zVYo};^@W|XGfO5tXG7qYZ8T3(a9{o83XR*{JEeC~JS7&4LMaap;<#}r%Y zwL)B!j<7{?g{P?sMKH9Etp<>xFG08ifs|fU%*Ave1BbC|F=Bs zONzrcUBBO37cq`8a-Y>SQGMhq{4`>|9S8N8*ftA_@}oA#y1gHWDr0S#Xlvjtg1!8_ z#w_gYIse!*E68Wf|5K*r!#Uw4clHx!gl=z@b~;;PN1*)rot371X)2Y=8yQ`qN8c-X zYAmqYzUqEeJJBQ8M?$XDJ^a@uc{pOLyh`b~UY0ZhlKtg5Lw1~No8TtQ+3Odd0rSGs ztBen3&QXje7R7bu(Ws~{Rqbg}f3bU3pkBPqwxzfs!r50*WIN)IHZ+DrDt$kQ6IfAJ zF%)sPH8t*+=@e*MxtDl8 z%Z{fEt7KWs7SZcw0U5Z!%~mPR`J~prd{+TkvPFORuRLR(&_BZz7H2Yaud=^}Z6urT zpbM1-TH)BT#?~+5N~gCu_ZszZ^S`^O(2iDYT$!p@pf`SNAhd*3$I@@mY6{4C?P z$*+qq!r3K}g`d9gt)g=GRc)dW$)ffloV6e2P<9hA{z^czXRihscW~cU2<2}2^oze> zYB+_1PSR1;vmgP+hT+A}RO0kamM9_cE5nN^lb4NxwhM3gJo@VOaYJwF)ZUzbA&JS+ z%MHUpZQc9cJDh-T^E}G#!-ir%eniGk52ffQ!-3VIdwMVOhoHu@suGQSAGb=;9qe<< zDtb)v8jvealo{V$(eAXl3k0ZjSGqZe4p{djKPA-Xn^twHv(c=ooQ{+WO+~q)&RY9% zn@?u^=}ncF2rE{(UFN2(1Dg$zn}B)#hw~S%xp{7GdpvfHL7*_ucs zaOF$oPCoiJ?(v=$Ed#v407JGJYk|Zp9P<*9=?1}P4voR@?+m$03Xr}n{c3q6Ik(3L zzD#c=7hG22s?kwY2uZ2LvA(uTUtDZAl4YvuUvAWslvM9Ux8EB9R2{zMc>T-#abrqx z)LU1C6x;hTNQj+|?Osz^mznyg_il4Al^KW2+i^#lMH)040V9||;CFg?9~PQ8u?~{d zeTEuYayU)%+Fb-{bG5T+R*dufz_`lvN5+2~C85ByiWLgV7@E74doH3wV0@A|{Q*Xk z%NPz_xBPwFfHP(NqEKATa-&8ZkqSZptB4v$>;F9fta-Q%# zhr3{v!D-80^%u>Ll_A9B(XKQV*Zvm+@=~uXFOKnt(mJ4Bw}z7CnH>rnMStl2wgQ1+ zp<3a>{dT-=crGOyN1g)%dEZ`cAW>f-cpir*7imJ$a=yxu0@HK*4G$i*Y#mgtd;Qh> z&CoesdhtVcysj8!5*Z{$w{xx*#k4;h)Si%~dpZpgU);XFi;K-}g~N6CLj(i#$yAl; zx!m0C)31EY_ZJI-KB#wOtrYg{kFS?d7Q@euFMhfs;aOmRTf8;8*A(LW+TldwhcLT9 znbb>z4c6Q~{zO21hnM3+-9zw;ME^65z!KhI_jy-L5lJbbi?GMR>D;~4N3E>z+~*4l zAN8@nLB8Up(KNsEvcMaO&-*D~5zFC-V>ItQ%jwDy9E?#{3t8wnQ;IRjHxk+N{znE5 zOBh$KcO4q0;JD^syhlsjN?9DNPhKCgU_bD~ul=aq^+Zm2H57WXuyUz}ca>BJjYD|KuI-Z-9$mShZ6A!f_^i@>ohD z#^II$=TQp!6909R{UhnXFN22jM?c(GE{xq1VpbX_dIUn4@5m**2?7~gDaIEf^~8~+ zx($p5KD=Fioj2Wk`gSGCNKPyk!)Qy0^)x3O{)dl#^GxDyr(W$ZUPl0_QO`&?=v_w0 zBFbKyaKwhDBDNZ=__%pP`%(RF-Bo?iC#b$UPnbFqdY`Xmyl`A(Ruzm=*dOer&+(@g zSc<9AbKLu)SOmY|ruKqXJ^pjeIcnb*7=5TbrY+Jhaa*cgzM4YW<8aX7AK#iduoiFT zN>&!5F!V#eWO|1=OVfS!^jRa##*2$o|K7rho~^aS7l@v7 zlOyF%{$Xtg17@!o9uGYE|E=KY6aF;kcQ}EN6DEs^9#@({_HddTvUt&;7NOnw*vy*V zKzx~s<+|XG(CeA-oPq1=zJ9cg0n$UMw;n6xqg<m=-`(3hVFy|Cw}=&QvpfubiAZdPaQk^ zBMi5d)`h&#yvo&ahrFqdn#RUUxH7sJqyY9U8T_ z<;$?&PQ0A$Xe~nLQ=2gxYDJUMqG88p%Tc#OC$Xf41V8bx#IW_kS46tbSFmTIXz=}M z6mkP4BHb|G8b!ai4_5o?-Wg^~D2~<|hu==QxfC=^=(97jrL)Wk6}4}}l|sWExEbtP zTP!wy5fKI~F_KF`aBTN`6hk3&e}ZhR>NcdeK4f0x_Xv`Y+XQH796lG1nzwpKb-j*u z{IREzWQT~v-6+F2yX&qGZ~cRa1I*0?!}RVseCR|^WNT72X)aiAuuL5{ix;Y}&xa=N zaJa<7r7^{k7S88I&V@wI6+=@Gs~iev`w#>ggm0HZ=11Tc)W2QvVWfqQSq<1U7s*%s z%@kM&R)rT!6X9I@2UHX*Q@wAye*3m;)Et&Ii}1ELIIkJrcS1{8S{TL%r@WK}xClI7 zrpli&O`#lBaf$n-uHxM;EZy{TeT&T1Px0#TJLqt~2M&!rz*F|vsiL_(j6Z0(JzIFE z0}?BCE)xPJO^5G;C-^^dnYj*%b@{I7Hg>J~i?vQzpL_WYwZhXEuKE?ayh?rM-Kl@C zA`D)wTw0vVD4iC&OPsD*h_dI;K4!AHKM{%Iedf6Sp<9ltlR)$gIf%{AIX#?60z7CKwqy`r(C%;uK3s^(MFAe@p zWq8xu!~8c{QyYTev*YbCtGVr-dYI&$zca}<$FHS+L-Nvm<=GtaehWqvaxVV)B1X>O z`Y}tjlSDg>jM8Vli=O!9W@v?AdyHjn?>gg8twVe?K9>mk91r9dFBBOk|K+I<1j0W0 zhwTiW@5>9~3bgvVZU|C>Bcf7C5xcyqnq=sYwdmSBeuOJf z_x_@gmq+}@F6FOH47bixdz0&lHpT6C#&nl5ui8 z(Ssm43T?5;By;=#&JJ*UjP0M64eU-ck0-r7PSjMB&j*C`r{8p*k)fiUi$O+^CuC;n zLZ5j|_$Tog^Lu*nm(en=Pd{jR*B1L5>emI=wK3%A<64KI|Ff5Q2Sw zg2oQKHwd#c@^=-3=ixxu+-eh?sc#*k8_@j;E{N2TWK2pP=jpSgB? zhcrPIRMxkNai5hS6vDBPAVM#8CMvirv>a7jP?E1G*cwxk8^pfmNeG_u{x?K|c!@OU zHy*xktBcfXWDW}r)oFYv;v)TM@WNXPw-)r7szgs=9^gXP#ht-=95>9>h0_;0H7G?c zV6#mX{*37BYnt9|wi`bys?RDv%B{{F4_;60ND)l_182=2r|2=YkCX4gw9Ib2AFMV6EsW7c* zGQs&#&*Q=bA}7W}@pYg~JAnTJ`t{j-HDg=VR~M z_loW8W_~**cb0z*eBYP!Z_427-QP)Cx8HANFSS`bx7nsAwQ8NpzFX>AKG?S3Is}z$ z9qT4TZjzs;^li?ik)dI6lTVTQJRjA;)slDE%6UG+(9)=m?>WWhGCnuk{e@hSMFwFW z>NDOanMP+-VK+;@N@~W~%M|cf=b(X_L-)4S-wl;G`3K#;L|u>Xp8K9loF5FWCR;xH z7*K3x3acYSMmhn zVL=OpTG!ek*aQj3_*NZd3n7y>bb8%6$17=M)H>nwwgtarSwh^fd3h5U%T=IHV)XH< zQHJw>@@X^mLxgeL;bd=;c3#6MUJ}2D4w&eXQOR*b4HQyk1Y=~vRjW)F8c)-Y{ZKVU zy@-PX{#nCepiVj>;`|lwL=MWplM~_5>w`fBi70)%_d=s8H9PgBM*RxnLCiUlFZzB~ z?Qqlh63yxdzcmb;2!BTe3pd3)X5C%H;D|SdME18b-F|xwUl?2#R%9fI?{ihFVaNn< z26)zK`T~Eefr~Hz%7=23=$?DnVh3Rb$`nOtM+{8rNI|mD_PF8Xb_}A@OoVi1QG&A;Ct1ioD~4jf!Zyc4N3&y|!l1Pxc5n2@+8R@DwvnL(5T&JK`X1t>%i1+m?gtQ$8B1ReoUIq)7CTvI6 z31g(AR$u{7`%__Jv~(P&mQ#t`^qz)^<+l4F{vu%^!Q9o%*o)Kr5wtw-WCEK+|Q z5&dh5egp*(itlbyHL`n%R5`02;pJ%scNb18OoT9+dQqf=8saB2-6%yM6Px>D|F8kp z0;DuFS$ajLmDWOUS9|;fCM|vMvK?VzaTnKKkB@AAF;4eXeD|$zu;pz*#AwQ#<5y&?eC1kH8 z)w&%8PekeIWEngTp^dgW;g`Ri=Q^oupm_0zE2m$UZ&exjyx_3W@t{pQsJ3oM_gd6b zS|u_H-I}`ZHb`ufx=Yuvru|*h9xv_qF-DQBm{d;1^FM9axENF?M2>(fKZH|;uOt7YEIlQ=9mm|!^6Q;7FV8o1TyL&MbWS1`hNg}0%Zn@=K@0^V~(kgAaP3;vd8s;Y2 zF_6i&?E2gu7}@ewN*ZVHB}Tl$=m2ZON%xG~&*yTlWnKqcSDT$*s+M)T&RQxqLqrh} z4-=|3@0-lHU^hyMYjV>%*G^U}zcT`x=j|uoE4p@J5YYmzDXJK!L`(7{qwbiRs7=%#AR1@2XHCHxF4uDUkN!8sOX zWbJcC3aDgzqFuVywQhymjEfz7Rx@85N8!(!p_A&?3GN3jNgGF7r0g({2T`k`^+DOcHy%V?=INQSKAm?k}EITIsT+0 z-VlF}ADw31t#!+aJ7x|0UCr^B>!eN9h|u1!B;+XYj{v!I!8ROK^ zwc|v*aYm`ePm@+o*%iSF&y#ETqHCIqMp3NF&d@R9a?&PW*MK>}6f(sEdL7u<8X_(R zf7nRU(7{(1*ey(-xamg|RWGh&TD%lLfCdwC(x-#mihEG&Qf7}=9DgLycyIN?h=gnw zjVn(BHNi3aEoKvkl^xmbDV5!VD)GrRIPF$>GrCKHpPGd!S`ALF6aKF0X-YSt#D$wz zGw|T-{z99UEp!x}{tn7+n%|)w?RUSO2W;I8OufZIq|-8FYoh&VIE@z~ zCstP<&)cws5$@XfX!LqN*g9a-FdKhwI2eNvuZyiNE?5^g7M!87MB{0e8mb5NNvnh78mmegh6C1JPe6KUzi94>k_=AsW$z24GT zVagsaSmWyv8ay7+j67cOrEbI?N4g5wqowWTnukex2Q=zP1{!I^jMurKS3S;nURey^ zZ<%vP;_QMB(A&8F*k*Zrn6oprXX1q%imb~)jJ4(J_{|R{7;xzL>G71k8At4jpfO## z>?FRNWLb55n{Dr{^;Cdb>;vA_cW(^Q^Vg73d>xuN?=>mtpJJR?lneBb?n#m&En` zFt;_q_4g%VY_&dmI931qT-#fHJ(&vdF{&POcA;0kE$AZdrq-Al zVJ_(6n6pOFoq^w!*vG1z&D@L{bh#Tz-<717key5Ny{S1N@0;dzqTN5SHYES}D4r14&+&(9 zF?bJ<45*<0<*|8gMGeGBi5rb>uiK+W2IvN@ZUnbIbWM!mI|KT-i?e>_|AOrv)H#}8^+8Sa-Kol9pwClk12vkJ=*I1kVUPT$Y(tJcBBEAlQo}B=}+$G zNDIBEv!*V@(b$t2iY2ts%{SoVqlJal@?lhk{vi4q1~-=AY(XuKoYCYPR;)6fcm-;gEUWHx~RSLoU08d6$BE6&Xd2>n);cbD@z*@ect!u8DEGC#U4|X z&^u?rd!OAd$ReJy`R9e9cd9jOzuv?D`K>pU&Zx1hg2v^(Q1h-+8q;^y&lE#{389i| z+`PfIkBdZq*;Q7Vj0k?_ukJucBEiO<)q#9pq?Jk6?ppQL{ z65^u(heoH&^$Gu;lo7X%-3Pi=0onu z<08N^aK`0%Z^f_1VR~+#)$k}AuhBt{DYP*x84b0s`Ape`Z&Gvtml%7<;u$ZtT!@hr zXS^2W$Vl^rDLB=TPwG3T2gjcJu9(%$us#093HpX8C-SgBaO+LU&3(bfcD52ocxh%z z=1b*0K7c!$zaZBVG|JXEAw3O08~}Ab@lk228f&3;_uXyt4c3q7o%MC>+N_HJ41OKTvkpvjf|3-wMJjHX1-QpFcnC?IDHu zoYLH1OWd0?@bpHuTW^XaE$o?c6zHTp>EmCPwEAh|jq*JSv6F?1R12>jw{*JAJP*JB z=V@>zBu=G(uw!WBQw)d)1Wt zdpS-1K9)m6GHTKB1ZE#Opiw11E9sIWssyJKgjPk|ZNqj%vP> zcb}ksZy~S3R8^Aup&q+dZnu@7>)4@6Yb-W2WYB1(t%4g3EG*);*4LAC%oHRvUg$PS z&hPY3{f@h1uRIUxCgw>zPfX$tWOCp&E!{1~xx5WB4$R)b6+p~z8hvkGMecR{%>~0%Z48 z-{Z^6(Cd9M=Z%l)Le9m4O=I!m&Nxs)b>T>{1?f$x-9TsC?7rQaAP6R!7_A7as3~8I zFJ+xE9l1;3Fcv*V5_5Q~YZ1$%Z&skcdq<#c)_=Ix@!vnH1H_PnldB*xhrhcU}K&>gLBNws(x;%x%PL5&x~&q7O{jkLNHF~WEgce z>BnaRmP4xzXnCWlcw{xThc10Si>RR9PId`={$_lQul{5p9IE@0*L3KWRVBAyly-vL zCtMrBW?GAL@jR7DVJa-F%WBWUMpDOrWhaLef$Kvzd_!F|`Khc|%kaaU2}Nd%7*emZ z&~;+BAKYJI3RpM7e_oFct+%=rRaxv0Sz8=XPm<9JJr0Xm!67cwI<|bpJNO!6 zO(ar8$VzuG?b&XMTh@u#>jzy&nu|Uo)R) z!2R4U?x|IG20o*%{#1hXflc9hqm0I>j_SYuc74+u)Xqwcq2U6zJ;p`G2zxR<BlwvnMi)^F zpB{q`Qc2699UODJN~vwq`OwF-KeDG`I%#JWJ$EK4L+^P-;MIJpm9Iee;aPvl`!MazsUNYm^BA5|=*}RTV?1RH_?}}!L*4*$53Vi-i)bNQS z$+P0tj5mr)rcqdQ53w+(Hf<@3x;OglPg|dEBdH{HM~t}NM_k7`ALj*A$qbs#DrH8{ zT8I#A4MwRyj-tcy#5|YyVRu77$;CO0qH*Pg&pv;5CW^p~l-kx}x1Fzg+&ppPUn23e z2uv|g4+BD~#dong=ZfEv-HJv)RQ*;{A+n`ru_>e@z$ZbM{&uLkJmt01 zmV^7cSs0%L?>@S8TTMY~E52rDV)K@L<}DI5hSrxyl0g80&RA?VZHIny7xVNne*wJe zneTs`!dSVvN4^$&4hyUDH0PWCph0~E*UWuJzHW(cPO(Y`|M>9^|8AgBL(KuNStnuc z0YX5R{q;=}(?elKn};c;0$vs+1!$>v?AD|#zL&Mxt5w||>yCPsbeP1rZzzipc0IC*JNGrjp!ptSM^qBNx&gVnRoN9n_a8>4 z9_7P3-~&K_AJB)nYCU*@l7IY(Q{+Yy;(&;V^GoUTq-3anz1O~pDk_D+@ZX9-srn}y zDVBHdPXo(KMsBYB+TL~lPG%8wDcVEw1MGxKneBdXRwAJ?R_dg?t`GZZSwfBi@1Tq0>w|UgFCh>LU zYy<7v0@^W+&xjou@gLMi1*Edu`*ElF_C1VHe}v^b% zBI;AK0Rl@V+*^DugYHM@jqR_d(d?A|Z|p?-u4YHk#HF{3$PP&$3N}-qj@?)lX+w(V zr7J*ey9GIlUbh~8;xE*;b}t~q{_p^@L&J>bImn1 z;}Zpf;)nBw@%d5lR8gqYO9==JLypWRBe?mlN zf}%+^7}xmwCz|Gr9XEY|#K3Q^50=SQsl;pkk~jKl5In>PS!jIa+N9C@(w=Wv6e0F| zj5v9JA`?-JB$GkoB0)zHC*cda<=#P}qHIftaUa1zOgX)y^GDU3aY=i~{1G#_cHG?m zv$iIkyCM8dt^ra@z=h|ae@m$H&I+03Pn)O6Bk}ZiS&^HE*;TiB?mLovLEix)+{)K& zd#mA?;9cpW_dW=!_^aNRliwcYYGIuz$;tBdp$wXu2x61mJy`tyF>`^BK0JV2a5+c= zYoeEmZkUWc)jmde$-D=@+(Tf9@ps`n1mSc_Bv9EnNM6mB8zFzsijDZu=DUsxM{Gy3 ztmr1#2csoo5YT1|KW*6-%~~TIaNP*}gdCU>L~Od!IFMnPA1_Rjt?rktE{Y21*H5_j zD1GKoRzbTpi!t~zAGZcSKj?6YB~CWS426u8l9hGtCExedY03vxLp4OLrZ!j9_T*Wc z!$WE53@Bvy%((eClg?cwrkmJFlc+EUk^EK(*A8tp8Qg|CE=FQ0aWYz~TewZJ`MrFS zrGIQ>-$pG-y9idAC#V%ad%e8F{T|Z~EefZ6?q-s7Is1%2$WTEWvc|<#&$+YUDV&p6 z`uIJrAKKu)xX^%yHtD+F>czAt`H$~UE}jK-P5(+v4f!+r&0^kbqwzFbF;HA+$jA8DF?5m^~H_nwG4dMIMiqpcz%&Lfgg!T5#jVBL*p{oe<=;Cr6L zmAF_un%nc$V}qx-g(|nyi#1@@|2TDU?-9L;9IzfRaLM`+1Sn4sKPBOSDBj9{>4h ziwEn!2>K1r%qs_KB6NpON49&I-EYjq@rW9z)BQ|a{(Lc43Y}=F!}EXhAFe%)$FMrz z?u3YDER|eI0qNO6)vzWWx_A0e&jZvS0jxRoH_?%MJ?w3~jj~2$KC4)R-9$Hc5Y1=5 z5syD8L~6mA1DlM!7Ac>VHs9J8a(V>|bkO_V68AN2CC8;jPp{=_3BweZHu7~$YN5?D zZ57X$4z492OiX#=xAWFe^1+f+yMPaM{LQGr@x*!sg9X)KIklYA&qJ zbL#0yAiv%QyuTsmqLze;IQ|z-IzSOE^{lkjG!==mSOGKF-LI_{zetDh%Y%XwY6u94 zN}3#QI3QHWv{fUzm(0G6iI|LUqFT55mofN*reEy9GXOI{HeMp^TX5PADG@a?X!t`oo4<{e z+p_luo#g`(MlV8{pG!{q_k|~f2|1^tYrGdL-u0;0JCGYh&pkWh=-QzyJKjuZ zdAoV#zejzRU)Ms2>*n5DK8q6mM3lnM&2j5N?OIDa_F2lqfBr!Xc&yMlSot!XR(Ru# z&-k`|2!~>d$Xp4f^bQ)>Ff+!4WaiWOJoTAx@8W*;z0!IZNWQdcAIIQYd0)!DoU2dK zP{4O|q>>h!02_@R=|RiGtU_4%2$1`?=x^Ld5f7m7Gx0+;k;Z;U+l!Wma@WI0_K=jr zzd0(40k5s{C7ow=XNrrg4EU3Nebj$c4j5Oo&Qba-WaK11RDzr zir?qY_hoEIQbfIfukSA{FB$rdR8>{ozI{7I*hM|tKnYwfw6wIGoSX~{rd(t$8JwUkgo?L<{T?ej}SjX=jnRTBUBMwLLb zcAts-5pvcgFykNTW%51tD)u3qmA_{W-E$YAhZfsMm}zh+hehhYP~XzAo*SBsJRB>jK5u1Z<_u%O4 zP*Z6&&1`9Ei5S*6S;7(Mq+RxCFe?4={)T^gt>)<3`giVF)^^2V-aWP)R$_3XZJH?^ z`2qYhnNU?UKiRAdX89;o8FYz!O$z^9lK;RX54By~ullLD1%3vKB6HJ6UmA&D4u9Tf zz4N6sQf)3Een0f9F}LG$oXGCI^Gp9NupK9nAwchRbaHY}t=22%*Pl)>?%)1h^`gng zyMz!Y6YO{LgONMv5+of87S9?*{m^_aCdz2B(IuD}Fk>_I^v31;YUbMjSfO6Y_HcFZ z=?6uY-*IM+-5l=bOc#ZCXVDBZleQ7$JX=>PLc7KtL#DcSI5fg%g%y+iKdZJZG)kXc z(azyIUG)_2UqzYfd~h{qGMs49`Ov{qHyf97@IqkY>d^80XSb?@qo$6|B)8DA3sz)j z7Ksg#UsUwM%1RX-;0f^jaXG#LVxe@b2z#x({6X449?2X`1bIRuEiJfM8%tOJ_4-Sj zWrHIDs7~^PBSxhg&r~z2yyLXt1M5kghb>%Tz;PJzeFuYoSnQxu=b;8!g#YKLXJgLt$JVHRg zzo5yZW;7QdO#mN{EpNRB>n?JjUUykTXfyU5v8W)w!OM&)o26Fti}fdpG)8aPP^fYyhS)yeNuz*3ffvbC%oR=|auq z<-7TC$r^Vtu3WJ7RYS@DbHS}m1BauCh1hxQF|l`-^8vx?%LoRJ4p*|u1i8)!2hHPH zI^%+yCCC0cgZ3MG1IOWBoxaoQ0Lz4D8P)s)o?Umhd=7SLNr`s%NynT+EIh>9@7MNE zvdsp^XyoPPTj##eqOr2kWpkC4|9g@WST$E4Fp(=xiYMQ4zLHLXVb_QAzOY zy9f}#6;qrqfIJW#O_W1rEu#kRR86vfh6!7IZ;$4H{n9ss(Byh>_2^ts3;hR#+g*1O zLcoYTk>GeA$kvJrKO11U>U%(FKRxx#c`okl-Jo9dhy)jmgp&GlNj3q*(Jg$$QZ=@= zwg&%_E(!k5LnM{<&Z!ov&5IYMYNNT7Gb6smZ%oO;Ix=II!~A{TA|_=|-BWd>7YNb+ zV`P+V5wVk>eh8L*bp{+ozY4KkfuzTu6i4A&^vg5cE2RV*jH}ex%y)oBr9GJUfR$CA zr4_2uvGs8H26|nKiK@*wj(>1OM5An*&%Nhcb@Ou@fNI?wEmi<0bXruxU~zOhf;U&> zHP8Px1{Ykh`uRJW`_nStUJMwI%WRPI{JSo;fUA6ZwPACWcK4NeSEphZE>Q`stGQb( zbLbeey|na>F$Oamn+9veuTMOd@(K!ZbW;`;cg1`RqV!e3{&2n>C+E2@5J5ONwCU@+ zm-iv0>lL7jW-_N<|G_z&fq?;6SRnb~|81n@*}v7+3g>uDZMdElXynDx0ed}F*hQXj zCPJK6)xhBM$lWI9P$uQ%8rg`u*AMEs-@vj)_r8Yi+mZD{C#t+3;r6rys$ZUx_msk>P=lUd|UV#l>KnUf5+`d9r?V7H9SV3|9%TnHjF0#vh(tgeG(BG#8t^ z<87Mp4Ob>5TyC7XK@rZ#!Xj7DK}bxTMZ2k8q*biVHa`wFy>ii--93t>V~=HIScr7) z5)&^Rdd4LJ*O{1zDAtMnePpB#YbAI!DpKc-M8h3_d4`_Q&`>a`kvD}~m|(#fDtNT& zT$`WIoFi{Sj7~^N`8s?LQx^S73&{~tzD;@#>~*uU^U@4VFxF0qkIyCf_x?Rzjw%mc z46HvAR}3|_#xasEUAE$Xy}gPU)&t$$>bO<@lT?VhhDNDc51|J9q=cRv%~gC@c9P-g zniOh?gK!G2=LPdq8kK@`A)+~XrP{VRRp1c4@$)Y-BtXYt<6)`!jUl1x&@z)B`1tz0 zz(m$Wt%Hb*!E*%jIS!WH{Bu<`H8m}*98G+*yx{^(dCt$l`LVAJ;Tf~AsIApjmy}#P zf`_L6^=PcGHz@dpA8Ns_>H1SBf{l|?r@a`R*qJrQ^#i@GmX=F+;l2#|)i#D2384Da zIZ88`!D0&!u}TECKR%Y2czZvRiw+%`Wt3BI`DiLZP%`ZwIkjtyp-o0gnx}d3{@}nFqvz-PHJd&A ziG1ne;&X_;BOx>zcUbxU;^5%OPYZy%`N)W7&YtG?jt-Wk@&AMbm}Oq8t4FIpR%tLw zOH3?`ePt{uiK;ZbySp1|qN1#ZK&Y>9IkS$KfWE3uKCpj9bTH`MBY44Hl10%00ZtG#}x;9RAD!bKYg{3 zbo$K9%qsC)?lMojd7SVSm@Z@7jew60v-yj3YIz`=`Ju}<16;fO7o_@LG7fdqxQRw_ zE?dMhyLy%a_U{}phMAaaX5mS4Sz_}3rjO&+j8y1Em5 zPAxTbnwpx-Zz&LftzHMoA4_R4D_En*5uo`IGbuAW{Q?plY^JfSBbZ2@N}4unWZy)E zWt`I%njfrIj4)JyKCwKFKRQ?)mM63W_G4ioiJGTm#$Z(?(xT;Y1r|Itg6o8QBdA?!nN^``|s z^0ZxPe*UarzX_L#_4B&vlHpaWM1b?yT3HdGOXLkZQU$SS0;evi(l7kA2sM~JM?aU5 zk&%)@>vRNC!;;glkrBZZ<(MvM0%>dOg0{KlF)Dmmy)w)s4=BP#({7PADCcD{uff!X zo7UWzU$1KnVyvl4^lh?!e+&UZE$sM zt=`(VnQcxswtdeA%z!uaavMYg`=rD!0KJBEqGAF-MIzKH=H}*HWU;~J6Sm-;m!l5( z<3aoA`e6b+?rV;VKzDGzgX|jCi&5z<%mDbu90>{(ignY_(+)rA&_-Mk9R0e)UyvT#{{+QOnDa6oJ0QXc?V z24tbu6t5%LdH*YfP1z>deB#w|zYrBZfN$I(tB2gG#RM9ymitql>E7W9GLdn(MmQEc zjh7J()>Z0H7FuwvW5~=Vl*%;VAmz}rO$LTA{MQs^ruhy+$Y}7|YNod!L^jPt2I#7( zy`Xje3Dt>%K z@Gol|cp%#TM}@!e#lWr|;6$APZ|Xz_1bBI8Ra#64UL4Xj{GhT*1l3n5xRvi+DIbp+ z{9vd{-iS9_@n&*1^jU<3h7!q?)zrf&#|au}e;RPUT0SZhUxXD4pEi#ze)BqP96`xPE7<$*Se;?X9GA^DEGU z9E3kG+fsc(8`y{0`IE6~!WO30aa^TbR6e9fL2WJNpJ}n_U2mhT%O>49=aK4fVhngVkU(J-GPxSv>SUg}I`vs3={p zoTov-eShHPgHMV7g)V#4O*gu%^A~IDJpFjpjG&;jJ-Rh9Loy!+mS+nnRfRL*fyXr0M)^`}GF`to`aY zUm`~^D$<@k9 zas2&qf2%Un6Ea*ZI(?%r(&Hj?{$?;f=^*(2^JC>$T5J{;aIVLE7>Qfnvjk+J22(>0 z(C#HO(Ir0UH4iLl&@xCZd5DtX)lS+wwhN>jv#xcp0Ejxu zKW8&Oq=Ikwg~!m7<6%0E!%Y4JCUr)oq?DbQWv}0eBm2B22@XV%ZH0tLIKE{!ajt_8 zPy9BZ%h7(Yv3(W`9)E24?{U76jO8T}8ORDzr|aHyy=N4$u;KbpStG_g zk_}zInE`$p!*K27CO&2W>`wWEPu$BKS2K%o)Zk5CS?B`T*R0XhI@$5%0e^g^1q1YAL@@^fuVR6($)VslZP$Z0A4e!%32q2;2)XiO^EOOC% zEz(A;gm2d#Nbuf15b)78iZ^dMU7OsRt~=P8K8u!M*$c{_G30Q~dCSedH^yOC|EY8q zTBA9~Om3VFo(J{ux(n6oFnrCEyT@)_vE-g3Q0iH&FKVU}map*h=i=Cm-%)k=+%L(Y z^W`xP_l?B)hD&Kfr+=krYGb^$rR13$)r|W6TRxL^4R=0gom?Cy)j94yREYPouYO45 zZgP4x8Y0He4<@Q(ff09UeeeuJ%u4dQ5&P>n0y6YiTr{T|zjrm+3 zG_`~_wu|t@zxItDHl_j`N6I{Mw%Gzn4;nz`quyK`h0R@Y-afQHp+G!$HZU0jxg;HjGYxP)<$1(tEso|EsZ|! znV2q}E{^`S$QnH=!tP`h&9GFxOAPTmRLQ(Oob%X_Bd^UgFaJf2`mVY$;TkH+)%MZl z@gvszop0l$`V8ySODRXj#>zfF_}1&5N{Zeg|A6%=fjS8W2C*iaK8{AqFAnT_e~=tR z{wmd8ez%UlnMwIU{r*-Vy|t?#75Y$R<*#s>PEPDh=69r=Mh3dN+efhP(8mwz5uH}^=r*R`IFvQz&=t+FLOgZcNu?mI~;C1bHADH0U%%BP>{Sv(uSpQXWW z+=KcF(Sc`*QK^ft_Zz>f-*0UVq8(C6Z(^os_k8&bRX%fQy`5efZLnUka#4=IME~|J zgFjE4!=FdykFE1kB%13si767yyR`K?X3DmG?E5R;yuAMw{Y_JDp;wF)$-q8u-$z9X z#Gx*LiZquQSG?ZtVG2Dbfc3Id2_AkRf!~gh2 z*}jPV19DAPo8jEU>AEBP+FbkNhdzn9P-!`?X?|kfL(mbAIM@3vj&sMyH4fuV?z?Wn zDZ77O=RrdFR&s8=`MnI`hz;Z#rG|A62(YJ|dWu(Q&>d3Z872HW%uY_0f8D-q2~I!u z4?a!X(MT9XR<(0g8rdH&B^*MDmMpkSeek|P6cwbWcNsS^7YZ~wDEU9rh9V71OUxVjc)J-)?Za6CPjA%%%obF$dQGsfeV(e=Erpg^z8%ww!Xz-9CIv_(Zo zrh|8UvQa59x?@+gJr7UC>A~vu;ptO;XK%)og_@NW<_4pS3ClDh^uuPfqlYOzK?}li zv}z=fTeqaol$&eF$#oT1NBpu<9P9kSgzaxssw6Lextd3Fi&})sX5?dFVD>usq%487 zg~jiPyORR8gc`6Bon!Qz|x^!~t*4A=o|L#7m`A}$nYSZX%X#!eoIzMr>Lc8fbVV7?E+ApyfV|O(_o;d?%a#HIAu~Zee@J3hN*CXgfsct6T%wVu48`ZMz`(093cgDqN)s$VCXLd( z>I`Pt;otc%QkQolv1rckA>+3Eis?Qnq??6WFHlUTYtu@vd~~a9ZX>-(0xD6{dTMGn zkw~%28oF^5lfA_*dB;Mq*{rPO&H1um6eEvmpyCdmktmSe?s?MP4&=JrUCm2FZC?9T zR4O(%S$&0yuxWqRe*V3|=oT{NdxfTBErNkpDN(blbU~IKM3}ErZ5|gY*;nwaQ~BlZ zN*i7-uCY1VOzpI$;+4USfZ^4bn!7*Nt>2^iwYRr}7dZ$=h-yK}V^;fBze(^Cb=~~U zt=2mg**nvO2EBhtH_p-R-8IBy)YPYT?7vjBdXK7l9@xn(=wA8C_9d(Swn&Uef!xHoR5=ssx{8=Vv+%eN$#gvnaOXo zsTk_W$A7dG9q$RP?1`uhAKWITYz$$aJ^M+_a_~0S8gk#=mXD)=rPLr{*iKr!30CN` zStnXyR6`kw%rrrOCwEK+Fu{f}Xo$}r0LTw8fc`t44Uqkp7|6#kZ0jq0F+Oh`iIvU5 zc`JGC4pmkLv}W0aurh=4sF_H^9CaLjRbykoI&eP=Ie0<5p_AZny%_lJWHVu-2CU}% zuw7*nGCtGFXXq?rG?u#wM&(?1B@RHf!k9O9TJ&ZA*W=Al6^T6fxN+8?L}PNWLDxt0WU^M!Y%?X@^hD(joB1plZa!44AO}Za|yR9)ZTuADTr-2PnZMr_opeP*!MCnqMJ{KtgBVn5;m_h@En8-pJT#OgE3ouG-N1S%vf;}|UdR#<* zb)rb+TItDnm59;bd|UR>vUw8AoymOaCHnm6CuUS(3=pu3eucJKfuWJ-Bk&+r+85!x z$>|e@SN8UqPuC%%J?>Z%?+vmiF!XCriS8J%Hc3o0Oa&3mwU~UM#Xipi#^kj({>VV~ z0pHKFrcj3wRt^qH)(i_>Z4w!SxhH}$YJ&G`3au04U9k!=*q3eT<>uF-ha=cfu0p<9 zacwiFxK{nc%xTYqMwhv-5lLM)CY6H%JlIS9_m` z&?TUDCIP>H-qpG6do<1AEaur!1F;6=4fCEc7OKjETe*yZ3na8U(yciMe0!cD>;^7^ zo`WB+aA%eUm2=epTNnSzxT}J<_KXZu=OIx9@f*V>!ICA0q}_ABWTiysjc8{lagv&B z$I`i<@L~k$y-bWK9C#56iI!hJhOO3{Q_wk_m`3A1_`T`cdoh+xkS3oKUA~IrpZ;qt zabp?Q1s9&he{om{^TYGYa{lk?WSg#Jhuxq(v(GkFkSxR& zsZlnU(r*$Qf55gjLS}V9|B__4R1c=$NtBbN#UX^^+epSb-C6Thew{=vk zyT{B2w*~w-=Lg@u6}_zt_*~4q9ygTgbwM5Tw$D+qG?_Oivm<;IVw3p7kTFW`<_C(0_#JGI^# zcC(+Z<%8%CcpHsaVDxc5{Q@_Lq^KkF;Pqk{mAVH8IyILu12TX$FqA0+OwpTfB|RHX z0PJf!(>Q&8a-;EVtL`J&3r$VuBJJW_rKDW#A}AF1>-(vFf60@l7* zPx=1xfSB`&DkvCW1}C^Xs&9|sH=H+6e|6c(&HWwV#Ck=)pjLZM(>GA3S1jtjlis<| z1s~aYCI3a5pyOJSeIqN2P*Pgj)xofed>#!P5=D>v%wdaXKU-exa+~(rH;KPl@T<^- zYiHKfBvc^#K(jFUI_apv^xCUt^NFYBP&u=*z4G#$>$=~`=72q z?Vuc%Mq{aQlh0i1+iTbe487wi@L0h-(8&q9Tk^iD{>wTB?PKEGD-}~y33+)ZF0yuZ z0&Mg@zE>BUlhrOOAC5LERG^&>$Q_S~r?z6~8@t;27 zAaDqBgOsD7K&aC(*~s4GbhQ)8;lwhRU6E4lB9>4XZ;*c1|1>>3z%uS*ZQaF`=H)osyfu{72{s)(J39fd-_QoTtdCyor(GrUTC2mv zL`jFf?r;Gk0I~e&mbWAuPI3DapOiG*`;BIsN{dy?8q#7pIXF|=#f0Omcc!4OHC>)2 zwFTWGy>mzO-}p1wl|F*}yL!f_42t<6(Tx$dp`pvCX|wS^l@ z9PxvmL562o>l_jqIxomUg%5-h@#d=*tXFU-|Ka1ugKoCcd`;H(za%%{*{1TnP|i~p z8qQORqe$kl>~9Ic0-5?xjsj_=I&YSTLAv71j?slt>DZjZd7;Xn64VMh#@=z6`S2vQ z*EZ`t_K{{Y?j*%rNKfxRbp2udiD#Jy`Jz`0tBE8~Vh)q9=fs-z+W^2Jqg^%_7UjnDG-GfgSy&wOp^8MROZoqZjT~VM3 zYcKAZnA0Nmgw=?M|P-Ogmd#2r(1?#6w6{ z-KJeg5j+~v`p=&g_gWaSA)v>(Ke=gy^Pvj1<*t}*?oan&ib|n9D5-_VbmuM()-7sH zb#*#7qB}CXFlEvG_+S18r4%b|Ao;`xf~$<0z>L=`HMODJ)}0F@i(ML(dYcf-RqN-v zd@j$oVf7-jxk+}yj`QtrTkf{9mDXb-a&jR_0$2XGn3QTCmyT82#JQYp5+~e0VfyMN z(<-CpwcXGZL%;h=$GEFa7BIb@V7Wic3x|Cb7D~fA;oD7D)F;sSppYGOczF1f50~=W z{EsZ>^SK}zMaJQS)#W1!j(70*!tcJmAv1I*?4;r7&kKKV*g33LqJ2DR4 zVn)U0H^Sbv^g==m931$?6)>;$_QxGxftGv-57qnZA57k#9Uqu+ea|M>WwYMouCi$4 z@`|1xEh;DS;vjZ)FAWS0AGscL`kp9wde#nEQSn+0t`6tz&Mm1b@BmJ|Gn_I3-~i{x zjv)Wt<}0BMr_xJG zu~)|X9Jl)P>&HIp2DmBwY=cF;a`>*hTR5So%5vZjoU{Z#3`@ZNPpjipve2t9z2BGm#iQeE&3o>o4e02uaSM@061+=KHeNF97&qsP2S z;Vg5n={faOlW!9U#$!H@yia*02%tz7^f`BjW3|Pu7)M^%9uR0B38f7Nq+7D6cWw3} zRI3`u?8TATc*AQ;F0%98P8zZEoi<~i6O2T5a=&;askndBm6ViJK@6V85O0Im$sU9? zAPkA_G~Ji+nF{&HSUPG9OHEPee7i*ROCxlpC^)WoCH z-Ls1#ol?iT6q3Vjg}~cvK2Pgh`;vvyLNaS?C%P*uD^0k-{eSfIRM?;P4u^q(y7~j( z%X4syNq%vd3sXu=_Yer{qKg?L7JXeFF(E*E1CXcdI)H64(!%= zcKt60OMnXZ&#~4iIbR*jKuh!5>k7Cfx_5CjBa}jo9PhLpH5hHL4qJMOZto9>g~u0K z*ovr^x^7MVhn=Z;fQ|gy7CS##Hjbe2W$P)2(@HeV12n&9oe^{VGj(nQ5PmL>nwkV) zB;ez2LzXzH+vxSJXQ^DTn7xd1Pb4y^Vt(GNdUErqE=PeosgM8F-`wA45`30)2~<-4W!wkXr|Jdn3{RH_rw8C6ODZvOpdk;A=NW5P>=orj>BWWk%w#5Wnn(f4%f*{o;Y@1ez|2!Wh3D`@H?Emc>w&8pA*UOIo@j%iM z4su;6*h3V8Eqcn%&Dxq}&FH7M5|2XvoP+H|ImQhl`3>)-?l`fFN}H`I6=}6|h>WU{ zLJqUy5TKCz`u|FSC}ZE00tqxTFJIaHqrINe@VgK16!r9{K04F+Ch+Ije`fpW>Ycc# z^Uv$|g6;po@+CWNW+s^?>iyxLMY~^Mt=qQfUNPtv43Qauf#@Qam+Qt?wP#yh z{S)k_48SorI5^510_qTZ)dE@Osw!dS@8-B>Sg zvuS5%XMcS$hL0#RqRFQd-jj`s*G51OeDS-|`V5|SZcYw|{#QH%(mdZ2^T}*EH^}kY zEc;{A?VN>7{oHc=X&0B4E|xfbfnEQU`PM|sXfdVKf2f@A2gl}`-=INN_8Z^{$Y+C- zXPTN**>W+Uj2c$i@M(wnUL2085mlaR-|*&{dA+@jsA_23Yin%C%yjDg7=@${7chrH z+LXw>@W#GT7eOY!Ud$hQGASkUI?R57>MTAk4yF0JMuAC?HS{qm%4ct}fX%2aqjPkB z;7X}T>t1A=5On_uyMC^*97tW6NY^g9@R^aib%CQ*1Sg!Dnz2`Nw|r|hzdBx>FE_y; zp(5HZNLXcYa2#P)+l8pmlq{1Pxr4QM_v4Mlt}Gx#tPW*y8rh@=`s{Tv(fFP_A`XJ} zkU@DbMu|Z&l?4*HOyAj$C~#z_!SuU>nW{$eHtZj4>3v;Yj>0JqLZ0sK53CEj^|!DG zyuOZSR=tL5b+48q)Usr!p+fvET z9GmY3^mLh-(J2jEom?%(ctnbxew^8^n4l87s<&tlj)GfDmdx#${9nA!B6ut#W?U!H zLoEO>pCq$=<-BsvAA zf2)3Kl-oqPdBz9d_F%&NH**-ffjGoXS3WohL;w~l{27eH#ba zNlI{2<4)_VN1g`^a`RBIe|+@X4r=io{`Kg0Si0lXm3Gsz9E`(1e-e!C%(b~~jK8_{ z`f%;=V0b{TbQ<2*J=yo;UtdyQ@<20Zj`~~4jsBF^)`>P5e}iJKD<(L;)9LqbnQgi} zERDMN_>a$7dwDr7@}s9GiSLC4_*gTOm`%_o%0eTgRKQJh73i-i#8=L%I0>9wY4^K- zA3lm%cQ|go{rYIQ+mvG`d-aqP}1duOPz(E}vJ`VAwgAHiE) zv?ydNaXtTHSf?zTCFdgzhy9R$)op&=^*+Og9XCy_@X^`EzQBwK8A3s^M!`mU2F*&2 z#c+0w(hO~fwZmhy?I!qahjNcoM=fl_!mSWrePw!M zp)P6f_wy1-QFEn7w&$vL6qMSJ*Lz%ymp$7CHZ`!z>sL--cSuZ`zJrKRlX4K{4NnXX z(w3D&47dc1$bGbx_N@ik{mh<$fm@hTe0;9oWXsJuLSZ_<8X&aiuc+Xe@!EH(7cDI= zmhlvd=**OW11}Y^i>XTMqou#DiCl9f%c-yNs}*_vQL~I^gUH1gBiATbtPOR>Am** z#-Ol0S{?4-Y&@O^A~urlw*ALyzxrt}WQEWbE2%d0z3F_Zw8lX$y0^Q#cVGZon#K8^ z;EGy*$HhX7O4F8wLLQTaf5*6S=X&kMFsU@2@Ac&S9jy#K`~LpSk<)v(gA_`F(t-jH zC`FDoC$SJ1EDmPom8ow%R~IwGK22iW+y!qh&)v1{=A&O$PeP}ktnBv^!%542FWBXB zRnj8VD9MCykWA)TI<*2aMRFFmdixMOW_ETNCh6601Iw?34S^@!(eYU9Y=dW{Y^&+& za%E-3~ zy**v5?}jvgEq@1|hR)6h*Q)2V+YE~MSjk&rravgioWlp&c9Rcr`s&a(H?|H0b!bbn zZ#-c<9XC_lxQ!#`vQ|mL#KO|zTF3tAk$2m>Lr5{Xx2n#zn~Yt#=`*AI_Lmi~9I1{V z^vyVHR71VHpwhQ3n0;62KHmQJOKRLQ(KJpbCQA^M+#^3ECyTuN?Yu$V>Bzjj)L#gK zI+U``b%szvHrbSdXw6voZQ9OKm!F-zxTfY9q&q@%i6SkPRFMMjGb&`~EP#qhzOHN4 zS)IfMLMBL9&&R*0K?1{1aTA)Xe<2lj<_cy6_5~QY&Z&1 zxz;^;^k@MJNZqft6T>QE`)~~Za;k<9>F())P!2WGH45DXHgYTZhN+Sd5yp6JJ0eV4 zT7|{M(DdfH^({~P^YI)k*i^AA%F0a#D#(bTpAovLsl9euI>Jbs4t^>?@FOq$gM4vZ z5Uy)LxhT?)HZkbO$Zn#fpa8K49>M-*HAJSqb70#0oNaoo-h~F`F-nVG_t~>;Qg-7; zGf;|q5;(E^;9*#dm86^~Da8q>(Q9hXyxp||9z zre@`{&aaW8iPJS|QqUEeqpp-9d{{9cDm8EgMKp@Ir{`Y{l}BTxGky@ddo9A+=4`tR zo?$KGm@z!^21qd^g}bnT2lp5aIc7=Htt04JM0|k)o1z@Ky6L0!?q!N{(CMdx_D$Er z&4)W66G1lh20{RBo>YuDbGtf8N@8Mg5d#%|R`+w;#`~xN zl8-qEdwXH5(ezQ%TzL1eX@4~)3ca$>ohSf-mz9BGDOByHdZl`dl4M?Cm}D}0mL;QxPRoq@fQRSM^Wrg$_W=kR4SU_j@Sr9@-)T7cOPPtSSNzpulx5UnoaPo$oae~{o-@fW|q@sw2J0FloeuEA1dW^w7`gh$? zbLqU(-a3Ev1)UYGxW@kSc#gA2|Vlr8|i9Ef3hbkOE&oNyG(6<1xW+dd~QbqbV zvhtxdH(~l!9W28ay&oqb;pDR&{{Y!?Y^EziR(hq$;1y<7P@1n%LQ+zY+Aw<;5hen6 zYjUll&#OZn{@E_wb({JJCoa=a9f6RWV1uGiOG~lO9Y}fIEiHBdji}#VL47}Shggh~-!C4sA zG)ITu*To1lN8S631}iIWtSmz(CnwM($?2RE_A2Dxb$f<$EnJB~1YI{q-k`m9NqyhP$I(|a4txv@3|w97DlFNu6{r;RgRX30$PEmrjA~Gue?&<^)f4b~ zJ0YO+PoJ!< z<|0Ewq363c6J3%zF*cTjm^c7mKHTVT;>Ly|^5yAgkU1mW+&@CQa7YM?)zYYyGju&X zG1S(U1-v%&>9?2ax4uJYSy*|tY29U%3f+zCw z>P6g(*3V>QHuKVahN@nS#XFeN8$f4cN5`BwyXb#%PgDkCrykQ&x@TRmu<-HW+!7}z zC$ACAaWUf}+i=zIIjYkxg2>)Z$q)!4Zfxx0@v#*$xLWDdhS`|*>67wsAngaeVp)=o zjSbtPU$vfxYi&W1Nb_j1498g9`pOe|Q)qEfR7^g1eIP7MF@c|ZA6>VfT!H|Oxm4-l z##LVS^B73{3PxhCoSM0|PH-XS&Szga6Lk%tCC&N*XFH95ef`2`IxQGw|93LGT3K*rU-FAEJgym0Z08AIUZ z@95}gZBn3Nv%#aB4r)DN9d}HqEH|l;b%JTD9K+Ff_GLg&FUpC8>EVsNI^5S^-26(f{CQV@iVUa>_wR4uAs`jn!4_p^HXlrw^>@H{XZ)FpdqV`E(Pj#@7!i@v zl{9z@Ioj(`T$T)}78I=Z9Sc3H7e&rNn1rdI|IKL$(nJSl(D4_YYoXD%2*glm^cCQ# zxNR4@xVQ-SJwU7(-*f?Wl%S^^N@npUxW4u3e>I(XIF#=j_J?GTLPA8@WhZ1!NllV9 zNg_)nWc%9plw_Gmp$HW!lr5rcWeLfaq{zOLvZcxTp7VR(x4#@mnVy-spZmV9^ZcCG z1DTdQKLRmykD`NpbgPCC0|B34PpPZ5qhk+DmScf<0AwH9D8ys7Y$p=MCylOMlX_`U zA|W;@?>5*1&Y@tS4aWFh1Tmm$gJifB9lcu1zcGhO4>dvrXY`PV^dkj?3d zigGj>g<%L^_$$Smex8@tU*>LyM1h~*!Qjt1r)OaI;bZxjoGee$?aUuIRKbES)d6*7 zZ)w|}1CFC8*DG;s;*R;@gf*w>ztkSVA1oGS+m;seswnT_Ijhg2W-rX$;mWt9C<`{+ zii+Za3j@g`KHiv>_nVe|qhUq|7d;JbuC%12XJ)xreHB-G6GhX+N_wyQu0ofd_glP# zUJ9Emw=}<^(3#hU7p>t|N3z#9CrXw7?QbG?LKZz|X;}d~1a~$ww>%BI(9fQd+=7BQ zoBQWnQ_GyXzhXhPoqA@sG|BL-x2&uTUxD2O9`@>cv?eRMdfRR##ENCCD!BTNQeA)) zzW*Iy2GRBE)mePqMs{N2;)wF#E@23#2I5He zVmtMoKA73jbA5tJ_n}(qDSI)(94a5X^j*^Gu2U_IjVtJ3=4^1I;+fsjMjXOh0+7Y1 z31^x=llVt#?%j1lI~op+3sa>uZJoAve+mnD=0WuiB8?x zHxjGW|M&>q?U6uDelr83Y*UK9pnJT#yW95O!CG00a?i0put|qnW&DYl=IYN^r2dl` z^F;Ivr%tiFUhJ-}ilO_9hj$3>-Ft1~pb^Kp636TDtC6B<<0@8~JJ64PDCj;U(0=kp zF30y6KOTaBvT}oqkCN`Rnyh$x+}TUaqE~>p&zx0WVfP3yH$0|w?%`YF_ zxfS`F5s-#tKb{hPOI1nfH3~mDi6qo~tc3P#la0qm8ek4q1qE1G^!yzNk%@XD;Kt>4 zz;_Otzf6=ZPVzJkszI#9GFu0kFtI4i=cuA)i>~mgc)GR zRdW7T@IifB^`I!I+Y_ywny5;D{`@M%?Vas7+2>g!7iWQv$E z`9+-7=PO{5_7@sXUnSQlidl^=_7^BfE8}Vc_5WdwhkVHYK-w+Z_Ko6jTDH9x*dH|v zU9Y<;Xcac;AoaU%^JqxVtNS1i4IM&ZX(t_$9~_to%BH@`G(t}J^dmxa zW9IDOo3W?C86A_0|KNs+Mm<4lPg|QRS-Sb-$3*G#JS-8oF*}|au*QU(tMtDb80fjV zu?0EC)wYmee&!-n=FW8-AXY}o+E04{U&TX z6VoD`!<}vX&$se0)8==VgBb%x${4=n530SkKopjnr)nzT=&9aPnPlPDt^-zhDl-oA z+athsDA~uiUNi7n9;G4Sk9YR<uXT!KP&d(sQUC& zU@WElM_9w$JbdYU2?D zR6wMHu&r{bxLv>pFWZ9A5nAg@CSG2Dpetl|&!jLseHR4(>+kOY7|$DIvM-EI$1l$( zCmj+mb~*lOx?7J55OS~=%qur;XMl15_hKL8SomIisRjOMw%oWalje*o^QfhXsVjX`M$ zKKxgAzB01@Yq$BzPw2&}@w%iw zG0OrS=oQ5r_mK~X8M7?H%=n9E==PQSh|(jpAZcBtE+=Pn;%pg&QLHA+KJY>>2$ucK z$==)Ff;0!Ptbm!S@ZuFGYqk->-pQPMt7x8n^>c((I zzOj>&TpVp;@9J9q)yL!>pwxC=ZgmB#qFuT3g6!bdeJ*ibxo6VV&K&fei;IadC44J! zn#eY}EP9GJ)+H2g*8Pn->WLTh7YU+6Z7pLkwzM%+lwUw`kuQX^3s; zaOm&99dH#w7?cBTL9CEkp=XVi0e_*KJa_7Doow`j(}tQIYHo(UZFgP}E^P`Q*riLH zD+DM~nU4ep?B!o)O3)>*qZ$YvZQM~}(c*52b;icV78i#$1Z^1HcJ4PQPrmn*XvOmS z!P8PtK7&}L|ME}q2a~lnE`O#Ecn1XGCZ8J~i7K{}#tgUDi1Fg!+Qz4;1U!IfG z3@Z22fq{}YYgKq**IOps?PT%s*Vk{s-YhRKj~9#7Z9!4Qr{SN8+XnTLX*O4xapFlo z%t08eF78UYc(!8d&K}z(dJB`R@_qly8yg0_>Ye>uFWYkuK)e9GV#}Z${hv?~m)>1R z5F$=#S6LKNL)lVD&wRhMmHBj2OZ z^9a)c=GQd)Ut&k+*s9b-G9|5#*x#Wk4AvdGL zUcB4Hf7~2F&RX<<^tPfe$%;W|Dn>+!#=nAP+88AV^wrz!nte0xI)+>rNM7RXCN7?Q z%Khbjg_*_Mg~u-Pt;W+w4lXCsxDRUh+#MElU_emMxNWO89^@WR2ZI1XZR3#zn>%HO zOzRS(wb6FBB0rBY>7LjdLqa~{;1=hi)y#e?xrd1GDrd`t{`-;qlNzI9kW=;0R@B$K z8~&OkQfFIuKk+$v!b|2;Ue=fQ7Ew8C23wM!7VQp5py}#t8a{xGUd-U>SS6QQQO>M$bQI?{5bhHZPcNEqGApm)kJDZo*U~! zDlDOKA5P>b++5sO(ix~K#h`XuL4+yZ;;!%88+?~dc<8R!k7oVL?02thz06k(CBpY5@E!Tdrn*GbU2w0OY?`r!na1 z__qJbvEugHLn~X@|1{Ha+Fv_*{D8}UndcWwYO|BVV)M+i^lQYD6w3_n&|GP;YrbWg zyme`!enpNPX{!8OxXR0eRdnj&i=5DMgJc$BkoLIv=II>i*oy&q&nFuhl9aLoKi?@i z@w{WdSq{UE&Yn!W1yQo0)u&evSQMuu^2Dwj)B8d^THtoQalbF|ZdaY%d=%~Z?Akl* zn+XR@%G&uih9ep;W*#gjvu9g*G}q>4dXHM4Uo*5#UZ7W;>R!C^ASCG8zJED!W&LA4 zNgFOtYuktE)1!BsFVgbh{!flmTsGF`e&S`f8q0mg&Rl6O?jCZi$)=*a&n9GQ{>K|p zK_IQQbKQyQraAL7RMC6e$+S0nb@7j3Avtg;?itl=9=F)zL zN%*aHntfqf)XMT}<2^B(L!qTy`phxLB69---&+5AW0n?zmDLl4y3@}%^3})M=FPGq zn@l}M*HVjh1gMpjbmadY6`GENsFdgck)6Qpu$1Da?%ypAszk+;2OG}UPzo0Tw* zn%`lZW=hyjLv=n`ctXC(PA|)t*lHp*KN*875Z$21i)~H`)W_`H#lN|+n&ll(A`r(C zQ3-||r5FFo-V@osI}RGLU5lTvW4D$f>Has`I0V?kb924ZqO>)>^<#W<<(heH|80*4 zQT?}qtAFR_b8h47(b}JzuvRS9AiQa~r_P;tV*W%?aj`uhFl6zd2GtnKy);A3(gVKb zLUJ=CQ%q~Zbcx#|d!X%9R8)W-0eT1G=arigBbDE+W234Vk@KGa8okb7_27%xy^Sb^ z?rW@$pp$5wU6y5@{o|i{Y67Gc!WRu<*or^BcXKz1*KvN~f0$p*yM4whqwXr(vD zaOwX3DnU0xQ$b;U*z<;%ny?=$=``VZyr(K(LV}lWhKQ8Z`Ily|By&5bs0M$2FG@~U z&FwbB^5b83MmFD_;7}S?83LiU~Y9Jeav8e5#_*Tr3 z^L%O68}BK$0X-p>osd3>%TO4zI>TL5IIN)&C*Ni_w3XMusiRoPHuw5 zg~q;jkA@2jtqGp>@o(r)Rb=w*@~!#``#tY^3JVd>z3ed7xL6S>N?ZVJTYGa`ThkR~ zo0=P-wL8FP8k!adpzYA`_e8qnUfYBq{V?h@z$6aA!KbXOA{f|zy?FkdK*;UNG|SC2 zjZxIcvdieW^&kbc9-ghaxUfJFkd~%3c^L$x+O=YymcX%V7t~UUs}wMJjn3t5HigBe z4n^PRIh(9(Ti1vl%{Fl>fs#oG4t6Q`z6w7{L&cgyjm?Pr+O0v#W;2&gg)$4S&Si)@ zvabC}WE%FCo3%r*xlxsTf2GBv=KTp=xfg}?fXZZ5>e9E%Ixx8Vybthf7Xbdi-e(|y zKMEM^$f$>eH?HCWUDe^d^&5|R808|TDi&Qs0{(#v!s8_`4g~omHrf9_*tWuVei*zv zG3U#q%gBH7^N5i=trIS6qOdhp+9Dk<5FfAUE^MMvNT7?Tlu$$R?V;&8MKbPXn7 zZaEaJ7i7t?A)mmuvZ4ZCTkdtWRRcb;SbDF9`M(#`hXB*6@P63WVSIK16)fCAS_-R& z9tJFxf%os=&hpub3zt&S&^G@X~cLMLdC+z z-B>mlqn~)A6mmKoPf7Cv`kB)#d(_FWWP@Ps!VZ&OO(8cu-U-Tk7EUPiR(|wDuy_u* z5A#9bqlkC-M2##11EDH@CY9F?amm{^!i|#M5#;67HIcqK0%o=*M`9sytm%vFgz^c8 zCKZ0zcRNiC4^D#b06EJ_z)Pb~T>bPwq13l4}3gc$ZxnVl7jd*#_a`r z3thgs)rE=RU0#Xzg5~j^^Q4H0u5Rx1w8z>6v_nYm7|26+-KMf%fPNVNr!I+yh4g(& zoHB)L^)G+mY)LmQ>mExBi?=R)s(uFq56-Ps8>|uBkiCd)KzvD!F!?#FpsK1WGn0mZ zF>>@_I@z&r8bGlC&2lX5r4Z3y93AI}g+afxhY^mxkftJy&2|WdhGYoi38YpH6PoF+ zbA#{AU+&n?Z%YSX>r(5(Nbz|vJ?j806HSYwaBJZ>#tQj!ml9C9AWO*=@hCYXzu-gB zj3B)fTD;_Kfd1)&o91=ztPpV zUGNcwO4P(OEe$(iRiuqCuTVBs$jl$!1|kCx$3*_{s!X8E=e_TWp_tD0?TnGCe2q*K zDyddsDj|n3Lum3nxN{|RmH=q;I~F``MVaaD$?4`%Pn*aaz8M3`ma0Z8XV^h=lO8)~O zu2ZDcJj;LV%|*PTKv~n({n+e&FoY>`F^iG2 z%vI9ccJcFX#)hofeJijr&)vo|!`DnxeY`Unxstd94alKFMB5lEfx)C744s{KQ(I{R z>1wIK{+s=$6-Yjg@u83n*^e+3;N(tkG$Y?^K6}k5dNceXKoVPfz(_-#bal5E#}HWLt36;Bja|al}wyW4`s{ z@w(ZuzY#W8kJh{Vi-BDvaK66cXmk~~W}wQR@}ovC^73dy@s&ZDT$$`(CtzT}Yjx*u zvc+%pj9(Myzq_dnx_!YrXC%}N$tL!;HaDX-XvV#;jli@k7_P@9m`sF61qIs=A$*cf zbM2UL?ZhI45ZF$Pwe04Jh{GTC#Ph$)u2txq6Z~AzJmx5`6DHtWY2_ffKSqcU+UJt= z8HUUT)~MV43^6Xv!24#ocZ#XXcnX>Vjvx0$A_vx+{;a%vDAC@o87C~QZ2{p?l`J!mo-8!9>WMv!|{n*6C9w}zh zBjSsHz!T^v=7x@FT8oK@An%#i`AiGJC~*!(=t}(jxU$?FM^g#X`wKh=J=sf=_eluzWZ>C&g3Liek4N9lp2nUf{*t=if+nU%rld8+mWYpDgVk+s%_ zs7fsAizM|so0_f=DjL`kQn71Gt1Py-3T4JO4S*W>=2cX=MMampR`d4{?rlDq>Cmj5hlci}^9_>$El(;O;hI zZbNTre!XpnV+rE3mtOq~q>2{yN0q;I>sAM;TfFOnZncD`9d*?SFV1taL~!3kd9|ZO zmAacAoX@tzux+lxSz;94cu*x{(_OOFNiTuzkFHpxO{b5j zG(8|FP-W~3Tl#23M@^Nzd4G;SRY=E3Cm>XPw;T_fZ(34DG>)oXs%YznT3TZLyKj$d zB#m55F*PH->k5x1Mh0Gn91~a_h<9-&Ns4l31o_n82f4 ztp@i=ty_0}3j^@%I*7ILa{PCmtnPh+QE*(7ZAC9mJ*%ZY`b~tEZ}5yh2S?0_TSC9f zhhnQYO`=kN2p(%<$8Q}jAKNUl=o4B$OOv;Jr;{c$pX^!IUor9$1~zx7;)*~KLcst!nJ^V>Bi@lw@=%Ud~GGOoBTJbqXl zy~=%Ucf7)yjDpqSv#nfBq@CzNC^JUi{kgHpPl!JFBImv}?`Z=55RabH$vb=@?Ee5d Cw!9Po literal 0 HcmV?d00001 diff --git a/docs/img/figure5-m2m.png b/docs/img/figure5-m2m.png new file mode 100644 index 0000000000000000000000000000000000000000..af98f39781221a18af85e91e3a027669018ea8ea GIT binary patch literal 89295 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ⅈ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�Z>lCWeu>#98B%EjMN;mh@$N z)6@SzO5|0e^tEg5P3111Su0%D=$0tgHP}q3IAj9lL}J9UOTH^$!^cD1jr(is>g*@y z2ijrwxE&M0Rc9F981`iL{r|WB+x0(e`F{>M2+V!s Z0j#5xSiKbt{s6F#f{Y5dO4>Bw{{u~diFp73 literal 0 HcmV?d00001 diff --git a/docs/img/results08_1.jpg b/docs/img/results08_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6d56bb3f4ba93abbea82abe8d07e4bd1d379cc98 GIT binary patch literal 251893 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 z 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

HtRcuq(T?-b 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 RUnI=g-)a)8gDn4^{9nhayqf?3 literal 0 HcmV?d00001 diff --git a/docs/perf-sc08.md b/docs/perf-sc08.md index 4236592..6ff41ae 100644 --- a/docs/perf-sc08.md +++ b/docs/perf-sc08.md @@ -5,10 +5,10 @@ ### Fast Data Transfers at SuperComputing 2008 The record-setting demonstration was made possible through the use of twelve 10 Gbps links to SC08 provided by SCInet, CENIC, National Lambda Rail, Pacific Wave and Internet2, together with two fully populated Cisco 6509E switches, 10 gigabit Ethernet network interfaces provided by Myricom and Intel, two fiber channel S2A9900 storage platforms provided Data Direct Networks equipped with 8 Gbps host bus adapters from QLogic along with five X4500 and X4540 disk servers from Sun Microsystems. The server equipment consisted of 32 twin motherboards Supermicro systems using dual quad-core Intel Xeon processors. -![FDT @ SC08 Image](img/results08_1.jpg) +![FDT @ SC08 Image](docs/img/results08_1.jpg) ### 100G test with Ciena Second major milestone was achieved by the HEP team working together with Ciena, who had just completed its first OTU-4 (112 Gbps) standard link carrying a 100 Gbps payload (or 200 Gbps bidirectional) with forward error correction. The Caltech and Ciena teams used an optical fiber cable with ten fiber-pairs linking their neighboring booths, Ciena’s system to multiplex and demultiplex ten 10 Gbps links onto the single OTU-4 wavelength running on an 80 km fiber loop, and some of Caltech’s nodes used in setting the wide area network records together with FDT, to achieve full throughput over the new link. Thanks to FDT’s high throughput capabilities, and the error free links between the booths, the teams were able to achieve a maximum of 199.90 Gbps bi-directionally (memory-to-memory) within minutes of the start of the test, and an average of 191 Gbps during a 12 hour period that logged the transmission of 1.02 Petabytes overnight. -![FDT @ SC08 Image](img/ciena_sc08_1.jpg) +![FDT @ SC08 Image](docs/img/ciena_sc08_1.jpg) From e3ae11002a70cb7c006c0b396424a1e800363333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 5 Sep 2017 22:33:59 +0300 Subject: [PATCH 17/55] Fix image links --- docs/index.md | 2 +- docs/perf-sc08.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index cee573f..84d99f6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,4 +20,4 @@ FDT can be used to stream a large set of files across the network, so that a large dataset composed of thousands of files can be sent or received at full speed, without the network transfer restarting between files. -![Fast Data Transfer Diagram](docs/img/FDT_diagram.png) +![Fast Data Transfer Diagram](img/FDT_diagram.png) diff --git a/docs/perf-sc08.md b/docs/perf-sc08.md index 6ff41ae..4236592 100644 --- a/docs/perf-sc08.md +++ b/docs/perf-sc08.md @@ -5,10 +5,10 @@ ### Fast Data Transfers at SuperComputing 2008 The record-setting demonstration was made possible through the use of twelve 10 Gbps links to SC08 provided by SCInet, CENIC, National Lambda Rail, Pacific Wave and Internet2, together with two fully populated Cisco 6509E switches, 10 gigabit Ethernet network interfaces provided by Myricom and Intel, two fiber channel S2A9900 storage platforms provided Data Direct Networks equipped with 8 Gbps host bus adapters from QLogic along with five X4500 and X4540 disk servers from Sun Microsystems. The server equipment consisted of 32 twin motherboards Supermicro systems using dual quad-core Intel Xeon processors. -![FDT @ SC08 Image](docs/img/results08_1.jpg) +![FDT @ SC08 Image](img/results08_1.jpg) ### 100G test with Ciena Second major milestone was achieved by the HEP team working together with Ciena, who had just completed its first OTU-4 (112 Gbps) standard link carrying a 100 Gbps payload (or 200 Gbps bidirectional) with forward error correction. The Caltech and Ciena teams used an optical fiber cable with ten fiber-pairs linking their neighboring booths, Ciena’s system to multiplex and demultiplex ten 10 Gbps links onto the single OTU-4 wavelength running on an 80 km fiber loop, and some of Caltech’s nodes used in setting the wide area network records together with FDT, to achieve full throughput over the new link. Thanks to FDT’s high throughput capabilities, and the error free links between the booths, the teams were able to achieve a maximum of 199.90 Gbps bi-directionally (memory-to-memory) within minutes of the start of the test, and an average of 191 Gbps during a 12 hour period that logged the transmission of 1.02 Petabytes overnight. -![FDT @ SC08 Image](docs/img/ciena_sc08_1.jpg) +![FDT @ SC08 Image](img/ciena_sc08_1.jpg) From 3c224547c29cf74aef39c3e195cdc10464ed42bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 10 Sep 2017 13:48:36 +0300 Subject: [PATCH 18/55] Implemented dynamic class loader for user extensios (PreProcess filter and PostProcess filter) Added more filter examples Cleaned up comments --- src/lia/util/net/common/AcceptableTask.java | 3 - src/lia/util/net/copy/FDTReaderSession.java | 63 ++++++++++++------- src/lia/util/net/copy/FDTWriterSession.java | 35 ++++++++++- .../examples/FirewallFileExtension.java | 61 ++++++++++++++++++ .../net/copy/filters/examples/PostRename.java | 40 ++++++++++++ .../net/copy/filters/examples/PreRename.java | 38 +++++++++++ 6 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 src/lia/util/net/copy/filters/examples/FirewallFileExtension.java create mode 100644 src/lia/util/net/copy/filters/examples/PostRename.java create mode 100644 src/lia/util/net/copy/filters/examples/PreRename.java diff --git a/src/lia/util/net/common/AcceptableTask.java b/src/lia/util/net/common/AcceptableTask.java index 456e340..48fc3e1 100644 --- a/src/lia/util/net/common/AcceptableTask.java +++ b/src/lia/util/net/common/AcceptableTask.java @@ -1,6 +1,3 @@ -/* - * Copyright (c) 2017 NoMagic, Inc. All Rights Reserved. - */ package lia.util.net.common; import lia.util.net.copy.FDTServer; diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 3605cf8..76b18d9 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -3,9 +3,19 @@ */ package lia.util.net.copy; +import lia.util.net.common.*; +import lia.util.net.copy.disk.DiskReaderManager; +import lia.util.net.copy.disk.DiskReaderTask; +import lia.util.net.copy.filters.Postprocessor; +import lia.util.net.copy.filters.Preprocessor; +import lia.util.net.copy.filters.ProcessorInfo; +import lia.util.net.copy.transport.*; + import java.io.File; import java.io.IOException; import java.net.InetAddress; +import java.net.URL; +import java.net.URLClassLoader; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ArrayBlockingQueue; @@ -16,22 +26,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.FileChannelProvider; -import lia.util.net.common.NetloggerRecord; -import lia.util.net.common.Utils; -import lia.util.net.copy.disk.DiskReaderManager; -import lia.util.net.copy.disk.DiskReaderTask; -import lia.util.net.copy.filters.Postprocessor; -import lia.util.net.copy.filters.Preprocessor; -import lia.util.net.copy.filters.ProcessorInfo; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.CtrlMsg; -import lia.util.net.copy.transport.FDTProcolException; -import lia.util.net.copy.transport.FDTSessionConfigMsg; -import lia.util.net.copy.transport.TCPSessionWriter; - /** * The "reader" session; it will send data over the wire * @@ -192,8 +186,7 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi System.arraycopy(fileList, 0, processorInfo.fileList, 0, fileList.length); for (final String filterName : preProcessFilters) { - Preprocessor preprocessor = (Preprocessor) (Class.forName(filterName).newInstance()); - preprocessor.preProcessFileList(processorInfo, this.controlChannel.subject); + preProcess(processorInfo, filterName); } } } @@ -277,9 +270,7 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi FileReaderSession frs = new FileReaderSession(fName, this, isLoop, fcp); fileSessions.put(frs.sessionID, frs); setSessionSize(sessionSize() + frs.sessionSize()); - } - else - { + } else { logger.warning("File listed in file list is not a file! " + fName); } } @@ -301,6 +292,36 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi sendRemoteSessions(initialMapping, newRemappedFileList); } + private void preProcess(ProcessorInfo processorInfo, String filterName) throws Exception { + boolean searchElsewhere = false; + Preprocessor preprocessor = null; + try { + preprocessor = (Preprocessor) (Class.forName("lia.util.net.copy.filters.examples." + filterName).newInstance()); + } catch (ClassNotFoundException e) { + searchElsewhere = true; + } + if (searchElsewhere) { + try { + String userDirectory = System.getProperty("user.dir"); + File filter = new File(userDirectory + File.separator + "plugins" + File.separator); + logger.log(Level.FINER, "Trying to load plugin from 'plugins' directory. " + filter.toString()); + + URL url = filter.toURL(); + URL[] urls = new URL[]{url}; + ClassLoader cl = new URLClassLoader(urls); + Class cls = cl.loadClass(filterName); + + preprocessor = (Preprocessor) cls.newInstance(); + } catch (Exception e) { + logger.log(Level.FINER, "Failed to load filter from external plugins directory. " + e); + preprocessor = (Preprocessor) (Class.forName(filterName).newInstance()); + } + } + if (preprocessor != null) { + preprocessor.preProcessFileList(processorInfo, this.controlChannel.subject); + } + } + @Override public long getSize() { return sessionSize(); diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 812ec98..4f720ef 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -13,6 +13,8 @@ import java.io.File; import java.net.InetAddress; +import java.net.URL; +import java.net.URLClassLoader; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -675,9 +677,7 @@ private boolean doPostProcessing() throws Exception { processorInfo.remotePort = this.controlChannel.remotePort; for (final String filterName : postProcessFilters) { - Postprocessor postprocessor = (Postprocessor) (Class.forName(filterName).newInstance()); - postprocessor.postProcessFileList(processorInfo, this.controlChannel.subject, downCause(), - downMessage()); + postPprocess(processorInfo, filterName); } } } @@ -695,6 +695,35 @@ private boolean doPostProcessing() throws Exception { return (filtersCount > 0); } + private void postPprocess(ProcessorInfo processorInfo, String filterName) throws Exception { + boolean searchElsewhere = false; + Postprocessor postprocessor = null; + try { + postprocessor = (Postprocessor) (Class.forName("lia.util.net.copy.filters.examples." + filterName).newInstance()); + } catch (ClassNotFoundException e) { + searchElsewhere = true; + } + if (searchElsewhere) { + String userDirectory = System.getProperty("user.dir"); + File filter = new File(userDirectory + File.separator + "plugins" + File.separator); + logger.log(Level.FINER, "Trying to load plugin from 'plugins' directory. " + filter.toString()); + try { + URL url = filter.toURL(); + URL[] urls = new URL[]{url}; + ClassLoader cl = new URLClassLoader(urls); + Class cls = cl.loadClass(filterName); + postprocessor = (Postprocessor) cls.newInstance(); + } catch (Exception e) { + logger.log(Level.FINER, "Failed to load filter from external plugins directory. " + e); + postprocessor = (Postprocessor) (Class.forName(filterName).newInstance()); + } + } + if (postprocessor != null) { + postprocessor.postProcessFileList(processorInfo, this.controlChannel.subject, downCause(), + downMessage()); + } + } + private void checkFinished(Throwable finishCause) { if (logger.isLoggable(Level.FINER)) { diff --git a/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java b/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java new file mode 100644 index 0000000..4bdbc2d --- /dev/null +++ b/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java @@ -0,0 +1,61 @@ +package lia.util.net.copy.filters.examples; + +import lia.util.net.copy.FileSession; +import lia.util.net.copy.filters.Preprocessor; +import lia.util.net.copy.filters.ProcessorInfo; + +import javax.security.auth.Subject; +import java.util.Iterator; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + *

+ * Simple example which filters some files based on a suffix passed as a Java environment variable + * You may pass a config file as an environment variable, a regex, etc ... + *

+ *

+ * This filter can be used directly (as it ships with the fdt.jar): + *

+ *
java -DFirewallFileExtension.suffix="/some/path/TMP_TEST" -jar fdt.jar ... other params
+ * + * @see Pattern + * @author Raimondas Sirvinskas + */ +public class FirewallFileExtension implements Preprocessor { + + /** + * Logger used by this class + */ + private static final Logger logger = Logger.getLogger(FirewallFileExtension.class.getName()); + /** + * @param processorInfo + * @param peerSubject + * - not used + */ + public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception { + + final Map fileSessionMap = processorInfo.fileSessionMap; + final String firewallSuffix = System.getProperty("FirewallFileExtension.suffix"); + if(firewallSuffix == null || firewallSuffix.trim().isEmpty()) { + logger.log(Level.INFO,"[ FirewallFileNames ] No suffix defined"); + return; + } + + final String firewallSuffixTrim = firewallSuffix.trim(); + logger.log(Level.INFO," [ FirewallFileNames ] firewall suffix pattern=" + firewallSuffixTrim); + + for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext();) { + final Map.Entry entry = iterator.next(); + final FileSession fileSession = entry.getValue(); + final String fName = fileSession.fileName(); + logger.log(Level.INFO,"[ FirewallFileNames ] fname = " + fName); + if (fName.endsWith(firewallSuffixTrim)) { + logger.log(Level.INFO,"FNAME firewalled: " + fName); + iterator.remove(); + } + } + } +} diff --git a/src/lia/util/net/copy/filters/examples/PostRename.java b/src/lia/util/net/copy/filters/examples/PostRename.java new file mode 100644 index 0000000..4cad078 --- /dev/null +++ b/src/lia/util/net/copy/filters/examples/PostRename.java @@ -0,0 +1,40 @@ +package lia.util.net.copy.filters.examples; + +import lia.util.net.copy.filters.Postprocessor; +import lia.util.net.copy.filters.ProcessorInfo; + +import javax.security.auth.Subject; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Raimondas Sirvinskas + * @version 1.0 + */ +public class PostRename implements Postprocessor { + + /** + * Logger used by this class + */ + private static final Logger logger = Logger.getLogger(PostRename.class.getName()); + public static final String PREFIX = "prefix"; + public static final String DEFAULT_PREFIX = "RENAMED_"; + + public void postProcessFileList(ProcessorInfo processorInfo, Subject peerSubject, Throwable downCause, String downMessage) throws Exception { + logger.log(Level.INFO, " [ PostRename ] Subject: " + peerSubject); + String filePrefix = System.getProperty(PREFIX, DEFAULT_PREFIX); + + for (int i = 0; i < processorInfo.fileList.length; i++) { + try { + String name = processorInfo.fileList[i]; + final String outFilename = processorInfo.destinationDir + File.separator + filePrefix + name; + final String orgFileName = processorInfo.destinationDir + File.separator + name; + logger.log(Level.INFO, "Renaming file: " + name + " to: " + filePrefix + name); + new File(orgFileName).renameTo(new File(outFilename)); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } +} diff --git a/src/lia/util/net/copy/filters/examples/PreRename.java b/src/lia/util/net/copy/filters/examples/PreRename.java new file mode 100644 index 0000000..f1cf9b3 --- /dev/null +++ b/src/lia/util/net/copy/filters/examples/PreRename.java @@ -0,0 +1,38 @@ +package lia.util.net.copy.filters.examples; + +import lia.util.net.copy.filters.Preprocessor; +import lia.util.net.copy.filters.ProcessorInfo; + +import javax.security.auth.Subject; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Raimondas Sirvinskas + * @version 1.0 + */ +public class PreRename implements Preprocessor { + + /** + * Logger used by this class + */ + private static final Logger logger = Logger.getLogger(PreRename.class.getName()); + public static final String PREFIX = "prefix"; + public static final String DEFAULT_PREFIX = "NEW_FILE_"; + + public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) { + logger.log(Level.INFO, " [ PreRename ] Subject: " + peerSubject); + String filePrefix = System.getProperty(PREFIX, DEFAULT_PREFIX); + + for (int i = 0; i < processorInfo.fileList.length; i++) { + try { + final String outFilename = processorInfo.destinationDir + File.separator + filePrefix + processorInfo.fileList[i]; + logger.log(Level.INFO, "Renaming file: " + processorInfo.fileList[i] + " to: " + outFilename); + processorInfo.fileList[i] = outFilename; + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } +} From 4f0af0f495eff466c423f1503d9128b7a240c1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 10 Sep 2017 14:27:23 +0300 Subject: [PATCH 19/55] Organised imports and formatted code. --- src/apmon/ApMon.java | 988 +++-- src/apmon/ApMonException.java | 6 +- src/apmon/ApMonMonitoringConstants.java | 427 ++- src/apmon/BkThread.java | 504 +-- src/apmon/MonitoredJob.java | 497 ++- src/apmon/XDRDataOutput.java | 34 +- src/apmon/XDROutputStream.java | 187 +- src/apmon/host/HostPropertiesMonitor.java | 626 ++-- src/apmon/host/MacHostPropertiesMonitor.java | 921 ++--- src/apmon/host/Parser.java | 2 +- src/apmon/host/ProcReader.java | 322 +- src/apmon/host/cmdExec.java | 1514 ++++---- src/ch/ethz/ssh2/ChannelCondition.java | 95 +- src/ch/ethz/ssh2/Connection.java | 2229 ++++++----- src/ch/ethz/ssh2/ConnectionInfo.java | 80 +- src/ch/ethz/ssh2/ConnectionMonitor.java | 47 +- src/ch/ethz/ssh2/DHGexParameters.java | 204 +- src/ch/ethz/ssh2/HTTPProxyData.java | 130 +- src/ch/ethz/ssh2/HTTPProxyException.java | 28 +- src/ch/ethz/ssh2/InteractiveCallback.java | 80 +- src/ch/ethz/ssh2/KnownHosts.java | 1404 ++++--- src/ch/ethz/ssh2/LocalPortForwarder.java | 72 +- src/ch/ethz/ssh2/LocalStreamForwarder.java | 106 +- src/ch/ethz/ssh2/ProxyData.java | 9 +- src/ch/ethz/ssh2/SCPClient.java | 1108 +++--- src/ch/ethz/ssh2/SFTPException.java | 148 +- src/ch/ethz/ssh2/SFTPv3Client.java | 2297 ++++++------ src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java | 52 +- src/ch/ethz/ssh2/SFTPv3FileAttributes.java | 263 +- src/ch/ethz/ssh2/SFTPv3FileHandle.java | 57 +- src/ch/ethz/ssh2/ServerHostKeyVerifier.java | 40 +- src/ch/ethz/ssh2/Session.java | 756 ++-- src/ch/ethz/ssh2/StreamGobbler.java | 357 +- .../ethz/ssh2/auth/AuthenticationManager.java | 709 ++-- src/ch/ethz/ssh2/channel/Channel.java | 241 +- .../ethz/ssh2/channel/ChannelInputStream.java | 99 +- src/ch/ethz/ssh2/channel/ChannelManager.java | 2618 ++++++------- .../ssh2/channel/ChannelOutputStream.java | 106 +- .../ssh2/channel/IChannelWorkerThread.java | 8 +- .../ethz/ssh2/channel/LocalAcceptThread.java | 190 +- .../ethz/ssh2/channel/RemoteAcceptThread.java | 155 +- .../ssh2/channel/RemoteForwardingData.java | 14 +- .../ssh2/channel/RemoteX11AcceptThread.java | 238 +- src/ch/ethz/ssh2/channel/StreamForwarder.java | 154 +- src/ch/ethz/ssh2/channel/X11ServerData.java | 11 +- src/ch/ethz/ssh2/crypto/Base64.java | 246 +- src/ch/ethz/ssh2/crypto/CryptoWishList.java | 18 +- src/ch/ethz/ssh2/crypto/KeyMaterial.java | 111 +- src/ch/ethz/ssh2/crypto/PEMDecoder.java | 522 ++- src/ch/ethz/ssh2/crypto/PEMStructure.java | 14 +- src/ch/ethz/ssh2/crypto/SimpleDERReader.java | 280 +- src/ch/ethz/ssh2/crypto/cipher/AES.java | 1221 +++--- .../ethz/ssh2/crypto/cipher/BlockCipher.java | 11 +- .../crypto/cipher/BlockCipherFactory.java | 167 +- src/ch/ethz/ssh2/crypto/cipher/BlowFish.java | 639 ++-- src/ch/ethz/ssh2/crypto/cipher/CBCMode.java | 129 +- src/ch/ethz/ssh2/crypto/cipher/CTRMode.java | 100 +- .../ssh2/crypto/cipher/CipherInputStream.java | 238 +- .../crypto/cipher/CipherOutputStream.java | 233 +- src/ch/ethz/ssh2/crypto/cipher/DES.java | 625 ++-- src/ch/ethz/ssh2/crypto/cipher/DESede.java | 126 +- .../ethz/ssh2/crypto/cipher/NullCipher.java | 42 +- src/ch/ethz/ssh2/crypto/dh/DhExchange.java | 236 +- .../ethz/ssh2/crypto/dh/DhGroupExchange.java | 174 +- src/ch/ethz/ssh2/crypto/digest/Digest.java | 20 +- src/ch/ethz/ssh2/crypto/digest/HMAC.java | 162 +- .../ssh2/crypto/digest/HashForSSH2Types.java | 124 +- src/ch/ethz/ssh2/crypto/digest/MAC.java | 122 +- src/ch/ethz/ssh2/crypto/digest/MD5.java | 421 +-- src/ch/ethz/ssh2/crypto/digest/SHA1.java | 455 ++- src/ch/ethz/ssh2/log/Logger.java | 66 +- .../PacketChannelOpenConfirmation.java | 106 +- .../packets/PacketChannelOpenFailure.java | 106 +- .../packets/PacketChannelWindowAdjust.java | 88 +- .../ethz/ssh2/packets/PacketDisconnect.java | 90 +- .../PacketGlobalCancelForwardRequest.java | 52 +- .../packets/PacketGlobalForwardRequest.java | 52 +- src/ch/ethz/ssh2/packets/PacketIgnore.java | 60 +- src/ch/ethz/ssh2/packets/PacketKexDHInit.java | 36 +- .../ethz/ssh2/packets/PacketKexDHReply.java | 81 +- .../ssh2/packets/PacketKexDhGexGroup.java | 54 +- .../ethz/ssh2/packets/PacketKexDhGexInit.java | 36 +- .../ssh2/packets/PacketKexDhGexReply.java | 62 +- .../ssh2/packets/PacketKexDhGexRequest.java | 48 +- .../packets/PacketKexDhGexRequestOld.java | 37 +- src/ch/ethz/ssh2/packets/PacketKexInit.java | 286 +- src/ch/ethz/ssh2/packets/PacketNewKeys.java | 67 +- .../packets/PacketOpenDirectTCPIPChannel.java | 90 +- .../packets/PacketOpenSessionChannel.java | 99 +- .../ssh2/packets/PacketServiceAccept.java | 79 +- .../ssh2/packets/PacketServiceRequest.java | 79 +- .../packets/PacketSessionExecCommand.java | 51 +- .../ssh2/packets/PacketSessionPtyRequest.java | 85 +- .../ssh2/packets/PacketSessionStartShell.java | 46 +- .../PacketSessionSubsystemRequest.java | 53 +- .../ssh2/packets/PacketSessionX11Request.java | 83 +- .../ssh2/packets/PacketUserauthBanner.java | 94 +- .../ssh2/packets/PacketUserauthFailure.java | 60 +- .../packets/PacketUserauthInfoRequest.java | 138 +- .../packets/PacketUserauthInfoResponse.java | 41 +- .../PacketUserauthRequestInteractive.java | 55 +- .../packets/PacketUserauthRequestNone.java | 97 +- .../PacketUserauthRequestPassword.java | 85 +- .../PacketUserauthRequestPublicKey.java | 105 +- src/ch/ethz/ssh2/packets/Packets.java | 277 +- src/ch/ethz/ssh2/packets/TypesReader.java | 243 +- src/ch/ethz/ssh2/packets/TypesWriter.java | 275 +- src/ch/ethz/ssh2/sftp/AttrTextHints.java | 48 +- src/ch/ethz/ssh2/sftp/AttribBits.java | 192 +- src/ch/ethz/ssh2/sftp/AttribFlags.java | 201 +- src/ch/ethz/ssh2/sftp/AttribPermissions.java | 32 +- src/ch/ethz/ssh2/sftp/AttribTypes.java | 24 +- src/ch/ethz/ssh2/sftp/ErrorCodes.java | 157 +- src/ch/ethz/ssh2/sftp/OpenFlags.java | 329 +- src/ch/ethz/ssh2/sftp/Packet.java | 64 +- src/ch/ethz/ssh2/signature/DSAPrivateKey.java | 88 +- src/ch/ethz/ssh2/signature/DSAPublicKey.java | 54 +- src/ch/ethz/ssh2/signature/DSASHA1Verify.java | 251 +- src/ch/ethz/ssh2/signature/DSASignature.java | 32 +- src/ch/ethz/ssh2/signature/RSAPrivateKey.java | 54 +- src/ch/ethz/ssh2/signature/RSAPublicKey.java | 32 +- src/ch/ethz/ssh2/signature/RSASHA1Verify.java | 395 +- src/ch/ethz/ssh2/signature/RSASignature.java | 22 +- .../ssh2/transport/ClientServerHello.java | 171 +- src/ch/ethz/ssh2/transport/KexManager.java | 1072 +++--- src/ch/ethz/ssh2/transport/KexParameters.java | 31 +- src/ch/ethz/ssh2/transport/KexState.java | 33 +- .../ethz/ssh2/transport/MessageHandler.java | 7 +- .../ssh2/transport/NegotiateException.java | 7 +- .../ssh2/transport/NegotiatedParameters.java | 27 +- .../ssh2/transport/TransportConnection.java | 380 +- .../ethz/ssh2/transport/TransportManager.java | 1135 +++--- src/ch/ethz/ssh2/util/PasswordField.java | 228 +- src/ch/ethz/ssh2/util/PasswordReader.java | 28 +- src/ch/ethz/ssh2/util/TimeoutService.java | 222 +- src/ch/ethz/ssh2/util/Tokenizer.java | 70 +- src/edu/caltech/hep/dcapj/Config.java | 11 +- src/edu/caltech/hep/dcapj/PnfsUtil.java | 235 +- src/edu/caltech/hep/dcapj/dCacheFile.java | 386 +- .../dCacheFileChannelProviderFactory.java | 56 +- src/edu/caltech/hep/dcapj/dCapLayer.java | 41 +- .../hep/dcapj/io/dCacheFileInputStream.java | 32 +- .../hep/dcapj/io/dCacheFileOutputStream.java | 41 +- .../hep/dcapj/nio/dCacheFileChannel.java | 128 +- src/edu/caltech/hep/dcapj/test/Main.java | 8 +- src/edu/caltech/hep/dcapj/test/Main2.java | 10 +- src/edu/caltech/hep/dcapj/test/Main3.java | 87 +- .../hep/dcapj/util/ControlConnection.java | 14 +- .../dcapj/util/DataConnectionCallback.java | 4 +- .../caltech/hep/dcapj/util/IOCallback.java | 10 +- src/edu/caltech/hep/dcapj/util/Server.java | 18 +- src/edu/caltech/hep/dcapj/util/ServerNIO.java | 18 +- src/lia/gsi/ClientTest.java | 35 +- src/lia/gsi/FDTGSIServer.java | 22 +- src/lia/gsi/GSIServer.java | 430 ++- src/lia/gsi/authz/GridMap.java | 949 +++-- src/lia/gsi/authz/GridMapAuthorization.java | 50 +- .../gsi/authz/LocalMappingAuthorization.java | 88 +- src/lia/gsi/net/GSIBaseServer.java | 73 +- src/lia/gsi/net/GSIGssSocketFactory.java | 307 +- src/lia/gsi/net/Peer.java | 38 +- src/lia/gsi/ssh/GSIAuthenticationClient.java | 302 +- src/lia/gsi/ssh/TextSSHClient.java | 96 +- src/lia/util/net/common/AbstractBPool.java | 21 +- .../util/net/common/AbstractFDTCloseable.java | 111 +- .../util/net/common/AbstractFDTIOEntity.java | 20 +- src/lia/util/net/common/Config.java | 530 ++- src/lia/util/net/common/ControlStream.java | 97 +- src/lia/util/net/common/DDCopy.java | 428 +-- .../util/net/common/DirectByteBufferPool.java | 25 +- src/lia/util/net/common/FDTBuffer.java | 15 +- src/lia/util/net/common/FDTBufferPool.java | 192 +- src/lia/util/net/common/FDTCloseable.java | 12 +- src/lia/util/net/common/FDTCommandLine.java | 23 +- src/lia/util/net/common/FDTVersion.java | 54 +- .../util/net/common/FileChannelProvider.java | 14 +- .../common/FileChannelProviderFactory.java | 3 +- .../util/net/common/GSISSHControlStream.java | 476 ++- src/lia/util/net/common/HeaderBufferPool.java | 14 +- .../common/InvalidFDTParameterException.java | 12 +- src/lia/util/net/common/KernelTest.java | 107 +- src/lia/util/net/common/LocalHost.java | 230 +- src/lia/util/net/common/MassStorage.java | 15 +- src/lia/util/net/common/NetMatcher.java | 101 +- src/lia/util/net/common/NetloggerRecord.java | 78 +- src/lia/util/net/common/SSHControlStream.java | 143 +- .../util/net/common/StoragePathDecoder.java | 79 +- src/lia/util/net/common/Test.java | 40 +- src/lia/util/net/common/Test2MD5Sum.java | 55 +- src/lia/util/net/common/Utils.java | 84 +- src/lia/util/net/copy/Accountable.java | 12 +- src/lia/util/net/copy/AccountableEntity.java | 23 +- src/lia/util/net/copy/FDT.java | 289 +- src/lia/util/net/copy/FDTMain.java | 19 +- src/lia/util/net/copy/FDTReaderSession.java | 27 +- src/lia/util/net/copy/FDTServer.java | 52 +- src/lia/util/net/copy/FDTSession.java | 222 +- src/lia/util/net/copy/FDTSessionManager.java | 8 +- src/lia/util/net/copy/FDTWriterSession.java | 9 +- src/lia/util/net/copy/FileBlock.java | 25 +- src/lia/util/net/copy/FileBlockConsumer.java | 11 +- src/lia/util/net/copy/FileBlockProducer.java | 10 +- src/lia/util/net/copy/FileReaderSession.java | 20 +- src/lia/util/net/copy/FileSession.java | 28 +- src/lia/util/net/copy/FileWriterSession.java | 78 +- src/lia/util/net/copy/IOSession.java | 24 +- src/lia/util/net/copy/PartitionMap.java | 44 +- .../PosixFSFileChannelProviderFactory.java | 40 +- .../util/net/copy/disk/DiskReaderManager.java | 20 +- .../util/net/copy/disk/DiskReaderTask.java | 230 +- .../util/net/copy/disk/DiskWriterManager.java | 62 +- .../util/net/copy/disk/DiskWriterTask.java | 49 +- .../net/copy/disk/GenericDiskManager.java | 24 +- .../util/net/copy/disk/GenericDiskTask.java | 8 +- src/lia/util/net/copy/disk/ResumeManager.java | 18 +- .../util/net/copy/filters/Postprocessor.java | 3 +- .../util/net/copy/filters/Preprocessor.java | 5 +- .../util/net/copy/filters/ProcessorInfo.java | 43 +- .../examples/FirewallFileExtension.java | 22 +- .../filters/examples/FirewallFileNames.java | 26 +- .../copy/filters/examples/FixUserHome.java | 21 +- .../net/copy/filters/examples/PostRename.java | 4 +- .../copy/filters/examples/PostZipFilter.java | 14 +- .../net/copy/filters/examples/PreRename.java | 4 +- .../copy/filters/examples/PreZipFilter.java | 20 +- src/lia/util/net/copy/gui/AboutDialog.java | 265 +- .../net/copy/gui/ClientSessionManager.java | 476 +-- src/lia/util/net/copy/gui/ConnectDialog.java | 180 +- .../util/net/copy/gui/CustomLogHandler.java | 156 +- .../util/net/copy/gui/CustomPrintStream.java | 58 +- .../copy/gui/DummyRemoteSessionManager.java | 182 +- src/lia/util/net/copy/gui/EnhancedJPanel.java | 64 +- src/lia/util/net/copy/gui/FDTPropsDialog.java | 554 ++- src/lia/util/net/copy/gui/FolderFrame.java | 719 ++-- src/lia/util/net/copy/gui/FolderTable.java | 3321 ++++++++--------- .../net/copy/gui/GUISSHControlStream.java | 80 +- src/lia/util/net/copy/gui/HelpDialog.java | 151 +- .../util/net/copy/gui/PreferencesHandler.java | 156 +- src/lia/util/net/copy/gui/ProgressBarUI.java | 264 +- .../net/copy/gui/RemoteSessionManager.java | 1185 +++--- src/lia/util/net/copy/gui/StatusBar.java | 426 +-- .../util/net/copy/gui/TransferMonitor.java | 376 +- .../net/copy/gui/session/LocalSession.java | 596 +-- .../net/copy/gui/session/RemoteSession.java | 365 +- .../util/net/copy/gui/session/Session.java | 150 +- .../copy/monitoring/ApMonReportingTask.java | 208 +- .../ClientTransportMonitorTask.java | 45 +- .../copy/monitoring/ConsoleReportingTask.java | 23 +- .../DiskReaderManagerMonitoringTask.java | 27 +- .../DiskWriterManagerMonitoringTask.java | 18 +- .../monitoring/DiskWriterMonitoringTask.java | 86 +- .../monitoring/FDTInternalMonitoringTask.java | 139 +- .../net/copy/monitoring/FDTReportingTask.java | 111 +- .../monitoring/FDTSessionMonitoringTask.java | 8 +- .../monitoring/NetSessionMonitoringTask.java | 19 +- .../AbstractAccountableMonitoringTask.java | 101 +- .../net/copy/monitoring/jmx/DBPoolJMX.java | 14 +- .../copy/monitoring/jmx/DBPoolJMXMBean.java | 1 + .../copy/monitoring/lisa/CmdCheckerTask.java | 25 +- .../lisa/HostPropertiesMonitor.java | 830 ++-- .../monitoring/lisa/LISAReportingTask.java | 181 +- .../monitoring/lisa/LisaCtrlNotifier.java | 6 +- .../lisa/MacHostPropertiesMonitor.java | 936 +++-- .../net/copy/monitoring/lisa/MonClient.java | 30 +- .../net/copy/monitoring/lisa/ProcReader.java | 2631 +++++++------ .../net/copy/monitoring/lisa/cmdExec.java | 2180 +++++------ .../copy/monitoring/lisa/net/PatternUtil.java | 98 +- .../copy/monitoring/lisa/net/Statistics.java | 46 +- .../lisa/net/dev/InterfaceHandler.java | 1977 +++++----- .../lisa/net/dev/InterfaceStatistics.java | 521 +-- .../net/dev/InterfaceStatisticsStatic.java | 392 +- .../copy/monitoring/lisa/net/dev/MTUSet.java | 19 +- .../lisa/net/dev/TXQueueLenSet.java | 19 +- .../lisa/net/netstat/Connection.java | 271 +- .../monitoring/lisa/net/netstat/Netstat.java | 520 +-- .../lisa/net/netstat/NetstatHandler.java | 516 +-- .../lisa/net/statistics/IPStatistics.java | 880 ++--- .../net/statistics/StatisticsHandler.java | 1638 ++++---- .../lisa/net/statistics/TCPExtStatistics.java | 2399 ++++++------ .../lisa/net/statistics/TCPStatistics.java | 547 +-- .../lisa/net/statistics/UDPStatistics.java | 215 +- .../monitoring/lisa/xdr/SocketFactory.java | 381 +- .../copy/monitoring/lisa/xdr/XDRClient.java | 254 +- .../monitoring/lisa/xdr/XDRDataInput.java | 86 +- .../monitoring/lisa/xdr/XDRDataOutput.java | 34 +- .../monitoring/lisa/xdr/XDRGenericComm.java | 398 +- .../monitoring/lisa/xdr/XDRInputStream.java | 372 +- .../monitoring/lisa/xdr/XDRMLMappings.java | 13 +- .../copy/monitoring/lisa/xdr/XDRMessage.java | 90 +- .../lisa/xdr/XDRMessageNotifier.java | 2 +- .../monitoring/lisa/xdr/XDRNamedPipe.java | 65 +- .../monitoring/lisa/xdr/XDROutputStream.java | 186 +- .../lisa/xdr/XDRRandomAccessFile.java | 243 +- .../monitoring/lisa/xdr/XDRSerializable.java | 8 +- .../monitoring/lisa/xdr/XDRTcpSocket.java | 83 +- .../net/copy/transport/ControlChannel.java | 106 +- .../transport/ControlChannelNotifier.java | 9 +- src/lia/util/net/copy/transport/CtrlMsg.java | 147 +- .../net/copy/transport/FDTKeyAttachement.java | 34 +- .../net/copy/transport/FDTListFilesMsg.java | 3 +- .../copy/transport/FDTProcolException.java | 12 +- .../transport/FDTReaderKeyAttachement.java | 8 +- .../copy/transport/FDTSessionConfigMsg.java | 27 +- .../transport/FDTWriterKeyAttachement.java | 78 +- .../FDTWriterKeyAttachementComparator.java | 21 +- .../util/net/copy/transport/PingDaemon.java | 283 +- .../net/copy/transport/SocketReaderTask.java | 24 +- .../util/net/copy/transport/SocketTask.java | 26 +- .../net/copy/transport/SocketWriterTask.java | 35 +- .../net/copy/transport/SpeedLimitManager.java | 37 +- .../util/net/copy/transport/SpeedLimiter.java | 7 +- .../net/copy/transport/TCPSessionReader.java | 70 +- .../net/copy/transport/TCPSessionWriter.java | 99 +- .../copy/transport/TCPTransportProvider.java | 120 +- .../net/copy/transport/gui/FileHandler.java | 48 +- .../copy/transport/gui/GUIControlChannel.java | 125 +- .../gui/GUIControlChannelNotifier.java | 6 +- .../net/copy/transport/gui/GUIMessage.java | 42 +- .../transport/gui/ServerSessionManager.java | 846 ++--- .../transport/internal/FDTSelectionKey.java | 44 +- .../transport/internal/SelectionHandler.java | 7 +- .../transport/internal/SelectionManager.java | 349 +- src/lia/util/net/jiperf/ByteBufferPool.java | 61 +- src/lia/util/net/jiperf/JIperf.java | 212 +- src/lia/util/net/jiperf/JIperfClient.java | 377 +- src/lia/util/net/jiperf/JIperfServer.java | 341 +- .../net/jiperf/control/ControlStream.java | 157 +- .../net/jiperf/control/StreamConsumer.java | 5 +- .../util/net/jiperf/control/StreamPumper.java | 37 +- src/lia/util/net/jiperf/test/FDTNetPerf.java | 134 +- .../commons/cli/AlreadySelectedException.java | 10 +- src/org/apache/commons/cli/BasicParser.java | 19 +- src/org/apache/commons/cli/CommandLine.java | 160 +- .../apache/commons/cli/CommandLineParser.java | 24 +- src/org/apache/commons/cli/GnuParser.java | 134 +- src/org/apache/commons/cli/HelpFormatter.java | 780 ++-- .../commons/cli/MissingArgumentException.java | 12 +- .../commons/cli/MissingOptionException.java | 12 +- src/org/apache/commons/cli/Option.java | 542 +-- src/org/apache/commons/cli/OptionBuilder.java | 137 +- src/org/apache/commons/cli/OptionGroup.java | 82 +- src/org/apache/commons/cli/Options.java | 186 +- .../apache/commons/cli/ParseException.java | 15 +- src/org/apache/commons/cli/Parser.java | 172 +- .../commons/cli/PatternOptionBuilder.java | 115 +- src/org/apache/commons/cli/PosixParser.java | 244 +- src/org/apache/commons/cli/TypeHandler.java | 77 +- .../cli/UnrecognizedOptionException.java | 12 +- src/org/apache/commons/cli/overview.html | 37 +- src/org/apache/commons/cli/package.html | 3 +- .../apache/commons/lang/math/NumberUtils.java | 560 +-- 351 files changed, 38954 insertions(+), 41331 deletions(-) diff --git a/src/apmon/ApMon.java b/src/apmon/ApMon.java index 2aab73c..397b713 100644 --- a/src/apmon/ApMon.java +++ b/src/apmon/ApMon.java @@ -32,42 +32,20 @@ package apmon; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.URL; -import java.net.URLConnection; -import java.net.UnknownHostException; -import java.util.Date; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Random; -import java.util.StringTokenizer; -import java.util.Vector; -import java.util.logging.FileHandler; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; - import apmon.host.cmdExec; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.logging.*; + /** - * Data structure used for sending monitoring data to a MonaLisa module. + * Data structure used for sending monitoring data to a MonaLisa module. * The data is packed in UDP datagrams, in XDR format. * A datagram has the following structure: * - header which contains the ApMon version and the password for the MonALISA - * host and has the following syntax: v:p: - * - cluster name (string) + * host and has the following syntax: v:p: + * - cluster name (string) * - node name (string) * - number of parameters (int) * - for each parameter: name (string), value type (int), value @@ -78,277 +56,302 @@ * 2) multiple parameters in a packet (with the function sendParameters()) *
* ApMon can be configured to send periodically datagrams with monitoring information - * concerning the current application or the whole system. Some of the monitoring + * concerning the current application or the whole system. Some of the monitoring * information is only available on Linux systems. */ public class ApMon { - static final String APMON_VERSION = "2.2.7"; - public static final int MAX_DGRAM_SIZE = 8192; - - /** < Maximum UDP datagram size. */ + /** + * < Maximum UDP datagram size. + */ public static final int XDR_STRING = 0; - - /** < Used to code the string data type */ + /** + * < Used to code the string data type + */ public static final int XDR_INT32 = 2; - - /** < Used to code the 4 bytes integer data type */ + /** + * < Used to code the 4 bytes integer data type + */ public static final int XDR_REAL32 = 4; - - /** < Used to code the 4 bytes real data type */ + /** + * < Used to code the 4 bytes real data type + */ public static final int XDR_REAL64 = 5; - - /** < Used to code the 8 bytes real data type */ + /** + * < Used to code the 8 bytes real data type + */ public static final int DEFAULT_PORT = 8884; - - /** < The default port on which MonALISA listens */ - - /** Time interval (in sec) at which job monitoring datagrams are sent. */ + /** + * Time interval (in sec) at which job monitoring datagrams are sent. + */ public static final int JOB_MONITOR_INTERVAL = 20; - /** Time interval (in sec) at which system monitoring datagams are sent. */ + /** < The default port on which MonALISA listens */ + /** + * Time interval (in sec) at which system monitoring datagams are sent. + */ public static final int SYS_MONITOR_INTERVAL = 20; - - /** Time interval (in sec) at which the configuration files are checked for changes. */ + /** + * Time interval (in sec) at which the configuration files are checked for changes. + */ public static final int RECHECK_INTERVAL = 600; - - /** The maxim number of mesages that will be sent to MonALISA */ + /** + * The maxim number of mesages that will be sent to MonALISA + */ public static final int MAX_MSG_RATE = 20; - /** * The number of time intervals at which ApMon sends general system monitoring information (considering the time * intervals at which ApMon sends system monitoring information). */ public static final int GEN_MONITOR_INTERVALS = 100; - - /** Constant that indicates this object was initialized from a file. */ + static final String APMON_VERSION = "2.2.7"; + /** + * Constant that indicates this object was initialized from a file. + */ static final int FILE_INIT = 1; - /** Constant that indicates this object was initialized from a list. */ + /** + * Constant that indicates this object was initialized from a list. + */ static final int LIST_INIT = 2; - /** Constant that indicates this object was initialized directly. */ + /** + * Constant that indicates this object was initialized directly. + */ static final int DIRECT_INIT = 3; + static String osName = System.getProperty("os.name"); + private static Logger logger = Logger.getLogger("apmon"); + /** + * Java type -> XDR Type mapping + **/ + private static Map mValueTypes = new HashMap(); - /** The initialization source (can be a file or a list). */ - Object initSource = null; + static { - /** The initialization type (from file / list / directly). */ - int initType; + try { + LogManager logManager = LogManager.getLogManager(); + + //check if LogManager is already defined + if (logManager.getProperty("handlers") == null) { + try { + FileHandler fh = null; + try { + fh = new FileHandler("apmon.log"); + fh.setFormatter(new SimpleFormatter()); + } catch (Throwable t) { + t.printStackTrace(); + } + + logger.setUseParentHandlers(false); + logger.addHandler(fh); + logger.setLevel(Level.INFO); + } catch (Throwable t) { + System.err.println("[ ApMon ] [ static init ] [ logging ] Unable to load default logger props. Cause:"); + t.printStackTrace(); + } + } else { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "[ ApMon ] [ static init ] [ logging ] uses predefined logging properties"); + } + } + + } catch (Throwable t) { + System.err.println("[ ApMon ] [ static init ] [ logging ] Unable to check/load default logger props. Cause:"); + t.printStackTrace(); + } + + mValueTypes.put(String.class.getName(), new Integer(XDR_STRING)); + mValueTypes.put(Short.class.getName(), new Integer(XDR_INT32)); + mValueTypes.put(Integer.class.getName(), new Integer(XDR_INT32)); + mValueTypes.put(Float.class.getName(), new Integer(XDR_REAL64)); + mValueTypes.put(Double.class.getName(), new Integer(XDR_REAL64)); + + } + + /** + * don't allow a user to send more than MAX_MSG messages per second, in average + */ + protected long prvTime = 0; + protected double prvSent = 0; + protected double prvDrop = 0; + protected long crtTime = 0; + protected long crtSent = 0; + protected long crtDrop = 0; + protected double hWeight = Math.exp(-5.0 / 60.0); + /** < The size of the data inside the datagram */ + /** + * The initialization source (can be a file or a list). + */ + Object initSource = null; + /** + * The initialization type (from file / list / directly). + */ + int initType; /** * The configuration file and the URLs are checked for changes at this numer of seconds (if the network connections * are good). */ long recheckInterval = RECHECK_INTERVAL; - /** * If the configuraion URLs cannot be reloaded, the interval until the next attempt will be increased. This is the * actual value of the interval that is used by ApMon */ long crtRecheckInterval = RECHECK_INTERVAL; - String clusterName; - - /** < The name of the monitored cluster. */ + /** + * < The name of the monitored cluster. + */ String nodeName; - - /** < The name of the monitored node. */ + /** + * < The name of the monitored node. + */ Vector destAddresses; - - /** < The IP addresses where the results will be sent. */ + /** + * < The IP addresses where the results will be sent. + */ Vector destPorts; - - /** < The ports where the destination hosts listen. */ + /** + * < The ports where the destination hosts listen. + */ Vector destPasswds; - - /** < The Passwdords used for the destination hosts. */ + /** + * < The Passwdords used for the destination hosts. + */ byte[] buf; - - /** < The buffer which holds the message data (encoded in XDR). */ + /** + * < The buffer which holds the message data (encoded in XDR). + */ int dgramSize; - - /** < The size of the data inside the datagram */ - /** * Hashtable which holds theinitialization resources (Files, URLs) that must be periodically checked for changes, * and their latest modification times */ Hashtable confResources; - ByteArrayOutputStream baos; - DatagramSocket dgramSocket; - /** * The background thread which performs operations like checking the configuration file/URLs for changes and sending * datagrams with monitoring information. */ BkThread bkThread = null; - - /** Is true if the background thread was started. */ + /** + * Is true if the background thread was started. + */ boolean bkThreadStarted = false; - - /** Protects the variables that hold the settings for the background thread. */ + /** + * Protects the variables that hold the settings for the background thread. + */ Object mutexBack = new Object(); - - /** Used for the wait/notify mechanism in the background thread. */ + /** + * Used for the wait/notify mechanism in the background thread. + */ Object mutexCond = new Object(); - - /** Indicates if any of the settings for the background thread was changed. */ + /** + * Indicates if any of the settings for the background thread was changed. + */ boolean condChanged = false; - - /** These flags indicate changes in the monitoring configuration. */ + /** + * These flags indicate changes in the monitoring configuration. + */ boolean recheckChanged, jobMonChanged, sysMonChanged; - /** * If this flag is set to true, when the value of a parameter cannot be read from proc/, ApMon will not attempt to * include that value in the next datagrams. */ boolean autoDisableMonitoring = true; - - /** If this flag is true, the configuration file / URLs are periodically rechecked for changes. */ + /** + * If this flag is true, the configuration file / URLs are periodically rechecked for changes. + */ boolean confCheck = false; - /** * If this flag is true, packets with system information taken from /proc are periodically sent to MonALISA */ boolean sysMonitoring = false; + // long appPID; /** * If this flag is true, packets with job information taken from /proc are periodically sent to MonALISA */ boolean jobMonitoring = false; - /** * If this flag is true, packets with general system information taken from /proc are periodically sent to MonALISA */ boolean genMonitoring = false; - - /** Job/System monitoring information obtained from /proc is sent at these time intervals */ + /** + * Job/System monitoring information obtained from /proc is sent at these time intervals + */ long jobMonitorInterval = JOB_MONITOR_INTERVAL; - long sysMonitorInterval = SYS_MONITOR_INTERVAL; - int maxMsgRate = MAX_MSG_RATE; - /** * General system monitoring information is sent at a time interval equal to genMonitorIntervals * * sysMonitorInterval. */ int genMonitorIntervals = GEN_MONITOR_INTERVALS; - /** * Hashtables that associate the names of the parameters included in the monitoring datagrams and flags that * indicate whether they are active or not. */ long sysMonitorParams, jobMonitorParams, genMonitorParams; - /** * The time when the last datagram with job monitoring information was sent (in milliseconds since the Epoch). */ long lastJobInfoSend; - /** * The time when the last datagram with job monitoring information was sent (in milliseconds since the Epoch). */ long lastSysInfoSend; - - /** The last value for "utime" for the current process that was read from proc/ (only on Linux). */ + /** + * The last value for "utime" for the current process that was read from proc/ (only on Linux). + */ double lastUtime; - - /** The last value for "stime" for the current process that was read from proc/ (only on Linux). */ + /** + * The last value for "stime" for the current process that was read from proc/ (only on Linux). + */ double lastStime; - - // long appPID; - - /** The name of the host on which ApMon currently runs. */ + /** + * The name of the host on which ApMon currently runs. + */ String myHostname = null; - - /** The IP address of the host on which ApMon currently runs. */ + /** + * The IP address of the host on which ApMon currently runs. + */ String myIP = null; - - /** The number of CPUs on the machine that runs ApMon. */ + /** + * The number of CPUs on the machine that runs ApMon. + */ int numCPUs; - - /** The names of the network interfaces on this machine. */ + /** + * The names of the network interfaces on this machine. + */ Vector netInterfaces = new Vector(); - - /** The IPs of this machine. */ + /** + * The IPs of this machine. + */ Vector allMyIPs = new Vector(); - - /** the cluster name that will be included in the monitoring datagrams */ + /** + * the cluster name that will be included in the monitoring datagrams + */ String sysClusterName = "ApMon_userSend"; - - /** the node name that will be included in the monitoring datagrams */ + /** + * the node name that will be included in the monitoring datagrams + */ String sysNodeName = null; - Vector monJobs = new Vector(); - Hashtable sender = new Hashtable(); - private static Logger logger = Logger.getLogger("apmon"); - - static String osName = System.getProperty("os.name"); - - /** Java type -> XDR Type mapping **/ - private static Map mValueTypes = new HashMap(); - - static { - - try { - LogManager logManager = LogManager.getLogManager(); - - //check if LogManager is already defined - if(logManager.getProperty("handlers") == null) { - try { - FileHandler fh = null; - try { - fh = new FileHandler("apmon.log"); - fh.setFormatter(new SimpleFormatter()); - } catch (Throwable t) { - t.printStackTrace(); - } - - logger.setUseParentHandlers(false); - logger.addHandler(fh); - logger.setLevel(Level.INFO); - }catch(Throwable t) { - System.err.println("[ ApMon ] [ static init ] [ logging ] Unable to load default logger props. Cause:"); - t.printStackTrace(); - } - } else { - if(logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "[ ApMon ] [ static init ] [ logging ] uses predefined logging properties"); - } - } - - } catch(Throwable t) { - System.err.println("[ ApMon ] [ static init ] [ logging ] Unable to check/load default logger props. Cause:"); - t.printStackTrace(); - } - - mValueTypes.put(String.class.getName(), new Integer(XDR_STRING)); - mValueTypes.put(Short.class.getName(), new Integer(XDR_INT32)); - mValueTypes.put(Integer.class.getName(), new Integer(XDR_INT32)); - mValueTypes.put(Float.class.getName(), new Integer(XDR_REAL64)); - mValueTypes.put(Double.class.getName(), new Integer(XDR_REAL64)); - - } - /** * Initializes an ApMon object from a configuration file. - * - * @param filename - * The name of the file which contains the addresses and the ports of the destination hosts (see README - * for details about the structure of this file). - * @throws ApMonException - * , - * SocketException, IOException + * + * @param filename The name of the file which contains the addresses and the ports of the destination hosts (see README + * for details about the structure of this file). + * @throws ApMonException , + * SocketException, IOException */ public ApMon(String filename) throws ApMonException, SocketException, IOException { @@ -359,7 +362,90 @@ public ApMon(String filename) throws ApMonException, SocketException, IOExceptio initSenderRef(); } - /** Add a job pid to monitorized jobs vector */ + /** + * Initializes an ApMon object from a list with URLs. + * + * @param destList The list with URLs. the ports of the destination hosts (see README for details about the structure of + * this file). + * @throws ApMonException , + * SocketException, IOException + */ + public ApMon(Vector destList) throws ApMonException, SocketException, IOException { + initType = LIST_INIT; + initMonitoring(); + initSource = destList; + initialize(destList, true); + initSenderRef(); + } + + /** + * Initializes an ApMon data structure, using arrays instead of a file. + * + * @param destAddresses Array that contains the hostnames or IP addresses of the destination hosts. + * @param destPorts The ports where the MonaLisa modules listen on the destination hosts. + * @throws ApMonException , + * SocketException, IOException + */ + public ApMon(Vector destAddresses, Vector destPorts) throws ApMonException, SocketException, IOException { + this.initType = DIRECT_INIT; + arrayInit(destAddresses, destPorts, null); + initSenderRef(); + } + + /** + * Initializes an ApMon data structure, using arrays instead of a file. + * + * @param destAddresses Array that contains the hostnames or IP addresses of the destination hosts. + * @param destPorts The ports where the MonaLisa modules listen on the destination hosts. + * @param destPasswds The passwords for the destination hosts. + * @throws ApMonException , + * SocketException, IOException + */ + public ApMon(Vector destAddresses, Vector destPorts, Vector destPasswds) throws ApMonException, SocketException, IOException { + this.initType = DIRECT_INIT; + initMonitoring(); + arrayInit(destAddresses, destPorts, destPasswds); + initSenderRef(); + } + + /** + * Sets the ApMon loglevel. The possible values are: "FATAL", "WARNING", "INFO", "FINE", "DEBUG". + */ + public static void setLogLevel(String newLevel_s) { + int i; + String levels_s[] = { + "FATAL", "WARNING", "INFO", "FINE", "DEBUG" + }; + Level levels[] = { + Level.SEVERE, Level.WARNING, Level.INFO, Level.FINE, Level.FINEST + }; + + for (i = 0; i < 5; i++) + if (newLevel_s.equals(levels_s[i])) + break; + + if (i >= 5) { + logger.warning("[ setLogLevel() ] Invalid level value: " + newLevel_s); + return; + } + + logger.info("Setting logging level to " + newLevel_s); + logger.setLevel(levels[i]); + } + + // * Supported in Sun JRE >1.5 (returns -1 in prior versions) + public static int getPID() { + try { + final java.lang.management.RuntimeMXBean rt = java.lang.management.ManagementFactory.getRuntimeMXBean(); + return Integer.parseInt(rt.getName().split("@")[0]); + } catch (Throwable t) { + return -1; + } + } + + /** + * Add a job pid to monitorized jobs vector + */ public void addJobToMonitor(int pid, String workDir, String clusterName, String nodeName) { MonitoredJob job = new MonitoredJob(pid, workDir, clusterName, nodeName); if (!monJobs.contains(job)) @@ -368,7 +454,9 @@ else if (logger.isLoggable(Level.WARNING)) logger.warning("Job <" + job + "> already exsist."); } - /** Remove a pid form monitorized jobs vector */ + /** + * Remove a pid form monitorized jobs vector + */ public void removeJobToMonitor(int pid) { int i; for (i = 0; i < monJobs.size(); i++) { @@ -380,7 +468,9 @@ public void removeJobToMonitor(int pid) { } } - /** This is used to set the cluster and node name for the system-related monitored data. */ + /** + * This is used to set the cluster and node name for the system-related monitored data. + */ public void setMonitorClusterNode(String cName, String nName) { if (cName != null) sysClusterName = cName; @@ -390,16 +480,13 @@ public void setMonitorClusterNode(String cName, String nName) { /** * Initializes an ApMon object from a configuration file. - * - * @param filename - * The name of the file which contains the addresses and the ports of the destination hosts (see README - * for details about the structure of this file). - * @param firstTime - * If it is true, all the initializations will be done (the object is being constructed now). Else, only - * some structures will be reinitialized. - * @throws ApMonException - * , - * SocketException, IOException + * + * @param filename The name of the file which contains the addresses and the ports of the destination hosts (see README + * for details about the structure of this file). + * @param firstTime If it is true, all the initializations will be done (the object is being constructed now). Else, only + * some structures will be reinitialized. + * @throws ApMonException , + * SocketException, IOException */ void initialize(String filename, boolean firstTime) throws ApMonException, SocketException, IOException { Vector destAddresses = new Vector(); @@ -429,33 +516,12 @@ void initialize(String filename, boolean firstTime) throws ApMonException, Socke /** * Initializes an ApMon object from a list with URLs. - * - * @param initSource - * The list with URLs. the ports of the destination hosts (see README for details about the structure of - * this file). - * @throws ApMonException - * , - * SocketException, IOException - */ - public ApMon(Vector destList) throws ApMonException, SocketException, IOException { - initType = LIST_INIT; - initMonitoring(); - initSource = destList; - initialize(destList, true); - initSenderRef(); - } - - /** - * Initializes an ApMon object from a list with URLs. - * - * @param initSource - * The list with URLs. - * @param firstTime - * If it is true, all the initializations will be done (the object is being constructed now). Else, only - * some structures will be reinitialized. - * @throws ApMonException - * , - * SocketException, IOException + * + * @param destList The list with URLs. + * @param firstTime If it is true, all the initializations will be done (the object is being constructed now). Else, only + * some structures will be reinitialized. + * @throws ApMonException , + * SocketException, IOException */ void initialize(Vector destList, boolean firstTime) throws ApMonException, SocketException, IOException { int i; @@ -495,64 +561,17 @@ void initialize(Vector destList, boolean firstTime) throws ApMonException, Socke } } - /** - * Initializes an ApMon data structure, using arrays instead of a file. - * - * @param nDestinations - * The number of destination hosts where the results will be sent. - * @param destAddresses - * Array that contains the hostnames or IP addresses of the destination hosts. - * @param destPorts - * The ports where the MonaLisa modules listen on the destination hosts. - * @throws ApMonException - * , - * SocketException, IOException - */ - public ApMon(Vector destAddresses, Vector destPorts) throws ApMonException, SocketException, IOException { - this.initType = DIRECT_INIT; - arrayInit(destAddresses, destPorts, null); - initSenderRef(); - } - - /** - * Initializes an ApMon data structure, using arrays instead of a file. - * - * @param nDestinations - * The number of destination hosts where the results will be sent. - * @param destAddresses - * Array that contains the hostnames or IP addresses of the destination hosts. - * @param destPorts - * The ports where the MonaLisa modules listen on the destination hosts. - * @param destPasswds - * The passwords for the destination hosts. - * @throws ApMonException - * , - * SocketException, IOException - */ - public ApMon(Vector destAddresses, Vector destPorts, Vector destPasswds) throws ApMonException, SocketException, IOException { - this.initType = DIRECT_INIT; - initMonitoring(); - arrayInit(destAddresses, destPorts, destPasswds); - initSenderRef(); - } - /** * Parses a configuration file which contains addresses, ports and passwords for the destination hosts and puts the * results in the vectors given as parameters. - * - * @param filename - * The name of the configuration file. - * @param destAddresses - * Will contain the destination addresses. - * @param destPorts - * Will contain the ports from the destination hosts. - * @param destPasswds - * Will contain the passwords for the destination hosts. - * @param confRes - * Will contain the configuration resources (file, URLs). - * @throws IOException - * , - * ApMonException + * + * @param filename The name of the configuration file. + * @param destAddresses Will contain the destination addresses. + * @param destPorts Will contain the ports from the destination hosts. + * @param destPasswds Will contain the passwords for the destination hosts. + * @param confRes Will contain the configuration resources (file, URLs). + * @throws IOException , + * ApMonException */ void loadFile(String filename, Vector destAddresses, Vector destPorts, Vector destPasswds, Hashtable confRes) throws IOException, ApMonException { String line, tmp; @@ -586,15 +605,11 @@ void loadFile(String filename, Vector destAddresses, Vector destPorts, Vector de /** * Parses a web page which contains addresses, ports and passwords for the destination hosts and puts the results in * the vectors given as parameters. - * - * @param destAddresses - * Will contain the destination addresses. - * @param destPorts - * Will contain the ports from the destination hosts. - * @param destPasswds - * Will contain the passwords for the destination hosts. - * @param confRes - * Will contain the configuration resources (file, URLs). + * + * @param destAddresses Will contain the destination addresses. + * @param destPorts Will contain the ports from the destination hosts. + * @param destPasswds Will contain the passwords for the destination hosts. + * @param confRes Will contain the configuration resources (file, URLs). */ void loadURL(String url, Vector destAddresses, Vector destPorts, Vector destPasswds, Hashtable confRes) throws IOException, ApMonException { @@ -639,15 +654,11 @@ void loadURL(String url, Vector destAddresses, Vector destPorts, Vector destPass /** * Parses a line from a (local or remote) configuration file and adds the address and the port to the vectors that * are given as parameters. - * - * @param line - * The line to be parsed. - * @param destAddresses - * Contains destination addresses. - * @param destPorts - * Contains the ports from the destination hosts. - * @param destPasswds - * Contains the passwords for the destination hosts. + * + * @param line The line to be parsed. + * @param destAddresses Contains destination addresses. + * @param destPorts Contains the ports from the destination hosts. + * @param destPasswds Contains the passwords for the destination hosts. */ void addToDestinations(String line, Vector destAddresses, Vector destPorts, Vector destPasswds) { String addr; @@ -682,18 +693,12 @@ void addToDestinations(String line, Vector destAddresses, Vector destPorts, Vect /** * Internal method used to initialize an ApMon data structure. - * - * @param nDestinations - * The number of destination hosts where the results will be sent. - * @param destAddresses - * Array that contains the hostnames or IP addresses of the destination hosts. - * @param destPorts - * The ports where the MonaLisa modules listen on the destination hosts. - * @param destPasswds - * The passwords for the destination hosts. - * @throws ApMonException - * , - * SocketException, IOException + * + * @param destAddresses Array that contains the hostnames or IP addresses of the destination hosts. + * @param destPorts The ports where the MonaLisa modules listen on the destination hosts. + * @param destPasswds The passwords for the destination hosts. + * @throws ApMonException , + * SocketException, IOException */ void arrayInit(Vector destAddresses, Vector destPorts, Vector destPasswds) throws ApMonException, SocketException, IOException { @@ -702,21 +707,14 @@ void arrayInit(Vector destAddresses, Vector destPorts, Vector destPasswds) throw /** * Internal method used to initialize an ApMon data structure. - * - * @param nDestinations - * The number of destination hosts where the results will be sent. - * @param destAddresses - * Array that contains the hostnames or IP addresses of the destination hosts. - * @param destPorts - * The ports where the MonaLisa modules listen on the destination hosts. - * @param destPasswds - * The passwords for the destination hosts. - * @param firstTime - * If it is true, all the initializations will be done (the object is being constructed now). Else, only - * some of the data structures will be reinitialized. - * @throws ApMonException - * , - * SocketException, IOException + * + * @param destAddresses Array that contains the hostnames or IP addresses of the destination hosts. + * @param destPorts The ports where the MonaLisa modules listen on the destination hosts. + * @param destPasswds The passwords for the destination hosts. + * @param firstTime If it is true, all the initializations will be done (the object is being constructed now). Else, only + * some of the data structures will be reinitialized. + * @throws ApMonException , + * SocketException, IOException */ void arrayInit(Vector destAddresses, Vector destPorts, Vector destPasswds, boolean firstTime) throws ApMonException, SocketException, IOException { @@ -798,27 +796,23 @@ void arrayInit(Vector destAddresses, Vector destPorts, Vector destPasswds, boole setConfRecheck(confCheck, recheckInterval); } - /** For backward compatibility. */ + /** + * For backward compatibility. + */ public void sendTimedParameters(String clusterName, String nodeName, int nParams, Vector paramNames, Vector valueTypes, Vector paramValues, int timestamp) throws ApMonException, UnknownHostException, SocketException, IOException { sendTimedParameters(clusterName, nodeName, nParams, paramNames, paramValues, timestamp); } /** * Sends a set of parameters and thier values to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramNames - * Vector with the names of the parameters. - * @param paramValues - * Vector with the values of the parameters. - * @throws ApMonException - * , - * UnknownHostException, SocketException - * @param timestamp - * The user's timestamp + * + * @param clusterName The name of the cluster that is monitored. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramNames Vector with the names of the parameters. + * @param paramValues Vector with the values of the parameters. + * @param timestamp The user's timestamp + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendTimedParameters(String clusterName, String nodeName, int nParams, Vector paramNames, Vector paramValues, int timestamp) throws ApMonException, UnknownHostException, SocketException, IOException { @@ -832,7 +826,7 @@ public void sendTimedParameters(String clusterName, String nodeName, int nParams this.clusterName = clusterName; if (nodeName != null) - /** the user provided a name */ + /** the user provided a name */ this.nodeName = new String(nodeName); else { /** set the node name to the node's IP */ @@ -908,50 +902,44 @@ public void sendTimedParameters(String clusterName, String nodeName, int nParams } // synchronized } - /** For backward compatibility. */ + /** + * For backward compatibility. + */ public void sendParameters(String clusterName, String nodeName, int nParams, Vector paramNames, Vector valueTypes, Vector paramValues) throws ApMonException, UnknownHostException, SocketException, IOException { sendParameters(clusterName, nodeName, nParams, paramNames, paramValues); } /** * Sends a set of parameters and thier values to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramNames - * Vector with the names of the parameters. - * @param paramValues - * Vector with the values of the parameters. - * @throws ApMonException - * , - * UnknownHostException, SocketException + * + * @param clusterName The name of the cluster that is monitored. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramNames Vector with the names of the parameters. + * @param paramValues Vector with the values of the parameters. + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendParameters(String clusterName, String nodeName, int nParams, Vector paramNames, Vector paramValues) throws ApMonException, UnknownHostException, SocketException, IOException { sendTimedParameters(clusterName, nodeName, nParams, paramNames, paramValues, -1); } - /** For backward compatibility. */ + /** + * For backward compatibility. + */ public void sendParameter(String clusterName, String nodeName, String paramName, int valueType, Object paramValue) throws ApMonException, UnknownHostException, SocketException, IOException { sendParameter(clusterName, nodeName, paramName, paramValue); } /** * Sends a parameter and its value to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in - * the previous datagram. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramName - * The name of the parameter. - * @param paramValue - * The value of the parameter. - * @throws ApMonException - * , - * UnknownHostException, SocketException + * + * @param clusterName The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in + * the previous datagram. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramName The name of the parameter. + * @param paramValue The value of the parameter. + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendParameter(String clusterName, String nodeName, String paramName, Object paramValue) throws ApMonException, UnknownHostException, SocketException, IOException { Vector paramNames = new Vector(); @@ -962,28 +950,24 @@ public void sendParameter(String clusterName, String nodeName, String paramName, sendParameters(clusterName, nodeName, 1, paramNames, paramValues); } - /** For backward compatibility. */ + /** + * For backward compatibility. + */ public void sendTimedParameter(String clusterName, String nodeName, String paramName, int valueType, Object paramValue, int timestamp) throws ApMonException, UnknownHostException, SocketException, IOException { sendTimedParameter(clusterName, nodeName, paramName, paramValue, timestamp); } /** * Sends a parameter and its value to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in - * the previous datagram. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramName - * The name of the parameter. - * @param paramValue - * The value of the parameter. - * @throws ApMonException - * , - * UnknownHostException, SocketException - * @param timestamp - * The user's timestamp + * + * @param clusterName The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in + * the previous datagram. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramName The name of the parameter. + * @param paramValue The value of the parameter. + * @param timestamp The user's timestamp + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendTimedParameter(String clusterName, String nodeName, String paramName, Object paramValue, int timestamp) throws ApMonException, UnknownHostException, SocketException, IOException { Vector paramNames = new Vector(); @@ -996,19 +980,14 @@ public void sendTimedParameter(String clusterName, String nodeName, String param /** * Sends an integer parameter and its value to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in - * the previous datagram. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramName - * The name of the parameter. - * @param paramValue - * The value of the parameter. - * @throws ApMonException - * , - * UnknownHostException, SocketException + * + * @param clusterName The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in + * the previous datagram. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramName The name of the parameter. + * @param paramValue The value of the parameter. + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendParameter(String clusterName, String nodeName, String paramName, int paramValue) throws ApMonException, UnknownHostException, SocketException, IOException { sendParameter(clusterName, nodeName, paramName, new Integer(paramValue)); @@ -1016,21 +995,15 @@ public void sendParameter(String clusterName, String nodeName, String paramName, /** * Sends an integer parameter and its value to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in - * the previous datagram. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramName - * The name of the parameter. - * @param paramValue - * The value of the parameter. - * @throws ApMonException - * , - * UnknownHostException, SocketException - * @param timestamp - * The user's timestamp + * + * @param clusterName The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in + * the previous datagram. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramName The name of the parameter. + * @param paramValue The value of the parameter. + * @param timestamp The user's timestamp + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendTimedParameter(String clusterName, String nodeName, String paramName, int paramValue, int timestamp) throws ApMonException, UnknownHostException, SocketException, IOException { sendTimedParameter(clusterName, nodeName, paramName, new Integer(paramValue), timestamp); @@ -1038,19 +1011,14 @@ public void sendTimedParameter(String clusterName, String nodeName, String param /** * Sends a parameter of type double and its value to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. If it is NULL,we keep the same cluster and node name as in - * the previous datagram. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramName - * The name of the parameter. - * @param paramValue - * The value of the parameter. - * @throws ApMonException - * , - * UnknownHostException, SocketException + * + * @param clusterName The name of the cluster that is monitored. If it is NULL,we keep the same cluster and node name as in + * the previous datagram. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramName The name of the parameter. + * @param paramValue The value of the parameter. + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendParameter(String clusterName, String nodeName, String paramName, double paramValue) throws ApMonException, UnknownHostException, SocketException, IOException { @@ -1059,21 +1027,15 @@ public void sendParameter(String clusterName, String nodeName, String paramName, /** * Sends an integer parameter and its value to the MonALISA module. - * - * @param clusterName - * The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in - * the previous datagram. - * @param nodeName - * The name of the node from the cluster from which the value was taken. - * @param paramName - * The name of the parameter. - * @param paramValue - * The value of the parameter. - * @throws ApMonException - * , - * UnknownHostException, SocketException - * @param timestamp - * The user's timestamp + * + * @param clusterName The name of the cluster that is monitored. If it is NULL, we keep the same cluster and node name as in + * the previous datagram. + * @param nodeName The name of the node from the cluster from which the value was taken. + * @param paramName The name of the parameter. + * @param paramValue The value of the parameter. + * @param timestamp The user's timestamp + * @throws ApMonException , + * UnknownHostException, SocketException */ public void sendTimedParameter(String clusterName, String nodeName, String paramName, double paramValue, int timestamp) throws ApMonException, UnknownHostException, SocketException, IOException { @@ -1093,7 +1055,7 @@ void ensureSize(ByteArrayOutputStream baos) throws ApMonException { /** * Encodes in the XDR format the data from a ApMon structure. Must be called before sending the data over the * newtork. - * + * * @throws ApMonException */ void encodeParams(int nParams, Vector paramNames, Vector paramValues, int timestamp) throws ApMonException { @@ -1174,12 +1136,10 @@ public boolean getConfCheck() { /** * Settings for the periodical configuration rechecking feature. - * - * @param confRecheck - * If it is true, the configuration rechecking is enabled. - * @param interval - * The time interval at which the verifications are done. The interval will be automatically increased if - * ApMon cannot connect to the configuration URLs. + * + * @param confCheck If it is true, the configuration rechecking is enabled. + * @param interval The time interval at which the verifications are done. The interval will be automatically increased if + * ApMon cannot connect to the configuration URLs. */ public void setConfRecheck(boolean confCheck, long interval) { int val = -1; @@ -1229,18 +1189,6 @@ public long getRecheckInterval() { return val; } - /** - * Returns the actual value of the time interval (in seconds) between two recheck operations for the configuration - * file/URLs. - */ - long getCrtRecheckInterval() { - long val; - synchronized (mutexBack) { - val = this.crtRecheckInterval; - } - return val; - } - /** * Sets the value of the time interval (in seconds) between two recheck operations for the configuration file/URLs. * If the value is negative, the configuration rechecking is @@ -1253,6 +1201,18 @@ public void setRecheckInterval(long val) { setConfRecheck(false, val); } + /** + * Returns the actual value of the time interval (in seconds) between two recheck operations for the configuration + * file/URLs. + */ + long getCrtRecheckInterval() { + long val; + synchronized (mutexBack) { + val = this.crtRecheckInterval; + } + return val; + } + void setCrtRecheckInterval(long val) { synchronized (mutexBack) { crtRecheckInterval = val; @@ -1261,11 +1221,9 @@ void setCrtRecheckInterval(long val) { /** * Settings for the job monitoring feature. - * - * @param sysMonitoring - * If it is true, the job monitoring is enabled. - * @param interval - * The time interval at which the job monitoring datagrams are sent. + * + * @param jobMonitoring If it is true, the job monitoring is enabled. + * @param interval The time interval at which the job monitoring datagrams are sent. */ public void setJobMonitoring(boolean jobMonitoring, long interval) { int val = -1; @@ -1299,7 +1257,9 @@ public void setJobMonitoring(boolean jobMonitoring, long interval) { } } - /** Returns the value of the interval at which the job monitoring datagrams are sent. */ + /** + * Returns the value of the interval at which the job monitoring datagrams are sent. + */ public long getJobMonitorInterval() { long val; synchronized (mutexBack) { @@ -1308,7 +1268,9 @@ public long getJobMonitorInterval() { return val; } - /** Returns true if the job monitoring is enabled and false otherwise. */ + /** + * Returns true if the job monitoring is enabled and false otherwise. + */ public boolean getJobMonitoring() { boolean val; synchronized (mutexBack) { @@ -1319,11 +1281,9 @@ public boolean getJobMonitoring() { /** * Settings for the system monitoring feature. - * - * @param sysMonitoring - * If it is true, the system monitoring is enabled. - * @param interval - * The time interval at which the system monitoring datagrams are sent. + * + * @param sysMonitoring If it is true, the system monitoring is enabled. + * @param interval The time interval at which the system monitoring datagrams are sent. */ public void setSysMonitoring(boolean sysMonitoring, long interval) { int val = -1; @@ -1359,7 +1319,9 @@ public void setSysMonitoring(boolean sysMonitoring, long interval) { } } - /** Returns the value of the interval at which the system monitoring datagrams are sent. */ + /** + * Returns the value of the interval at which the system monitoring datagrams are sent. + */ public long getSysMonitorInterval() { long val; synchronized (mutexBack) { @@ -1369,7 +1331,9 @@ public long getSysMonitorInterval() { return val; } - /** Returns true if the job monitoring is enabled and false otherwise. */ + /** + * Returns true if the job monitoring is enabled and false otherwise. + */ public boolean getSysMonitoring() { boolean val; synchronized (mutexBack) { @@ -1380,13 +1344,11 @@ public boolean getSysMonitoring() { /** * Settings for the general system monitoring feature. - * - * @param genMonitoring - * If it is true, the general system monitoring is enabled. - * @param interval - * The number of time intervals at which the general system monitoring datagrams are sent (a - * "time interval" is the time interval between two subsequent system - * monitoring datagrams). + * + * @param genMonitoring If it is true, the general system monitoring is enabled. + * @param nIntervals The number of time intervals at which the general system monitoring datagrams are sent (a + * "time interval" is the time interval between two subsequent system + * monitoring datagrams). */ public void setGenMonitoring(boolean genMonitoring, int nIntervals) { @@ -1409,7 +1371,9 @@ public void setGenMonitoring(boolean genMonitoring, int nIntervals) { } } - /** Returns true if the general system monitoring is enabled and false otherwise. */ + /** + * Returns true if the general system monitoring is enabled and false otherwise. + */ public boolean getGenMonitoring() { boolean val; synchronized (mutexBack) { @@ -1453,7 +1417,9 @@ public Double getSystemParameter(String paramName) { return new Double(dVal); } - /** Enables or disables the background thread. */ + /** + * Enables or disables the background thread. + */ void setBackgroundThread(boolean val) { boolean stoppedThread = false; @@ -1493,31 +1459,6 @@ void setBackgroundThread(boolean val) { } } - /** - * Sets the ApMon loglevel. The possible values are: "FATAL", "WARNING", "INFO", "FINE", "DEBUG". - */ - public static void setLogLevel(String newLevel_s) { - int i; - String levels_s[] = { - "FATAL", "WARNING", "INFO", "FINE", "DEBUG" - }; - Level levels[] = { - Level.SEVERE, Level.WARNING, Level.INFO, Level.FINE, Level.FINEST - }; - - for (i = 0; i < 5; i++) - if (newLevel_s.equals(levels_s[i])) - break; - - if (i >= 5) { - logger.warning("[ setLogLevel() ] Invalid level value: " + newLevel_s); - return; - } - - logger.info("Setting logging level to " + newLevel_s); - logger.setLevel(levels[i]); - } - /** * This sets the maxim number of messages that are send to MonALISA in one second. Default, this number is 50. */ @@ -1547,7 +1488,9 @@ public void stopIt() { setBackgroundThread(false); } - /** Initializes the data structures used to configure the monitoring part of ApMon. */ + /** + * Initializes the data structures used to configure the monitoring part of ApMon. + */ void initMonitoring() { autoDisableMonitoring = true; sysMonitoring = false; @@ -1776,13 +1719,9 @@ void updateSEQ_NR() { /** * Displays the names, values and types of a set of parameters. - * - * @param paramNames - * Vector with the parameters' names. - * @param valueTypes - * Vector of Integers which represent the value types of the parameters. - * @param paramValues - * Vector with the values of the parameters. + * + * @param paramNames Vector with the parameters' names. + * @param paramValues Vector with the values of the parameters. */ String printParameters(Vector paramNames, Vector paramValues) { int i; @@ -1794,21 +1733,6 @@ String printParameters(Vector paramNames, Vector paramValues) { return res.toString(); } - /** don't allow a user to send more than MAX_MSG messages per second, in average */ - protected long prvTime = 0; - - protected double prvSent = 0; - - protected double prvDrop = 0; - - protected long crtTime = 0; - - protected long crtSent = 0; - - protected long crtDrop = 0; - - protected double hWeight = Math.exp(-5.0 / 60.0); - /** * Decide if the current datagram should be sent. This decision is based on the number of messages previously sent. */ @@ -1851,15 +1775,5 @@ public boolean shouldSend() { public String getMyHostname() { return myHostname; } - - // * Supported in Sun JRE >1.5 (returns -1 in prior versions) - public static int getPID() { - try { - final java.lang.management.RuntimeMXBean rt = java.lang.management.ManagementFactory.getRuntimeMXBean(); - return Integer.parseInt(rt.getName().split("@")[0]); - } catch (Throwable t) { - return -1; - } - } } diff --git a/src/apmon/ApMonException.java b/src/apmon/ApMonException.java index 7b5c901..503c612 100644 --- a/src/apmon/ApMonException.java +++ b/src/apmon/ApMonException.java @@ -34,10 +34,10 @@ /*** * Internal exception class. */ -public class ApMonException extends Exception{ - +public class ApMonException extends Exception { + static final long serialVersionUID = 979899100; - + public ApMonException(String s) { super(s); } diff --git a/src/apmon/ApMonMonitoringConstants.java b/src/apmon/ApMonMonitoringConstants.java index efea7de..b0ed936 100644 --- a/src/apmon/ApMonMonitoringConstants.java +++ b/src/apmon/ApMonMonitoringConstants.java @@ -39,70 +39,122 @@ public final class ApMonMonitoringConstants { //SYS_* Specific - public static final long SYS_LOAD1 = 0x1L; - public static final Long LSYS_LOAD1 = new Long(SYS_LOAD1); - public static final long SYS_LOAD5 = 0x2L; - public static final Long LSYS_LOAD5 = new Long(SYS_LOAD5); - public static final long SYS_LOAD15 = 0x4L; - public static final Long LSYS_LOAD15 = new Long(SYS_LOAD15); - - public static final long SYS_CPU_USR = 0x8L; - public static final Long LSYS_CPU_USR = new Long(SYS_CPU_USR); - public static final long SYS_CPU_SYS = 0x10L; - public static final Long LSYS_CPU_SYS = new Long(SYS_CPU_SYS); - public static final long SYS_CPU_IDLE = 0x20L; - public static final Long LSYS_CPU_IDLE = new Long(SYS_CPU_IDLE); - public static final long SYS_CPU_NICE = 0x40L; - public static final Long LSYS_CPU_NICE = new Long(SYS_CPU_NICE); - public static final long SYS_CPU_USAGE = 0x80L; - public static final Long LSYS_CPU_USAGE = new Long(SYS_CPU_USAGE); - - public static final long SYS_MEM_FREE = 0x100L; - public static final Long LSYS_MEM_FREE = new Long(SYS_MEM_FREE); - public static final long SYS_MEM_USED = 0x200L; - public static final Long LSYS_MEM_USED = new Long(SYS_MEM_USED); - public static final long SYS_MEM_USAGE = 0x400L; - public static final Long LSYS_MEM_USAGE = new Long(SYS_MEM_USAGE); - - public static final long SYS_PAGES_IN = 0x800L; - public static final Long LSYS_PAGES_IN = new Long(SYS_PAGES_IN); - public static final long SYS_PAGES_OUT = 0x1000L; - public static final Long LSYS_PAGES_OUT = new Long(SYS_PAGES_OUT); - - public static final long SYS_NET_IN = 0x2000L; - public static final Long LSYS_NET_IN = new Long(SYS_NET_IN); - public static final long SYS_NET_OUT = 0x4000L; - public static final Long LSYS_NET_OUT = new Long(SYS_NET_OUT); - public static final long SYS_NET_ERRS = 0x8000L; - public static final Long LSYS_NET_ERRS = new Long(SYS_NET_ERRS); - - public static final long SYS_SWAP_FREE = 0x10000L; - public static final Long LSYS_SWAP_FREE = new Long(SYS_SWAP_FREE); - public static final long SYS_SWAP_USED = 0x20000L; - public static final Long LSYS_SWAP_USED = new Long(SYS_SWAP_USED); - public static final long SYS_SWAP_USAGE = 0x40000L; - public static final Long LSYS_SWAP_USAGE = new Long(SYS_SWAP_USAGE); - - public static final long SYS_PROCESSES = 0x80000L; - public static final Long LSYS_PROCESSES = new Long(SYS_PROCESSES); - - public static final long SYS_NET_SOCKETS = 0x100000L; - public static final Long LSYS_NET_SOCKETS = new Long(SYS_NET_SOCKETS); - - public static final long SYS_NET_TCP_DETAILS = 0x200000L; - public static final Long LSYS_NET_TCP_DETAILS = new Long(SYS_NET_TCP_DETAILS); - - public static final long SYS_UPTIME = 0x400000L; - public static final Long LSYS_UPTIME = new Long(SYS_UPTIME); - - static HashMap HT_SYS_NAMES_TO_CONSTANTS = null; - private static HashMap HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES = null; - + public static final long SYS_LOAD1 = 0x1L; + public static final Long LSYS_LOAD1 = new Long(SYS_LOAD1); + public static final long SYS_LOAD5 = 0x2L; + public static final Long LSYS_LOAD5 = new Long(SYS_LOAD5); + public static final long SYS_LOAD15 = 0x4L; + public static final Long LSYS_LOAD15 = new Long(SYS_LOAD15); + + public static final long SYS_CPU_USR = 0x8L; + public static final Long LSYS_CPU_USR = new Long(SYS_CPU_USR); + public static final long SYS_CPU_SYS = 0x10L; + public static final Long LSYS_CPU_SYS = new Long(SYS_CPU_SYS); + public static final long SYS_CPU_IDLE = 0x20L; + public static final Long LSYS_CPU_IDLE = new Long(SYS_CPU_IDLE); + public static final long SYS_CPU_NICE = 0x40L; + public static final Long LSYS_CPU_NICE = new Long(SYS_CPU_NICE); + public static final long SYS_CPU_USAGE = 0x80L; + public static final Long LSYS_CPU_USAGE = new Long(SYS_CPU_USAGE); + + public static final long SYS_MEM_FREE = 0x100L; + public static final Long LSYS_MEM_FREE = new Long(SYS_MEM_FREE); + public static final long SYS_MEM_USED = 0x200L; + public static final Long LSYS_MEM_USED = new Long(SYS_MEM_USED); + public static final long SYS_MEM_USAGE = 0x400L; + public static final Long LSYS_MEM_USAGE = new Long(SYS_MEM_USAGE); + + public static final long SYS_PAGES_IN = 0x800L; + public static final Long LSYS_PAGES_IN = new Long(SYS_PAGES_IN); + public static final long SYS_PAGES_OUT = 0x1000L; + public static final Long LSYS_PAGES_OUT = new Long(SYS_PAGES_OUT); + + public static final long SYS_NET_IN = 0x2000L; + public static final Long LSYS_NET_IN = new Long(SYS_NET_IN); + public static final long SYS_NET_OUT = 0x4000L; + public static final Long LSYS_NET_OUT = new Long(SYS_NET_OUT); + public static final long SYS_NET_ERRS = 0x8000L; + public static final Long LSYS_NET_ERRS = new Long(SYS_NET_ERRS); + + public static final long SYS_SWAP_FREE = 0x10000L; + public static final Long LSYS_SWAP_FREE = new Long(SYS_SWAP_FREE); + public static final long SYS_SWAP_USED = 0x20000L; + public static final Long LSYS_SWAP_USED = new Long(SYS_SWAP_USED); + public static final long SYS_SWAP_USAGE = 0x40000L; + public static final Long LSYS_SWAP_USAGE = new Long(SYS_SWAP_USAGE); + + public static final long SYS_PROCESSES = 0x80000L; + public static final Long LSYS_PROCESSES = new Long(SYS_PROCESSES); + + public static final long SYS_NET_SOCKETS = 0x100000L; + public static final Long LSYS_NET_SOCKETS = new Long(SYS_NET_SOCKETS); + + public static final long SYS_NET_TCP_DETAILS = 0x200000L; + public static final Long LSYS_NET_TCP_DETAILS = new Long(SYS_NET_TCP_DETAILS); + + public static final long SYS_UPTIME = 0x400000L; + public static final Long LSYS_UPTIME = new Long(SYS_UPTIME); + //GENERIC_* + public static final long GEN_HOSTNAME = 0x1L; + public static final Long LGEN_HOSTNAME = new Long(GEN_HOSTNAME); + public static final long GEN_IP = 0x2L; + public static final Long LGEN_IP = new Long(GEN_IP); + public static final long GEN_CPU_MHZ = 0x4L; + public static final Long LGEN_CPU_MHZ = new Long(GEN_CPU_MHZ); + public static final long GEN_NO_CPUS = 0x8L; + public static final Long LGEN_NO_CPUS = new Long(GEN_NO_CPUS); + public static final long GEN_TOTAL_MEM = 0x10L; + public static final Long LGEN_TOTAL_MEM = new Long(GEN_TOTAL_MEM); + public static final long GEN_TOTAL_SWAP = 0x20L; + public static final Long LGEN_TOTAL_SWAP = new Long(GEN_TOTAL_SWAP); + public static final long GEN_CPU_VENDOR_ID = 0x40L; + public static final Long LGEN_CPU_VENDOR_ID = new Long(GEN_CPU_VENDOR_ID); + public static final long GEN_CPU_FAMILY = 0x80L; + public static final Long LGEN_CPU_FAMILY = new Long(GEN_CPU_FAMILY); + public static final long GEN_CPU_MODEL = 0x100L; + public static final Long LGEN_CPU_MODEL = new Long(GEN_CPU_MODEL); + public static final long GEN_CPU_MODEL_NAME = 0x200L; + public static final Long LGEN_CPU_MODEL_NAME = new Long(GEN_CPU_MODEL_NAME); + public static final long GEN_BOGOMIPS = 0x400L; + public static final Long LGEN_BOGOMIPS = new Long(GEN_BOGOMIPS); + public static final long JOB_RUN_TIME = 0x1L; + public static final Long LJOB_RUN_TIME = new Long(JOB_RUN_TIME); + public static final long JOB_CPU_TIME = 0x2L; + public static final Long LJOB_CPU_TIME = new Long(JOB_CPU_TIME); + public static final long JOB_CPU_USAGE = 0x4L; + public static final Long LJOB_CPU_USAGE = new Long(JOB_CPU_USAGE); + public static final long JOB_MEM_USAGE = 0x8L; + public static final Long LJOB_MEM_USAGE = new Long(JOB_MEM_USAGE); + public static final long JOB_WORKDIR_SIZE = 0x10L; + public static final Long LJOB_WORKDIR_SIZE = new Long(JOB_WORKDIR_SIZE); + public static final long JOB_DISK_TOTAL = 0x20L; + public static final Long LJOB_DISK_TOTAL = new Long(SYS_CPU_IDLE); + public static final long JOB_DISK_USED = 0x40L; + public static final Long LJOB_DISK_USED = new Long(JOB_DISK_USED); + public static final long JOB_DISK_FREE = 0x80L; + public static final Long LJOB_DISK_FREE = new Long(JOB_DISK_FREE); + public static final long JOB_DISK_USAGE = 0x100L; + + //JOB_* + public static final Long LJOB_DISK_USAGE = new Long(JOB_DISK_USAGE); + public static final long JOB_VIRTUALMEM = 0x200L; + public static final Long LJOB_VIRTUALMEM = new Long(JOB_VIRTUALMEM); + public static final long JOB_RSS = 0x400L; + public static final Long LJOB_RSS = new Long(JOB_RSS); + public static final long JOB_OPEN_FILES = 0x800L; + public static final Long LJOB_OPEN_FILES = new Long(JOB_OPEN_FILES); + static HashMap HT_SYS_NAMES_TO_CONSTANTS = null; + private static HashMap HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES = null; + private static HashMap HT_GEN_NAMES_TO_CONSTANTS = null; + private static HashMap HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES = null; + private static HashMap HT_JOB_NAMES_TO_CONSTANTS = null; + private static HashMap HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES = null; + static { HT_SYS_NAMES_TO_CONSTANTS = new HashMap(); - - HT_SYS_NAMES_TO_CONSTANTS.put("sys_load1", LSYS_LOAD1); - HT_SYS_NAMES_TO_CONSTANTS.put("sys_load5", LSYS_LOAD5); + + HT_SYS_NAMES_TO_CONSTANTS.put("sys_load1", LSYS_LOAD1); + HT_SYS_NAMES_TO_CONSTANTS.put("sys_load5", LSYS_LOAD5); HT_SYS_NAMES_TO_CONSTANTS.put("sys_load15", LSYS_LOAD15); HT_SYS_NAMES_TO_CONSTANTS.put("sys_cpu_usr", LSYS_CPU_USR); @@ -110,11 +162,11 @@ public final class ApMonMonitoringConstants { HT_SYS_NAMES_TO_CONSTANTS.put("sys_cpu_idle", LSYS_CPU_IDLE); HT_SYS_NAMES_TO_CONSTANTS.put("sys_cpu_nice", LSYS_CPU_NICE); HT_SYS_NAMES_TO_CONSTANTS.put("sys_cpu_usage", LSYS_CPU_USAGE); - + HT_SYS_NAMES_TO_CONSTANTS.put("sys_mem_free", LSYS_MEM_FREE); HT_SYS_NAMES_TO_CONSTANTS.put("sys_mem_used", LSYS_MEM_USED); HT_SYS_NAMES_TO_CONSTANTS.put("sys_mem_usage", LSYS_MEM_USAGE); - + HT_SYS_NAMES_TO_CONSTANTS.put("sys_pages_in", LSYS_PAGES_IN); HT_SYS_NAMES_TO_CONSTANTS.put("sys_pages_out", LSYS_PAGES_OUT); @@ -125,134 +177,64 @@ public final class ApMonMonitoringConstants { HT_SYS_NAMES_TO_CONSTANTS.put("sys_swap_free", LSYS_SWAP_FREE); HT_SYS_NAMES_TO_CONSTANTS.put("sys_swap_used", LSYS_SWAP_USED); HT_SYS_NAMES_TO_CONSTANTS.put("sys_swap_usage", LSYS_SWAP_USAGE); - + HT_SYS_NAMES_TO_CONSTANTS.put("sys_processes", LSYS_PROCESSES); HT_SYS_NAMES_TO_CONSTANTS.put("sys_net_sockets", LSYS_NET_SOCKETS); HT_SYS_NAMES_TO_CONSTANTS.put("sys_net_tcp_details", LSYS_NET_TCP_DETAILS); - + HT_SYS_NAMES_TO_CONSTANTS.put("sys_uptime", LSYS_UPTIME); - + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES = new HashMap(); - HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_LOAD1, "load1"); - HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_LOAD5, "load5"); + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_LOAD1, "load1"); + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_LOAD5, "load5"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_LOAD15, "load15"); - + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_CPU_USR, "cpu_usr"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_CPU_SYS, "cpu_sys"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_CPU_IDLE, "cpu_idle"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_CPU_NICE, "cpu_nice"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_CPU_USAGE, "cpu_usage"); - + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_MEM_FREE, "mem_free"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_MEM_USED, "mem_used"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_MEM_USAGE, "mem_usage"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_PAGES_IN, "pages_in"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_PAGES_OUT, "pages_out"); - + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_NET_IN, "in"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_NET_OUT, "out"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_NET_ERRS, "errs"); - + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_SWAP_FREE, "swap_free"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_SWAP_USED, "swap_used"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_SWAP_USAGE, "swap_usage"); - + HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_PROCESSES, "processes"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_NET_SOCKETS, "sockets"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_NET_TCP_DETAILS, "sockets_tcp"); HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES.put(LSYS_UPTIME, "uptime"); - - } - - - //helper functions - private static String getName(Long param, HashMap hm) { - if(param == null) return null; - if(!hm.containsValue(param)) return null; - - for(Iterator it=hm.keySet().iterator(); it.hasNext();){ - String key = (String)it.next(); - if(hm.get(key).equals(param)) return key; - } - - //should not get normally here .... but who knows :) - return null; - } - private static Long getIdx(String name, HashMap hm) { - if( name == null ) return null; - return (Long)hm.get(name); - } - - private static String getMLParamName(Long idx, HashMap hm) { - if(idx == null) return null; - return (String)hm.get(idx); - } - - - //this function can be slower ... it will be used only for debugging - public static String getSysName(Long param) { - return getName(param, HT_SYS_NAMES_TO_CONSTANTS); - } - - public static Long getSysIdx(String name) { - return getIdx(name, HT_SYS_NAMES_TO_CONSTANTS); - } - - public static String getSysMLParamName(Long idx) { - return getMLParamName(idx, HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES); } - public static String getSysMLParamName(long idx) { - return getMLParamName(new Long(idx), HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES); - } - - //GENERIC_* - public static final long GEN_HOSTNAME = 0x1L; - public static final Long LGEN_HOSTNAME = new Long(GEN_HOSTNAME); - public static final long GEN_IP = 0x2L; - public static final Long LGEN_IP = new Long(GEN_IP); - public static final long GEN_CPU_MHZ = 0x4L; - public static final Long LGEN_CPU_MHZ = new Long(GEN_CPU_MHZ); - public static final long GEN_NO_CPUS = 0x8L; - public static final Long LGEN_NO_CPUS = new Long(GEN_NO_CPUS); - public static final long GEN_TOTAL_MEM = 0x10L; - public static final Long LGEN_TOTAL_MEM = new Long(GEN_TOTAL_MEM); - public static final long GEN_TOTAL_SWAP = 0x20L; - public static final Long LGEN_TOTAL_SWAP = new Long(GEN_TOTAL_SWAP); - public static final long GEN_CPU_VENDOR_ID = 0x40L; - public static final Long LGEN_CPU_VENDOR_ID = new Long(GEN_CPU_VENDOR_ID); - public static final long GEN_CPU_FAMILY = 0x80L; - public static final Long LGEN_CPU_FAMILY = new Long(GEN_CPU_FAMILY); - public static final long GEN_CPU_MODEL = 0x100L; - public static final Long LGEN_CPU_MODEL = new Long(GEN_CPU_MODEL); - public static final long GEN_CPU_MODEL_NAME = 0x200L; - public static final Long LGEN_CPU_MODEL_NAME = new Long(GEN_CPU_MODEL_NAME); - public static final long GEN_BOGOMIPS = 0x400L; - public static final Long LGEN_BOGOMIPS = new Long(GEN_BOGOMIPS); - - private static HashMap HT_GEN_NAMES_TO_CONSTANTS = null; - private static HashMap HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES = null; - static { HT_GEN_NAMES_TO_CONSTANTS = new HashMap(); - - HT_GEN_NAMES_TO_CONSTANTS.put("hostname", LGEN_HOSTNAME); - HT_GEN_NAMES_TO_CONSTANTS.put("ip", LGEN_IP); + + HT_GEN_NAMES_TO_CONSTANTS.put("hostname", LGEN_HOSTNAME); + HT_GEN_NAMES_TO_CONSTANTS.put("ip", LGEN_IP); HT_GEN_NAMES_TO_CONSTANTS.put("cpu_MHz", LGEN_CPU_MHZ); HT_GEN_NAMES_TO_CONSTANTS.put("no_CPUs", LGEN_NO_CPUS); HT_GEN_NAMES_TO_CONSTANTS.put("total_mem", LGEN_TOTAL_MEM); HT_GEN_NAMES_TO_CONSTANTS.put("total_swap", LGEN_TOTAL_SWAP); - HT_GEN_NAMES_TO_CONSTANTS.put("cpu_vendor_id", LGEN_CPU_VENDOR_ID); - HT_GEN_NAMES_TO_CONSTANTS.put("cpu_family", LGEN_CPU_FAMILY); - HT_GEN_NAMES_TO_CONSTANTS.put("cpu_model", LGEN_CPU_MODEL); - HT_GEN_NAMES_TO_CONSTANTS.put("cpu_model_name", LGEN_CPU_MODEL_NAME); - HT_GEN_NAMES_TO_CONSTANTS.put("bogomips", LGEN_BOGOMIPS); + HT_GEN_NAMES_TO_CONSTANTS.put("cpu_vendor_id", LGEN_CPU_VENDOR_ID); + HT_GEN_NAMES_TO_CONSTANTS.put("cpu_family", LGEN_CPU_FAMILY); + HT_GEN_NAMES_TO_CONSTANTS.put("cpu_model", LGEN_CPU_MODEL); + HT_GEN_NAMES_TO_CONSTANTS.put("cpu_model_name", LGEN_CPU_MODEL_NAME); + HT_GEN_NAMES_TO_CONSTANTS.put("bogomips", LGEN_BOGOMIPS); + - HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES = new HashMap(); HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_HOSTNAME, "hostname"); HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_IP, "ip"); @@ -260,66 +242,18 @@ public static String getSysMLParamName(long idx) { HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_NO_CPUS, "no_CPUs"); HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_TOTAL_MEM, "total_mem"); HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_TOTAL_SWAP, "total_swap"); - HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_VENDOR_ID, "cpu_vendor_id"); - HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_FAMILY, "cpu_family"); - HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_MODEL, "cpu_model"); - HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_MODEL_NAME, "cpu_model_name"); - HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_BOGOMIPS, "bogomips"); - } - - public static String getGenName(Long param) { - return getName(param, HT_GEN_NAMES_TO_CONSTANTS); - } - - public static Long getGenIdx(String name) { - return getIdx(name, HT_GEN_NAMES_TO_CONSTANTS); - } - - public static String getGenMLParamName(Long idx) { - return getMLParamName(idx, HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES); + HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_VENDOR_ID, "cpu_vendor_id"); + HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_FAMILY, "cpu_family"); + HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_MODEL, "cpu_model"); + HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_CPU_MODEL_NAME, "cpu_model_name"); + HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES.put(LGEN_BOGOMIPS, "bogomips"); } - public static String getGenMLParamName(long idx) { - return getMLParamName(new Long(idx), HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES); - } - - //JOB_* - - public static final long JOB_RUN_TIME = 0x1L; - public static final Long LJOB_RUN_TIME = new Long(JOB_RUN_TIME); - public static final long JOB_CPU_TIME = 0x2L; - public static final Long LJOB_CPU_TIME = new Long(JOB_CPU_TIME); - public static final long JOB_CPU_USAGE = 0x4L; - public static final Long LJOB_CPU_USAGE = new Long(JOB_CPU_USAGE); - - public static final long JOB_MEM_USAGE = 0x8L; - public static final Long LJOB_MEM_USAGE = new Long(JOB_MEM_USAGE); - public static final long JOB_WORKDIR_SIZE = 0x10L; - public static final Long LJOB_WORKDIR_SIZE = new Long(JOB_WORKDIR_SIZE); - public static final long JOB_DISK_TOTAL = 0x20L; - public static final Long LJOB_DISK_TOTAL = new Long(SYS_CPU_IDLE); - public static final long JOB_DISK_USED = 0x40L; - public static final Long LJOB_DISK_USED = new Long(JOB_DISK_USED); - public static final long JOB_DISK_FREE = 0x80L; - public static final Long LJOB_DISK_FREE = new Long(JOB_DISK_FREE); - - public static final long JOB_DISK_USAGE = 0x100L; - public static final Long LJOB_DISK_USAGE = new Long(JOB_DISK_USAGE); - public static final long JOB_VIRTUALMEM = 0x200L; - public static final Long LJOB_VIRTUALMEM = new Long(JOB_VIRTUALMEM); - public static final long JOB_RSS = 0x400L; - public static final Long LJOB_RSS = new Long(JOB_RSS); - public static final long JOB_OPEN_FILES = 0x800L; - public static final Long LJOB_OPEN_FILES = new Long(JOB_OPEN_FILES); - - private static HashMap HT_JOB_NAMES_TO_CONSTANTS = null; - private static HashMap HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES = null; - static { HT_JOB_NAMES_TO_CONSTANTS = new HashMap(); - - HT_JOB_NAMES_TO_CONSTANTS.put("job_run_time", LJOB_RUN_TIME); - HT_JOB_NAMES_TO_CONSTANTS.put("job_cpu_time", LJOB_CPU_TIME); + + HT_JOB_NAMES_TO_CONSTANTS.put("job_run_time", LJOB_RUN_TIME); + HT_JOB_NAMES_TO_CONSTANTS.put("job_cpu_time", LJOB_CPU_TIME); HT_JOB_NAMES_TO_CONSTANTS.put("job_cpu_usage", LJOB_CPU_USAGE); HT_JOB_NAMES_TO_CONSTANTS.put("job_mem_usage", LJOB_MEM_USAGE); @@ -327,38 +261,95 @@ public static String getGenMLParamName(long idx) { HT_JOB_NAMES_TO_CONSTANTS.put("job_disk_total", LJOB_DISK_TOTAL); HT_JOB_NAMES_TO_CONSTANTS.put("job_disk_used", LJOB_DISK_USED); HT_JOB_NAMES_TO_CONSTANTS.put("job_disk_free", LJOB_DISK_FREE); - + HT_JOB_NAMES_TO_CONSTANTS.put("job_disk_usage", LJOB_DISK_USAGE); HT_JOB_NAMES_TO_CONSTANTS.put("job_virtualmem", LJOB_VIRTUALMEM); HT_JOB_NAMES_TO_CONSTANTS.put("job_rss", LJOB_RSS); - HT_JOB_NAMES_TO_CONSTANTS.put("job_open_files", LJOB_OPEN_FILES); - + HT_JOB_NAMES_TO_CONSTANTS.put("job_open_files", LJOB_OPEN_FILES); + HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES = new HashMap(); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_RUN_TIME, "run_time"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_CPU_TIME, "cpu_time"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_CPU_USAGE, "cpu_usage"); - + HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_MEM_USAGE, "mem_usage"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_WORKDIR_SIZE, "workdir_size"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_DISK_TOTAL, "disk_total"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_DISK_USED, "disk_used"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_DISK_FREE, "disk_free"); - + HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_DISK_USAGE, "disk_usage"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_VIRTUALMEM, "disk_virtualmem"); HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_RSS, "disk_rss"); - HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_OPEN_FILES, "open_files"); + HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES.put(LJOB_OPEN_FILES, "open_files"); + + } - } + //helper functions + private static String getName(Long param, HashMap hm) { + if (param == null) return null; + if (!hm.containsValue(param)) return null; + + for (Iterator it = hm.keySet().iterator(); it.hasNext(); ) { + String key = (String) it.next(); + if (hm.get(key).equals(param)) return key; + } + + //should not get normally here .... but who knows :) + return null; + } + + private static Long getIdx(String name, HashMap hm) { + if (name == null) return null; + return (Long) hm.get(name); + } + + private static String getMLParamName(Long idx, HashMap hm) { + if (idx == null) return null; + return (String) hm.get(idx); + } + + //this function can be slower ... it will be used only for debugging + public static String getSysName(Long param) { + return getName(param, HT_SYS_NAMES_TO_CONSTANTS); + } + + public static Long getSysIdx(String name) { + return getIdx(name, HT_SYS_NAMES_TO_CONSTANTS); + } + + public static String getSysMLParamName(Long idx) { + return getMLParamName(idx, HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES); + } + + public static String getSysMLParamName(long idx) { + return getMLParamName(new Long(idx), HT_SYS_CONSTANTS_TO_ML_PARAM_NAMES); + } + + public static String getGenName(Long param) { + return getName(param, HT_GEN_NAMES_TO_CONSTANTS); + } + + public static Long getGenIdx(String name) { + return getIdx(name, HT_GEN_NAMES_TO_CONSTANTS); + } + + public static String getGenMLParamName(Long idx) { + return getMLParamName(idx, HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES); + } + + public static String getGenMLParamName(long idx) { + return getMLParamName(new Long(idx), HT_GEN_CONSTANTS_TO_ML_PARAM_NAMES); + } public static String getJobName(Long param) { return getName(param, HT_JOB_NAMES_TO_CONSTANTS); } - + public static Long getJobIdx(String name) { return getIdx(name, HT_JOB_NAMES_TO_CONSTANTS); } - + public static String getJobMLParamName(Long idx) { return getMLParamName(idx, HT_JOB_CONSTANTS_TO_ML_PARAM_NAMES); } diff --git a/src/apmon/BkThread.java b/src/apmon/BkThread.java index 0ae7596..041b151 100644 --- a/src/apmon/BkThread.java +++ b/src/apmon/BkThread.java @@ -32,26 +32,20 @@ package apmon; +import apmon.host.HostPropertiesMonitor; +import apmon.host.Parser; +import apmon.host.cmdExec; + import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.net.URLConnection; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.StringTokenizer; -import java.util.Vector; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; -import apmon.host.HostPropertiesMonitor; -import apmon.host.Parser; -import apmon.host.cmdExec; - /** * Separate thread which periodically checks the configuration file/URLs for changes and periodically sends datagrams * with monitoring information. @@ -82,6 +76,248 @@ public BkThread(ApMon apm) { this.setDaemon(true); } + public static Hashtable getCpuInfo() throws IOException, ApMonException { + if (osName.indexOf("Linux") < 0) + return null; + + Hashtable info = new Hashtable(); + + BufferedReader in = null; + FileReader fr = null; + try { + fr = new FileReader("/proc/cpuinfo"); + in = new BufferedReader(fr); + String line = null; + while ((line = in.readLine()) != null) { + StringTokenizer st = new StringTokenizer(line, ":"); + if (line.startsWith("cpu MHz")) { + st.nextToken(); + String freq_s = st.nextToken(); + if (freq_s == null) + throw new ApMonException("Error reading CPU frequency from /proc/cpuinfo"); + info.put(ApMonMonitoringConstants.LGEN_CPU_MHZ, freq_s); + } + if (line.startsWith("vendor_id")) { + st.nextToken(); + String vendor = st.nextToken(); + if (vendor == null) + throw new ApMonException("Error reading CPU vendor_id from /proc/cpuinfo"); + info.put(ApMonMonitoringConstants.LGEN_CPU_VENDOR_ID, vendor); + } + if (line.startsWith("model") && !line.startsWith("model name")) { + st.nextToken(); + String model = st.nextToken(); + if (model == null) + throw new ApMonException("Error reading CPU model from /proc/cpuinfo"); + info.put(ApMonMonitoringConstants.LGEN_CPU_MODEL, model); + } + if (line.startsWith("cpu family")) { + st.nextToken(); + String cpufam = st.nextToken(); + if (cpufam == null) + throw new ApMonException("Error reading CPU family from /proc/cpuinfo"); + info.put(ApMonMonitoringConstants.LGEN_CPU_FAMILY, cpufam); + } + if (line.startsWith("model name")) { + st.nextToken(); + String modelname = st.nextToken(); + if (modelname == null) + throw new ApMonException("Error reading CPU model name from /proc/cpuinfo"); + info.put(ApMonMonitoringConstants.LGEN_CPU_MODEL_NAME, modelname); + } + if (line.startsWith("bogomips")) { + st.nextToken(); + String bogomips = st.nextToken(); + if (bogomips == null) + throw new ApMonException("Error reading CPU bogomips from /proc/cpuinfo"); + info.put(ApMonMonitoringConstants.LGEN_BOGOMIPS, bogomips); + } + } + } finally { + if (fr != null) { + try { + fr.close(); + } catch (Throwable ignore) { + } + } + if (in != null) { + try { + in.close(); + } catch (Throwable ignore) { + } + } + } + return info; + } + + /** + * Returns the system boot time in milliseconds since the Epoch. Works only on Linux systems. + */ + public static long getBootTime() throws IOException, ApMonException { + if (osName.indexOf("Linux") < 0) + return 0; + + BufferedReader in = null; + FileReader fr = null; + try { + fr = new FileReader("/proc/stat"); + in = new BufferedReader(fr); + String line = null; + while ((line = in.readLine()) != null) { + if (line.startsWith("btime")) + break; + } + + if (line == null) + throw new ApMonException("Error reading boot time from /proc/stat"); + + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); + String btime_s = st.nextToken(); + + if (btime_s == null) + throw new ApMonException("Error reading boot time from /proc/stat"); + + return (Long.parseLong(btime_s) * 1000); + } finally { + if (fr != null) { + try { + fr.close(); + } catch (Throwable ignore) { + } + } + if (in != null) { + try { + in.close(); + } catch (Throwable ignore) { + } + } + } + } + + /* in days */ + public static double getUpTime() throws IOException, ApMonException { + if (osName.indexOf("Linux") < 0) + return 0; + + FileReader fr = null; + BufferedReader in = null; + + try { + fr = new FileReader("/proc/uptime"); + in = new BufferedReader(fr); + String line = in.readLine(); + if (line == null) + throw new ApMonException("Error reading boot time from /proc/uptime"); + + StringTokenizer st = new StringTokenizer(line); + String up_time = st.nextToken(); + + if (up_time == null) + throw new ApMonException("Error reading optime from /proc/uptime"); + + return (Double.parseDouble(up_time)) / (3600 * 24); + } finally { + if (fr != null) { + try { + fr.close(); + } catch (Throwable ignore) { + } + } + if (in != null) { + try { + in.close(); + } catch (Throwable ignore) { + } + } + } + + } + + /** + * Returns the number of CPUs in the system + */ + public static int getNumCPUs() throws IOException, ApMonException { + if (osName.indexOf("Linux") < 0) + return 0; + + FileReader fr = null; + BufferedReader in = null; + try { + fr = new FileReader("/proc/stat"); + in = new BufferedReader(fr); + String line = null; + int numCPUs = 0; + while ((line = in.readLine()) != null) { + if (line.startsWith("cpu") && Character.isDigit(line.charAt(3))) + numCPUs++; + } + + if (numCPUs == 0) + throw new ApMonException("Error reading CPU frequency from /proc/stat"); + + return numCPUs; + } finally { + if (fr != null) { + try { + fr.close(); + } catch (Throwable ignore) { + } + } + if (in != null) { + try { + in.close(); + } catch (Throwable ignore) { + } + } + } + } + + public static void getNetConfig(Vector netInterfaces, Vector ips) throws IOException, ApMonException { + String line; + Parser parser = new Parser(); + cmdExec exec = new cmdExec(); + + String output = exec.executeCommandReality("/sbin/ifconfig -a", ""); + if (exec.isError()) + output = null; + exec.stopIt(); + + String crtIfaceName = null; + if (output != null && !output.equals("")) { + parser.parse(output); + line = parser.nextLine(); + while (line != null) { + if (line == null) + break; + StringTokenizer lst = new StringTokenizer(line, " :\t"); + if (line.startsWith(" ") || line.startsWith("\t")) { + if (line.indexOf("inet") < 0) { + line = parser.nextLine(); + continue; + } + + lst.nextToken(); + lst.nextToken(); + String addr_t = lst.nextToken(); + if (!addr_t.equals("127.0.0.1")) { + ips.add(addr_t); + netInterfaces.add(crtIfaceName); + } + } else { + // get the name + String netName = lst.nextToken(); + if (netName != null && !netName.startsWith("lo") && netName.indexOf("eth") != -1) { + crtIfaceName = new String(netName); + } + } + + line = parser.nextLine(); + } + } + + } + void stopIt() { hasToRun = false; } @@ -185,7 +421,7 @@ void sendOneJobInfo(MonitoredJob monJob) { hm.putAll(hmJobDisk); } - for (Iterator it = hm.keySet().iterator(); it.hasNext();) { + for (Iterator it = hm.keySet().iterator(); it.hasNext(); ) { Long lParam = (Long) it.next(); try { if (isActive_Job(lParam)) { @@ -210,7 +446,9 @@ void sendOneJobInfo(MonitoredJob monJob) { } } - /** Sends an UDP datagram with system monitoring information */ + /** + * Sends an UDP datagram with system monitoring information + */ void sendSysInfo() { double value = 0.0; Vector paramNames, paramValues; @@ -230,7 +468,7 @@ void sendSysInfo() { HashMap hms = monitor.getHashParams(); if (hms != null) { - for (Iterator it = hms.keySet().iterator(); it.hasNext();) { + for (Iterator it = hms.keySet().iterator(); it.hasNext(); ) { Long lParam = (Long) it.next(); try { if (isActive_Sys(lParam)) { @@ -717,242 +955,4 @@ public void run() { } - public static Hashtable getCpuInfo() throws IOException, ApMonException { - if (osName.indexOf("Linux") < 0) - return null; - - Hashtable info = new Hashtable(); - - BufferedReader in = null; - FileReader fr = null; - try { - fr = new FileReader("/proc/cpuinfo"); - in = new BufferedReader(fr); - String line = null; - while ((line = in.readLine()) != null) { - StringTokenizer st = new StringTokenizer(line, ":"); - if (line.startsWith("cpu MHz")) { - st.nextToken(); - String freq_s = st.nextToken(); - if (freq_s == null) - throw new ApMonException("Error reading CPU frequency from /proc/cpuinfo"); - info.put(ApMonMonitoringConstants.LGEN_CPU_MHZ, freq_s); - } - if (line.startsWith("vendor_id")) { - st.nextToken(); - String vendor = st.nextToken(); - if (vendor == null) - throw new ApMonException("Error reading CPU vendor_id from /proc/cpuinfo"); - info.put(ApMonMonitoringConstants.LGEN_CPU_VENDOR_ID, vendor); - } - if (line.startsWith("model") && !line.startsWith("model name")) { - st.nextToken(); - String model = st.nextToken(); - if (model == null) - throw new ApMonException("Error reading CPU model from /proc/cpuinfo"); - info.put(ApMonMonitoringConstants.LGEN_CPU_MODEL, model); - } - if (line.startsWith("cpu family")) { - st.nextToken(); - String cpufam = st.nextToken(); - if (cpufam == null) - throw new ApMonException("Error reading CPU family from /proc/cpuinfo"); - info.put(ApMonMonitoringConstants.LGEN_CPU_FAMILY, cpufam); - } - if (line.startsWith("model name")) { - st.nextToken(); - String modelname = st.nextToken(); - if (modelname == null) - throw new ApMonException("Error reading CPU model name from /proc/cpuinfo"); - info.put(ApMonMonitoringConstants.LGEN_CPU_MODEL_NAME, modelname); - } - if (line.startsWith("bogomips")) { - st.nextToken(); - String bogomips = st.nextToken(); - if (bogomips == null) - throw new ApMonException("Error reading CPU bogomips from /proc/cpuinfo"); - info.put(ApMonMonitoringConstants.LGEN_BOGOMIPS, bogomips); - } - } - } finally { - if (fr != null) { - try { - fr.close(); - } catch (Throwable ignore) { - } - } - if (in != null) { - try { - in.close(); - } catch (Throwable ignore) { - } - } - } - return info; - } - - /** Returns the system boot time in milliseconds since the Epoch. Works only on Linux systems. */ - public static long getBootTime() throws IOException, ApMonException { - if (osName.indexOf("Linux") < 0) - return 0; - - BufferedReader in = null; - FileReader fr = null; - try { - fr = new FileReader("/proc/stat"); - in = new BufferedReader(fr); - String line = null; - while ((line = in.readLine()) != null) { - if (line.startsWith("btime")) - break; - } - - if (line == null) - throw new ApMonException("Error reading boot time from /proc/stat"); - - StringTokenizer st = new StringTokenizer(line); - st.nextToken(); - String btime_s = st.nextToken(); - - if (btime_s == null) - throw new ApMonException("Error reading boot time from /proc/stat"); - - return (Long.parseLong(btime_s) * 1000); - } finally { - if (fr != null) { - try { - fr.close(); - } catch (Throwable ignore) { - } - } - if (in != null) { - try { - in.close(); - } catch (Throwable ignore) { - } - } - } - } - - /* in days */ - public static double getUpTime() throws IOException, ApMonException { - if (osName.indexOf("Linux") < 0) - return 0; - - FileReader fr = null; - BufferedReader in = null; - - try { - fr = new FileReader("/proc/uptime"); - in = new BufferedReader(fr); - String line = in.readLine(); - if (line == null) - throw new ApMonException("Error reading boot time from /proc/uptime"); - - StringTokenizer st = new StringTokenizer(line); - String up_time = st.nextToken(); - - if (up_time == null) - throw new ApMonException("Error reading optime from /proc/uptime"); - - return (Double.parseDouble(up_time)) / (3600 * 24); - } finally { - if (fr != null) { - try { - fr.close(); - } catch (Throwable ignore) { - } - } - if (in != null) { - try { - in.close(); - } catch (Throwable ignore) { - } - } - } - - } - - /** Returns the number of CPUs in the system */ - public static int getNumCPUs() throws IOException, ApMonException { - if (osName.indexOf("Linux") < 0) - return 0; - - FileReader fr = null; - BufferedReader in = null; - try { - fr = new FileReader("/proc/stat"); - in = new BufferedReader(fr); - String line = null; - int numCPUs = 0; - while ((line = in.readLine()) != null) { - if (line.startsWith("cpu") && Character.isDigit(line.charAt(3))) - numCPUs++; - } - - if (numCPUs == 0) - throw new ApMonException("Error reading CPU frequency from /proc/stat"); - - return numCPUs; - } finally { - if (fr != null) { - try { - fr.close(); - } catch (Throwable ignore) { - } - } - if (in != null) { - try { - in.close(); - } catch (Throwable ignore) { - } - } - } - } - - public static void getNetConfig(Vector netInterfaces, Vector ips) throws IOException, ApMonException { - String line; - Parser parser = new Parser(); - cmdExec exec = new cmdExec(); - - String output = exec.executeCommandReality("/sbin/ifconfig -a", ""); - if (exec.isError()) - output = null; - exec.stopIt(); - - String crtIfaceName = null; - if (output != null && !output.equals("")) { - parser.parse(output); - line = parser.nextLine(); - while (line != null) { - if (line == null) - break; - StringTokenizer lst = new StringTokenizer(line, " :\t"); - if (line.startsWith(" ") || line.startsWith("\t")) { - if (line.indexOf("inet") < 0) { - line = parser.nextLine(); - continue; - } - - lst.nextToken(); - lst.nextToken(); - String addr_t = lst.nextToken(); - if (!addr_t.equals("127.0.0.1")) { - ips.add(addr_t); - netInterfaces.add(crtIfaceName); - } - } else { - // get the name - String netName = lst.nextToken(); - if (netName != null && !netName.startsWith("lo") && netName.indexOf("eth") != -1) { - crtIfaceName = new String(netName); - } - } - - line = parser.nextLine(); - } - } - - } - } diff --git a/src/apmon/MonitoredJob.java b/src/apmon/MonitoredJob.java index 291df1f..75f7286 100644 --- a/src/apmon/MonitoredJob.java +++ b/src/apmon/MonitoredJob.java @@ -32,275 +32,272 @@ package apmon; +import apmon.host.cmdExec; + import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.StringTokenizer; import java.util.Vector; -import java.util.logging.Logger; import java.util.logging.Level; -import java.io.File; - -import apmon.host.cmdExec; +import java.util.logging.Logger; public class MonitoredJob { - int pid; - - /* the job's working dierctory */ - String workDir; - - /* the cluster name that will be included in the monitoring datagrams */ - String clusterName; - - /* the node name that will be included in the monitoring datagrams */ - String nodeName; - - private static Logger logger = Logger.getLogger("apmon"); - - cmdExec exec = null; - - public MonitoredJob(int _pid, String _workDir, String _clusterName, String _nodeName) { - pid = _pid; - workDir = _workDir; - clusterName = _clusterName; - nodeName = _nodeName; - exec = new cmdExec(); - } - - public int getPid() { - return pid; - } - - public HashMap readJobDiskUsage() { - HashMap hm = new HashMap(); - String cmd = null, aux = null, line = null, result = null; - double workdir_size = 0.0, disk_total = 0.0, disk_used = 0.0, disk_free = 0.0, disk_usage = 0.0; - - if (workDir == null) - return null; - - cmd = "du -Lscm " + workDir + " | tail -1 | cut -f 1"; - result = exec.executeCommandReality(cmd, ""); - workdir_size = Double.parseDouble(result); - hm.put(ApMonMonitoringConstants.LJOB_WORKDIR_SIZE, new Double(workdir_size)); - - cmd = "df -m " + workDir + " | tail -1"; - result = exec.executeCommand(cmd, ""); - StringTokenizer st = new StringTokenizer(result, " %"); - st.nextToken(); - - aux = st.nextToken(); - disk_total = Double.parseDouble(aux); - hm.put(ApMonMonitoringConstants.LJOB_DISK_TOTAL, new Double(workdir_size)); - - aux = st.nextToken(); - disk_used = Double.parseDouble(aux); - hm.put(ApMonMonitoringConstants.LJOB_DISK_USED, new Double(workdir_size)); - - aux = st.nextToken(); - disk_free = Double.parseDouble(aux); - hm.put(ApMonMonitoringConstants.LJOB_DISK_FREE, new Double(workdir_size)); - - aux = st.nextToken(); - disk_usage = Double.parseDouble(aux); - hm.put(ApMonMonitoringConstants.LJOB_DISK_USAGE, new Double(workdir_size)); - - return hm; - } - - public Vector getChildren() { - Vector pids, ppids, children; - String cmd = null, result = null; - int nProcesses = 0, nChildren = 1; - int i, j; - - cmd = "ps --no-headers -eo ppid,pid"; - result = exec.executeCommandReality(cmd, ""); - boolean pidFound = false; - if (result == null) { - logger.warning("The child processes could not be determined"); - return null; - } - - StringTokenizer st = new StringTokenizer(result, " \n"); - nProcesses = st.countTokens() / 2; - - pids = new Vector(); - ppids = new Vector(); - children = new Vector(); - children.add(new Integer(pid)); - while (st.hasMoreTokens()) { - i = Integer.parseInt(st.nextToken()); - j = Integer.parseInt(st.nextToken()); - if (j == pid) - pidFound = true; - ppids.add(new Integer(i)); - pids.add(new Integer(j)); - if (i == ((Integer) children.elementAt(0)).intValue()) { - children.add(new Integer(j)); - nChildren++; - } - } - - if (!pidFound) - return null; - - i = 1; - - while (i < nChildren) { - /* find the children of the i-th child */ - for (j = 0; j < nProcesses; j++) { - if (ppids.elementAt(j).equals(children.elementAt(i))) { - children.add(pids.elementAt(j)); - nChildren++; - } - } - i++; - } - - return children; - } - - public static long parsePSTime(String s) { - long days, hours, mins, secs; - if (s.indexOf('-') > 0) { - StringTokenizer st = new StringTokenizer(s, "-:"); - days = Long.parseLong(st.nextToken()); - hours = Long.parseLong(st.nextToken()); - mins = Long.parseLong(st.nextToken()); - secs = Long.parseLong(st.nextToken()); - return 24 * 3600 * days + 3600 * hours + 60 * mins + secs; - } - if (s.indexOf(':') > 0 && s.indexOf(':') != s.lastIndexOf(':')) { - StringTokenizer st = new StringTokenizer(s, ":"); - hours = Long.parseLong(st.nextToken()); - mins = Long.parseLong(st.nextToken()); - secs = Long.parseLong(st.nextToken()); - return 3600 * hours + 60 * mins + secs; - } - - if (s.indexOf(':') > 0) { - StringTokenizer st = new StringTokenizer(s, ":"); - mins = Long.parseLong(st.nextToken()); - secs = Long.parseLong(st.nextToken()); - return 60 * mins + secs; - } - - return -1; - } - - public HashMap readJobInfo() throws IOException { - Vector children; - HashMap ret = new HashMap(); - String cmd = null, result = null; - BufferedReader fp; - String line = null; - - int i; - - double rsz = 0.0, vsz = 0.0; - double etime = 0.0, cputime = 0.0; - double pcpu = 0.0, pmem = 0.0; - - double _rsz, _vsz; - double _etime, _cputime; - double _pcpu, _pmem; - - long apid, fd = 0; + private static Logger logger = Logger.getLogger("apmon"); + int pid; + /* the job's working dierctory */ + String workDir; + /* the cluster name that will be included in the monitoring datagrams */ + String clusterName; + /* the node name that will be included in the monitoring datagrams */ + String nodeName; + cmdExec exec = null; + + public MonitoredJob(int _pid, String _workDir, String _clusterName, String _nodeName) { + pid = _pid; + workDir = _workDir; + clusterName = _clusterName; + nodeName = _nodeName; + exec = new cmdExec(); + } + + public static long parsePSTime(String s) { + long days, hours, mins, secs; + if (s.indexOf('-') > 0) { + StringTokenizer st = new StringTokenizer(s, "-:"); + days = Long.parseLong(st.nextToken()); + hours = Long.parseLong(st.nextToken()); + mins = Long.parseLong(st.nextToken()); + secs = Long.parseLong(st.nextToken()); + return 24 * 3600 * days + 3600 * hours + 60 * mins + secs; + } + if (s.indexOf(':') > 0 && s.indexOf(':') != s.lastIndexOf(':')) { + StringTokenizer st = new StringTokenizer(s, ":"); + hours = Long.parseLong(st.nextToken()); + mins = Long.parseLong(st.nextToken()); + secs = Long.parseLong(st.nextToken()); + return 3600 * hours + 60 * mins + secs; + } + + if (s.indexOf(':') > 0) { + StringTokenizer st = new StringTokenizer(s, ":"); + mins = Long.parseLong(st.nextToken()); + secs = Long.parseLong(st.nextToken()); + return 60 * mins + secs; + } + + return -1; + } + + public int getPid() { + return pid; + } + + public HashMap readJobDiskUsage() { + HashMap hm = new HashMap(); + String cmd = null, aux = null, line = null, result = null; + double workdir_size = 0.0, disk_total = 0.0, disk_used = 0.0, disk_free = 0.0, disk_usage = 0.0; + + if (workDir == null) + return null; + + cmd = "du -Lscm " + workDir + " | tail -1 | cut -f 1"; + result = exec.executeCommandReality(cmd, ""); + workdir_size = Double.parseDouble(result); + hm.put(ApMonMonitoringConstants.LJOB_WORKDIR_SIZE, new Double(workdir_size)); + + cmd = "df -m " + workDir + " | tail -1"; + result = exec.executeCommand(cmd, ""); + StringTokenizer st = new StringTokenizer(result, " %"); + st.nextToken(); + + aux = st.nextToken(); + disk_total = Double.parseDouble(aux); + hm.put(ApMonMonitoringConstants.LJOB_DISK_TOTAL, new Double(workdir_size)); + + aux = st.nextToken(); + disk_used = Double.parseDouble(aux); + hm.put(ApMonMonitoringConstants.LJOB_DISK_USED, new Double(workdir_size)); + + aux = st.nextToken(); + disk_free = Double.parseDouble(aux); + hm.put(ApMonMonitoringConstants.LJOB_DISK_FREE, new Double(workdir_size)); + + aux = st.nextToken(); + disk_usage = Double.parseDouble(aux); + hm.put(ApMonMonitoringConstants.LJOB_DISK_USAGE, new Double(workdir_size)); + + return hm; + } + + public Vector getChildren() { + Vector pids, ppids, children; + String cmd = null, result = null; + int nProcesses = 0, nChildren = 1; + int i, j; + + cmd = "ps --no-headers -eo ppid,pid"; + result = exec.executeCommandReality(cmd, ""); + boolean pidFound = false; + if (result == null) { + logger.warning("The child processes could not be determined"); + return null; + } + + StringTokenizer st = new StringTokenizer(result, " \n"); + nProcesses = st.countTokens() / 2; + + pids = new Vector(); + ppids = new Vector(); + children = new Vector(); + children.add(new Integer(pid)); + while (st.hasMoreTokens()) { + i = Integer.parseInt(st.nextToken()); + j = Integer.parseInt(st.nextToken()); + if (j == pid) + pidFound = true; + ppids.add(new Integer(i)); + pids.add(new Integer(j)); + if (i == ((Integer) children.elementAt(0)).intValue()) { + children.add(new Integer(j)); + nChildren++; + } + } + + if (!pidFound) + return null; + + i = 1; + + while (i < nChildren) { + /* find the children of the i-th child */ + for (j = 0; j < nProcesses; j++) { + if (ppids.elementAt(j).equals(children.elementAt(i))) { + children.add(pids.elementAt(j)); + nChildren++; + } + } + i++; + } + + return children; + } + + public HashMap readJobInfo() throws IOException { + Vector children; + HashMap ret = new HashMap(); + String cmd = null, result = null; + BufferedReader fp; + String line = null; + + int i; + + double rsz = 0.0, vsz = 0.0; + double etime = 0.0, cputime = 0.0; + double pcpu = 0.0, pmem = 0.0; + + double _rsz, _vsz; + double _etime, _cputime; + double _pcpu, _pmem; + + long apid, fd = 0; /* * this list contains strings of the form "rsz_vsz_command" for every pid; it is used to avoid adding several times processes that have multiple threads and appear in ps as * sepparate processes, occupying exactly the same amount of memory and having the same command name. For every line from the output of the ps command we verify if the * rsz_vsz_command combination is already in the list. */ - Vector mem_cmd_list = new Vector(); + Vector mem_cmd_list = new Vector(); /* get the list of the process' descendants */ - children = getChildren(); + children = getChildren(); - if (children == null) - return null; + if (children == null) + return null; - logger.fine("Number of children for process " + pid + ": " + children.size()); + logger.fine("Number of children for process " + pid + ": " + children.size()); /* issue the "ps" command to obtain information on all the descendants */ - cmd = "ps --no-headers --pid "; - for (i = 0; i < children.size() - 1; i++) - cmd = cmd + children.elementAt(i) + ","; - cmd = cmd + children.elementAt(children.size() - 1); - - cmd = cmd + " -o pid,etime,time,%cpu,%mem,rsz,vsz,comm"; - result = exec.executeCommandReality(cmd, ""); - - StringTokenizer rst = new StringTokenizer(result, "\n"); - while (rst.hasMoreTokens()) { - line = rst.nextToken(); - StringTokenizer st = new StringTokenizer(line, " \t"); - - apid = Long.parseLong(st.nextToken()); - _etime = (double) parsePSTime(st.nextToken()); - _cputime = (double) parsePSTime(st.nextToken()); - _pcpu = Double.parseDouble(st.nextToken()); - _pmem = Double.parseDouble(st.nextToken()); - _rsz = Double.parseDouble(st.nextToken()); - _vsz = Double.parseDouble(st.nextToken()); - String cmdName = st.nextToken(); - - etime = etime > _etime ? etime : _etime; - cputime += _cputime; - pcpu += _pcpu; - - String mem_cmd_s = "" + _rsz + "_" + _vsz + "_" + cmdName; - // mem_cmd_list.add(mem_cmd_s); - if (mem_cmd_list.indexOf(mem_cmd_s) == -1) { - pmem += _pmem; - vsz += _vsz; - rsz += _rsz; - mem_cmd_list.add(mem_cmd_s); - long _fd = countOpenFD(apid); - if (_fd != -1) - fd += _fd; - } - } - - ret.put(ApMonMonitoringConstants.LJOB_RUN_TIME, new Double(etime)); - ret.put(ApMonMonitoringConstants.LJOB_CPU_TIME, new Double(cputime)); - ret.put(ApMonMonitoringConstants.LJOB_CPU_USAGE, new Double(pcpu)); - ret.put(ApMonMonitoringConstants.LJOB_MEM_USAGE, new Double(pmem)); - ret.put(ApMonMonitoringConstants.LJOB_RSS, new Double(rsz)); - ret.put(ApMonMonitoringConstants.LJOB_VIRTUALMEM, new Double(vsz)); - ret.put(ApMonMonitoringConstants.LJOB_OPEN_FILES, new Double(fd)); - - return ret; - } - - /** count the number of open files for the given pid */ - public long countOpenFD(long pid) { - - long open_files; - int mypid = ApMon.getPID(); - String dir = "/proc/" + pid + "/fd"; - File f = new File(dir); - if(f.exists()){ - if(f.canRead()){ - open_files = (f.list()).length - 2; - if(pid == mypid) - open_files -= 2; - logger.log(Level.FINE, "Counting open_files for process " + pid); - }else{ - open_files = -1; - logger.log(Level.SEVERE, "ProcInfo: cannot count the number of opened files for job" + pid); - } - }else{ - open_files = -1; - logger.log(Level.SEVERE, "ProcInfo: job " + pid + "not exist."); - } - return open_files; - } - - - public String toString() { - return new String("[" + pid + "]" + " " + workDir + " " + " " + clusterName + " " + nodeName); - } + cmd = "ps --no-headers --pid "; + for (i = 0; i < children.size() - 1; i++) + cmd = cmd + children.elementAt(i) + ","; + cmd = cmd + children.elementAt(children.size() - 1); + + cmd = cmd + " -o pid,etime,time,%cpu,%mem,rsz,vsz,comm"; + result = exec.executeCommandReality(cmd, ""); + + StringTokenizer rst = new StringTokenizer(result, "\n"); + while (rst.hasMoreTokens()) { + line = rst.nextToken(); + StringTokenizer st = new StringTokenizer(line, " \t"); + + apid = Long.parseLong(st.nextToken()); + _etime = (double) parsePSTime(st.nextToken()); + _cputime = (double) parsePSTime(st.nextToken()); + _pcpu = Double.parseDouble(st.nextToken()); + _pmem = Double.parseDouble(st.nextToken()); + _rsz = Double.parseDouble(st.nextToken()); + _vsz = Double.parseDouble(st.nextToken()); + String cmdName = st.nextToken(); + + etime = etime > _etime ? etime : _etime; + cputime += _cputime; + pcpu += _pcpu; + + String mem_cmd_s = "" + _rsz + "_" + _vsz + "_" + cmdName; + // mem_cmd_list.add(mem_cmd_s); + if (mem_cmd_list.indexOf(mem_cmd_s) == -1) { + pmem += _pmem; + vsz += _vsz; + rsz += _rsz; + mem_cmd_list.add(mem_cmd_s); + long _fd = countOpenFD(apid); + if (_fd != -1) + fd += _fd; + } + } + + ret.put(ApMonMonitoringConstants.LJOB_RUN_TIME, new Double(etime)); + ret.put(ApMonMonitoringConstants.LJOB_CPU_TIME, new Double(cputime)); + ret.put(ApMonMonitoringConstants.LJOB_CPU_USAGE, new Double(pcpu)); + ret.put(ApMonMonitoringConstants.LJOB_MEM_USAGE, new Double(pmem)); + ret.put(ApMonMonitoringConstants.LJOB_RSS, new Double(rsz)); + ret.put(ApMonMonitoringConstants.LJOB_VIRTUALMEM, new Double(vsz)); + ret.put(ApMonMonitoringConstants.LJOB_OPEN_FILES, new Double(fd)); + + return ret; + } + + /** + * count the number of open files for the given pid + */ + public long countOpenFD(long pid) { + + long open_files; + int mypid = ApMon.getPID(); + String dir = "/proc/" + pid + "/fd"; + File f = new File(dir); + if (f.exists()) { + if (f.canRead()) { + open_files = (f.list()).length - 2; + if (pid == mypid) + open_files -= 2; + logger.log(Level.FINE, "Counting open_files for process " + pid); + } else { + open_files = -1; + logger.log(Level.SEVERE, "ProcInfo: cannot count the number of opened files for job" + pid); + } + } else { + open_files = -1; + logger.log(Level.SEVERE, "ProcInfo: job " + pid + "not exist."); + } + return open_files; + } + + + public String toString() { + return new String("[" + pid + "]" + " " + workDir + " " + " " + clusterName + " " + nodeName); + } } diff --git a/src/apmon/XDRDataOutput.java b/src/apmon/XDRDataOutput.java index 6d4f949..ea80775 100644 --- a/src/apmon/XDRDataOutput.java +++ b/src/apmon/XDRDataOutput.java @@ -6,32 +6,32 @@ /** * An interface implemented by output streams that support XDR. + * * @author Tony Johnson (tonyj@slac.stanford.edu) * @version $Id: XDRDataOutput.java,v 1.1.1.1 2005-08-10 12:51:21 catac Exp $ */ -public interface XDRDataOutput extends DataOutput -{ - void pad() throws IOException; +public interface XDRDataOutput extends DataOutput { + void pad() throws IOException; - void writeDoubleArray(double[] array) throws IOException; + void writeDoubleArray(double[] array) throws IOException; - void writeDoubleArray(double[] array, int start, int n) throws IOException; + void writeDoubleArray(double[] array, int start, int n) throws IOException; - void writeFloatArray(float[] array) throws IOException; + void writeFloatArray(float[] array) throws IOException; - void writeFloatArray(float[] array, int start, int n) throws IOException; + void writeFloatArray(float[] array, int start, int n) throws IOException; - void writeIntArray(int[] array) throws IOException; + void writeIntArray(int[] array) throws IOException; - void writeIntArray(int[] array, int start, int n) throws IOException; + void writeIntArray(int[] array, int start, int n) throws IOException; - /** - * Write a string preceeded by its (int) length - */ - void writeString(String string) throws IOException; + /** + * Write a string preceeded by its (int) length + */ + void writeString(String string) throws IOException; - /** - * Write a string (no length is written) - */ - void writeStringChars(String string) throws IOException; + /** + * Write a string (no length is written) + */ + void writeStringChars(String string) throws IOException; } diff --git a/src/apmon/XDROutputStream.java b/src/apmon/XDROutputStream.java index aaeed66..2be61c7 100644 --- a/src/apmon/XDROutputStream.java +++ b/src/apmon/XDROutputStream.java @@ -9,105 +9,102 @@ * A class for writing XDR files. Not too hard to do in Java since the XDR format is very * similar to the Java native DataStream format, except for String and the fact that elements * (ro an array of elements) are always padded to a multiple of 4 bytes. - * + *

* This class requires the user to call the pad method, to skip to the next * 4-byte boundary after writing an element or array of elements that may not * span a multiple of 4 bytes. + * * @author Tony Johnson (tonyj@slac.stanford.edu) * @version $Id: XDROutputStream.java,v 1.1.1.1 2005-08-10 12:51:21 catac Exp $ */ -public class XDROutputStream extends DataOutputStream implements XDRDataOutput -{ - public XDROutputStream(OutputStream out) - { - super(new CountedOutputStream(out)); - cout = (CountedOutputStream) this.out; - } - public void writeString(String s) throws IOException - { - writeInt(s.length()); - byte[] ascii = s.getBytes(); - write(ascii); - pad(); - } - public void writeStringChars(String s) throws IOException - { - byte[] ascii = s.getBytes(); - write(ascii); - pad(); - } - public void writeIntArray(int[] array) throws IOException - { - writeInt(array.length); - for (int i=0; i * TODO To change the template for this generated type comment go to Window - * Preferences - Java - Code Style - Code Templates */ public class MacHostPropertiesMonitor { - protected String[] networkInterfaces; - protected String activeInterface; - protected String cpuUsage = "0"; - protected String cpuUSR = "0"; - protected String cpuSYS = "0"; - protected String cpuIDLE = "0"; - protected String nbProcesses = "0"; - protected String load1 = "0"; - protected String load5 = "0"; - protected String load15 = "0"; - protected String memUsed = "0"; - protected String memFree = "0"; - protected String memUsage = "0"; - protected String netIn = "0"; - protected String netOut = "0"; - protected String pagesIn = "0"; - protected String pagesOut = "0"; - protected String macAddress = "unknown"; - protected String diskIO = "0"; - protected String diskIn = "0"; - protected String diskOut = "0"; - protected String diskFree = "0"; - protected String diskUsed = "0"; - protected String diskTotal = "0"; - protected String command = ""; - protected static Object lock = new Object(); - protected static int ptnr = 0; - protected cmdExec execute = null; - protected String sep = null; - private Parser parser = null; + protected static Object lock = new Object(); + protected static int ptnr = 0; + protected String[] networkInterfaces; + protected String activeInterface; + protected String cpuUsage = "0"; + protected String cpuUSR = "0"; + protected String cpuSYS = "0"; + protected String cpuIDLE = "0"; + protected String nbProcesses = "0"; + protected String load1 = "0"; + protected String load5 = "0"; + protected String load15 = "0"; + protected String memUsed = "0"; + protected String memFree = "0"; + protected String memUsage = "0"; + protected String netIn = "0"; + protected String netOut = "0"; + protected String pagesIn = "0"; + protected String pagesOut = "0"; + protected String macAddress = "unknown"; + protected String diskIO = "0"; + protected String diskIn = "0"; + protected String diskOut = "0"; + protected String diskFree = "0"; + protected String diskUsed = "0"; + protected String diskTotal = "0"; + protected String command = ""; + protected cmdExec execute = null; + protected String sep = null; protected Hashtable netSockets = null; protected Hashtable netTcpDetails = null; - - public MacHostPropertiesMonitor() { + private Parser parser = null; + + public MacHostPropertiesMonitor() { - parser = new Parser(); + parser = new Parser(); execute = new cmdExec(); - execute.setTimeout(3 * 1000); - sep = System.getProperty("file.separator"); - // get the network interfaces up - command = sep + "sbin" + sep + "ifconfig -l -u"; - String result = execute.executeCommand(command, "lo0"); - //System.out.println(command + " = "+ result); - if (result == null || result.equals("")) { - System.out.println(command + ": No result???"); - } else { - int where = result.indexOf("lo0"); - networkInterfaces = - result - .substring(where + 3, result.length()) - .replaceAll(" ", " ") - .trim() - .split( - " "); - - // get the currently used Mac Address - for (int i = 0; i < networkInterfaces.length; i++) { - String current = networkInterfaces[i]; - command = sep + "sbin" + sep + "ifconfig " + current; - result = execute.executeCommand(command, current); - //System.out.println(command + " = " + result); - if (result == null || result.equals("")) { - System.out.println(command + ": No result???"); - } else { - if (result.indexOf("inet ") != -1) { - int pointI = result.indexOf("ether"); - int pointJ = result.indexOf("media", pointI); - macAddress = - result.substring(pointI + 5, pointJ).trim(); - //System.out.println("Mac Address:" + macAddress); - activeInterface = current; - } - } - } - } - - // get the disk information - command = sep + "bin" + sep + "df -k -h " + sep; - result = execute.executeCommand(command, "/dev"); - //System.out.println(command + " = "+ result); - if (result == null || result.equals("")) { - System.out.println(command + ": No result???"); - } else { - parseDf(result); - } - - update(); - } - - public String getMacAddresses() { - return macAddress; - } - - public void update() { - - if (execute == null) { - execute = new cmdExec(); - execute.setTimeout(100 * 1000); - } - - // get CPU, load, Mem, Pages, Processes from '/usr/bin/top' - command = sep + "usr" + sep + "bin" + sep - + "top -d -l2 -n1 -F -R -X"; - String result = execute.executeCommand(command, "PID", 2); - //System.out.println(command + " = "+ result); - if (result == null || result.equals("")) { - System.out.println("No result???"); - } else { - parseTop(result); - } - } - - private void parseIfConfig(String toParse) { - - //System.out.println("result of ifconfig:" + toParse); - if (toParse.indexOf("inet ") != -1) { - int pointI = toParse.indexOf("ether"); - int pointJ = toParse.indexOf("media", pointI); - macAddress = toParse.substring(pointJ + 5, pointI).trim(); - System.out.println("Mac Address:" + macAddress); - } - } - - private void parseDf(String toParse) { - - //System.out.println("result of df -k -h /:" + toParse); - - int pointI = toParse.indexOf("/dev/"); - int pointJ = 0; - int pointK = 0; - - // Get the size of the root disk - try { - pointJ = toParse.indexOf(" ", pointI); - pointK = indexOfUnitLetter(toParse, pointJ); - diskTotal = toParse.substring(pointJ, pointK).trim(); - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - - // Get the capacity used - try { - pointI = toParse.indexOf(" ", pointK); - pointJ = indexOfUnitLetter(toParse, pointI); - diskUsed = toParse.substring(pointI, pointJ).trim(); - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - - // Get the free space - try { - pointK = toParse.indexOf(" ", pointJ); - pointI = indexOfUnitLetter(toParse, pointK); - diskFree = toParse.substring(pointK, pointI).trim(); - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; + execute.setTimeout(3 * 1000); + sep = System.getProperty("file.separator"); + // get the network interfaces up + command = sep + "sbin" + sep + "ifconfig -l -u"; + String result = execute.executeCommand(command, "lo0"); + //System.out.println(command + " = "+ result); + if (result == null || result.equals("")) { + System.out.println(command + ": No result???"); + } else { + int where = result.indexOf("lo0"); + networkInterfaces = + result + .substring(where + 3, result.length()) + .replaceAll(" ", " ") + .trim() + .split( + " "); + + // get the currently used Mac Address + for (int i = 0; i < networkInterfaces.length; i++) { + String current = networkInterfaces[i]; + command = sep + "sbin" + sep + "ifconfig " + current; + result = execute.executeCommand(command, current); + //System.out.println(command + " = " + result); + if (result == null || result.equals("")) { + System.out.println(command + ": No result???"); + } else { + if (result.indexOf("inet ") != -1) { + int pointI = result.indexOf("ether"); + int pointJ = result.indexOf("media", pointI); + macAddress = + result.substring(pointI + 5, pointJ).trim(); + //System.out.println("Mac Address:" + macAddress); + activeInterface = current; + } + } + } + } + + // get the disk information + command = sep + "bin" + sep + "df -k -h " + sep; + result = execute.executeCommand(command, "/dev"); + //System.out.println(command + " = "+ result); + if (result == null || result.equals("")) { + System.out.println(command + ": No result???"); + } else { + parseDf(result); + } + + update(); + } + + public String getMacAddresses() { + return macAddress; + } + + public void update() { + + if (execute == null) { + execute = new cmdExec(); + execute.setTimeout(100 * 1000); + } + + // get CPU, load, Mem, Pages, Processes from '/usr/bin/top' + command = sep + "usr" + sep + "bin" + sep + + "top -d -l2 -n1 -F -R -X"; + String result = execute.executeCommand(command, "PID", 2); + //System.out.println(command + " = "+ result); + if (result == null || result.equals("")) { + System.out.println("No result???"); + } else { + parseTop(result); + } + } + + private void parseIfConfig(String toParse) { + + //System.out.println("result of ifconfig:" + toParse); + if (toParse.indexOf("inet ") != -1) { + int pointI = toParse.indexOf("ether"); + int pointJ = toParse.indexOf("media", pointI); + macAddress = toParse.substring(pointJ + 5, pointI).trim(); + System.out.println("Mac Address:" + macAddress); + } + } + + private void parseDf(String toParse) { + + //System.out.println("result of df -k -h /:" + toParse); + + int pointI = toParse.indexOf("/dev/"); + int pointJ = 0; + int pointK = 0; + + // Get the size of the root disk + try { + pointJ = toParse.indexOf(" ", pointI); + pointK = indexOfUnitLetter(toParse, pointJ); + diskTotal = toParse.substring(pointJ, pointK).trim(); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get the capacity used + try { + pointI = toParse.indexOf(" ", pointK); + pointJ = indexOfUnitLetter(toParse, pointI); + diskUsed = toParse.substring(pointI, pointJ).trim(); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get the free space + try { + pointK = toParse.indexOf(" ", pointJ); + pointI = indexOfUnitLetter(toParse, pointK); + diskFree = toParse.substring(pointK, pointI).trim(); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; /*System.out.println( - "Disk: Total:" + "Disk: Total:" + diskTotal + " Used:" + diskUsed + " Free:" + diskFree);*/ - } - - private int indexOfUnitLetter(String inside, int from) { - - int temp = inside.indexOf('K', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('M', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('G', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('B', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('T', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('b', from); - if (temp - from > 10) - temp = -1; - } - } - } - } - } - return temp; - } - - private int lastIndexOfUnitLetter(String inside, int from) { - - int temp = inside.lastIndexOf('K', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('M', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('G', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('B', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('T', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('b', from); - if (from - temp > 10) - temp = -1; - } - } - } - } - } - return temp; - } - private double howMuchKiloBytes(char a) { - - switch (a) { - case 'T' : - return 1073741824.0; - case 'G' : - return 1048576.0; - case 'M' : - return 1024.0; - case 'K' : - return 1.0; - case 'B' : - return 0.0009765625; - default : - return 1.0; - } - } - - private double howMuchMegaBytes(char a) { - - switch (a) { - case 'T' : - return 1048576.0; - case 'G' : - return 1024.0; - case 'M' : - return 1.0; - case 'K' : - return 0.0009765625; - case 'B' : - return 0.0000009537; - default : - return 1.0; - } - } - - private void parseNetstat(){ + } + + private int indexOfUnitLetter(String inside, int from) { + + int temp = inside.indexOf('K', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('M', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('G', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('B', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('T', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('b', from); + if (temp - from > 10) + temp = -1; + } + } + } + } + } + return temp; + } + + private int lastIndexOfUnitLetter(String inside, int from) { + + int temp = inside.lastIndexOf('K', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('M', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('G', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('B', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('T', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('b', from); + if (from - temp > 10) + temp = -1; + } + } + } + } + } + return temp; + } + + private double howMuchKiloBytes(char a) { + + switch (a) { + case 'T': + return 1073741824.0; + case 'G': + return 1048576.0; + case 'M': + return 1024.0; + case 'K': + return 1.0; + case 'B': + return 0.0009765625; + default: + return 1.0; + } + } + + private double howMuchMegaBytes(char a) { + + switch (a) { + case 'T': + return 1048576.0; + case 'G': + return 1024.0; + case 'M': + return 1.0; + case 'K': + return 0.0009765625; + case 'B': + return 0.0000009537; + default: + return 1.0; + } + } + + private void parseNetstat() { String output = execute.executeCommandReality("netstat -an", ""); if (output != null && !output.equals("")) { parser.parse(output); String line = parser.nextLine(); - + netSockets.put("tcp", new Integer(0)); netSockets.put("udp", new Integer(0)); netSockets.put("unix", new Integer(0)); @@ -280,8 +284,8 @@ private void parseNetstat(){ netTcpDetails.put("LISTEN", new Integer(0)); netTcpDetails.put("CLOSING", new Integer(0)); netTcpDetails.put("UNKNOWN", new Integer(0)); - - try{ + + try { String key = null; int value = 0; while (line != null) { @@ -289,201 +293,208 @@ private void parseNetstat(){ line = parser.nextLine(); continue; } - if(line.startsWith("tcp")){ + if (line.startsWith("tcp")) { key = "tcp"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); - StringTokenizer st = new StringTokenizer(line, " []"); - while(st.hasMoreTokens()){ + StringTokenizer st = new StringTokenizer(line, " []"); + while (st.hasMoreTokens()) { String element = st.nextToken(); key = element; - if(netTcpDetails.containsKey(element)){ - value = ((Integer)netTcpDetails.get(key)).intValue(); + if (netTcpDetails.containsKey(element)) { + value = ((Integer) netTcpDetails.get(key)).intValue(); value++; netTcpDetails.put(key, new Integer(value)); } } } - if(line.startsWith("udp")){ + if (line.startsWith("udp")) { key = "udp"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); } - if(line.startsWith("unix")){ + if (line.startsWith("unix")) { key = "unix"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); } - if(line.startsWith("icm")){ + if (line.startsWith("icm")) { key = "icm"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); } line = parser.nextLine(); } - }catch (Exception e) { + } catch (Exception e) { } } } - private void parseTop(String toParse) { - - //System.out.println("\n******\n"+toParse+"\n********\n"); - - int pointA = 0; - int pointB = 0; - int unitPos = 0; - double sum = 0.0; - - // Get number of total Processes - try { - pointA = toParse.indexOf("Procs:"); - //System.out.println("First Procs at " + pointA); - pointA = toParse.indexOf("Procs:", pointA + 6) + 6; - //System.out.println("Second Procs at " + pointA); - pointB = toParse.indexOf(",", pointA + 1); - nbProcesses = toParse.substring(pointA, pointB).trim(); - //System.out.println(nbProcesses + " processes"); - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - - // Get the loads... - try { - pointA = toParse.indexOf("LoadAvg:", pointA); - pointA += 9; - pointB = toParse.indexOf(",", pointA); - load1 = toParse.substring(pointA, pointB).trim(); - pointA = toParse.indexOf(",", pointB + 1); - load5 = toParse.substring(pointB + 1, pointA).trim(); - pointB = toParse.indexOf("CPU:", pointA + 1); - pointB = toParse.lastIndexOf(".", pointB); - load15 = toParse.substring(pointA + 1, pointB).trim(); - //System.out.println("load: [" + load1 + "][" + load5 + "][" + load15 + "]"); - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - - // Get CPUs... - try { - pointB = toParse.indexOf("CPU:", pointB + 1) + 4; - pointA = toParse.indexOf("% user", pointB); - cpuUSR = toParse.substring(pointB, pointA).trim(); - pointA = toParse.indexOf(",", pointA); - pointB = toParse.indexOf("% sys", pointA + 1); - cpuSYS = toParse.substring(pointA + 1, pointB).trim(); - pointA = toParse.indexOf(",", pointB); - pointB = toParse.indexOf("% idle", pointA + 1); - cpuIDLE = toParse.substring(pointA + 1, pointB).trim(); - sum = 100.0 - Double.parseDouble(cpuIDLE); - cpuUsage = String.valueOf(sum); + private void parseTop(String toParse) { + + //System.out.println("\n******\n"+toParse+"\n********\n"); + + int pointA = 0; + int pointB = 0; + int unitPos = 0; + double sum = 0.0; + + // Get number of total Processes + try { + pointA = toParse.indexOf("Procs:"); + //System.out.println("First Procs at " + pointA); + pointA = toParse.indexOf("Procs:", pointA + 6) + 6; + //System.out.println("Second Procs at " + pointA); + pointB = toParse.indexOf(",", pointA + 1); + nbProcesses = toParse.substring(pointA, pointB).trim(); + //System.out.println(nbProcesses + " processes"); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get the loads... + try { + pointA = toParse.indexOf("LoadAvg:", pointA); + pointA += 9; + pointB = toParse.indexOf(",", pointA); + load1 = toParse.substring(pointA, pointB).trim(); + pointA = toParse.indexOf(",", pointB + 1); + load5 = toParse.substring(pointB + 1, pointA).trim(); + pointB = toParse.indexOf("CPU:", pointA + 1); + pointB = toParse.lastIndexOf(".", pointB); + load15 = toParse.substring(pointA + 1, pointB).trim(); + //System.out.println("load: [" + load1 + "][" + load5 + "][" + load15 + "]"); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get CPUs... + try { + pointB = toParse.indexOf("CPU:", pointB + 1) + 4; + pointA = toParse.indexOf("% user", pointB); + cpuUSR = toParse.substring(pointB, pointA).trim(); + pointA = toParse.indexOf(",", pointA); + pointB = toParse.indexOf("% sys", pointA + 1); + cpuSYS = toParse.substring(pointA + 1, pointB).trim(); + pointA = toParse.indexOf(",", pointB); + pointB = toParse.indexOf("% idle", pointA + 1); + cpuIDLE = toParse.substring(pointA + 1, pointB).trim(); + sum = 100.0 - Double.parseDouble(cpuIDLE); + cpuUsage = String.valueOf(sum); /*System.out.println("Cpu Usage:" + cpuUsage + " user:" + cpuUSR + " sys:" + cpuSYS + " idle:" + cpuIDLE);*/ - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - - // Get Mem... - try { - pointA = toParse.indexOf("PhysMem", pointB); - pointA += 8; - pointB = toParse.indexOf("M used", pointA); - pointA = toParse.lastIndexOf(",", pointB); - memUsed = toParse.substring(pointA + 1, pointB).trim(); - pointB = toParse.indexOf("M free", pointB); - pointA = toParse.lastIndexOf(",", pointB); - memFree = toParse.substring(pointA + 1, pointB).trim(); - //System.out.println("Mem Used:"+memUsed+"M Free:"+memFree+"M"); - sum = Double.parseDouble(memUsed) + Double.parseDouble(memFree); - double percentage = Integer.parseInt(memUsed) / sum * 100; - memUsage = String.valueOf(percentage); - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - - // Pages In/Out... - try { - pointA = toParse.indexOf("VirtMem:", pointB + 6); - pointB = toParse.indexOf("pagein", pointA); - pointA = toParse.lastIndexOf(",", pointB); - pagesIn = toParse.substring(pointA + 1, pointB).trim(); - pointA = toParse.indexOf("pageout", pointB); - pointB = toParse.lastIndexOf(",", pointA); - pagesOut = toParse.substring(pointB + 1, pointA).trim(); - //System.out.println("Pages In:" + pagesIn + " Out" + pagesOut); - } catch (java.lang.StringIndexOutOfBoundsException e) { - System.out.println("Can't find pages in :" + toParse); - }; - - // Get Network IO... - try { - pointA = toParse.indexOf("Networks:", pointB) + 9; - pointB = toParse.indexOf("data =", pointA) + 6; - pointA = toParse.indexOf("in", pointB); - unitPos = lastIndexOfUnitLetter(toParse, pointA); - netIn = toParse.substring(pointB, unitPos).trim(); - //System.out.print("Net In:" + netIn); - double factor = - howMuchMegaBytes( - (toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); - netIn = String.valueOf(Double.parseDouble(netIn) * factor * 4); - pointB = toParse.indexOf("out", pointA); - unitPos = lastIndexOfUnitLetter(toParse, pointB); - factor = - howMuchMegaBytes( - (toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); - netOut = toParse.substring(pointA + 3, unitPos).trim(); - //System.out.println("Net Out:" + netOut); - netOut = String.valueOf(Double.parseDouble(netOut) * factor); - //System.out.println("Network In:" + netIn + " OUT:" + netOut); - } catch (java.lang.StringIndexOutOfBoundsException e) { - System.out.println(e); - }; - - // Get Disks IO... - try { - pointB = toParse.indexOf("Disks:", pointA) + 6; - pointA = toParse.indexOf("data =", pointB) + 6; - pointB = toParse.indexOf("in,", pointA); - unitPos = lastIndexOfUnitLetter(toParse, pointB); - diskIn = toParse.substring(pointA, unitPos).trim(); - pointA = toParse.indexOf("out", pointB); - unitPos = lastIndexOfUnitLetter(toParse, pointA); - diskOut = toParse.substring(pointB + 3, unitPos).trim(); - - //System.out.println("diskIO In:" + diskIn + " Out:" + diskOut); - diskIO = diskOut; - } catch (java.lang.StringIndexOutOfBoundsException e) { - }; - } - + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get Mem... + try { + pointA = toParse.indexOf("PhysMem", pointB); + pointA += 8; + pointB = toParse.indexOf("M used", pointA); + pointA = toParse.lastIndexOf(",", pointB); + memUsed = toParse.substring(pointA + 1, pointB).trim(); + pointB = toParse.indexOf("M free", pointB); + pointA = toParse.lastIndexOf(",", pointB); + memFree = toParse.substring(pointA + 1, pointB).trim(); + //System.out.println("Mem Used:"+memUsed+"M Free:"+memFree+"M"); + sum = Double.parseDouble(memUsed) + Double.parseDouble(memFree); + double percentage = Integer.parseInt(memUsed) / sum * 100; + memUsage = String.valueOf(percentage); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Pages In/Out... + try { + pointA = toParse.indexOf("VirtMem:", pointB + 6); + pointB = toParse.indexOf("pagein", pointA); + pointA = toParse.lastIndexOf(",", pointB); + pagesIn = toParse.substring(pointA + 1, pointB).trim(); + pointA = toParse.indexOf("pageout", pointB); + pointB = toParse.lastIndexOf(",", pointA); + pagesOut = toParse.substring(pointB + 1, pointA).trim(); + //System.out.println("Pages In:" + pagesIn + " Out" + pagesOut); + } catch (java.lang.StringIndexOutOfBoundsException e) { + System.out.println("Can't find pages in :" + toParse); + } + ; + + // Get Network IO... + try { + pointA = toParse.indexOf("Networks:", pointB) + 9; + pointB = toParse.indexOf("data =", pointA) + 6; + pointA = toParse.indexOf("in", pointB); + unitPos = lastIndexOfUnitLetter(toParse, pointA); + netIn = toParse.substring(pointB, unitPos).trim(); + //System.out.print("Net In:" + netIn); + double factor = + howMuchMegaBytes( + (toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); + netIn = String.valueOf(Double.parseDouble(netIn) * factor * 4); + pointB = toParse.indexOf("out", pointA); + unitPos = lastIndexOfUnitLetter(toParse, pointB); + factor = + howMuchMegaBytes( + (toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); + netOut = toParse.substring(pointA + 3, unitPos).trim(); + //System.out.println("Net Out:" + netOut); + netOut = String.valueOf(Double.parseDouble(netOut) * factor); + //System.out.println("Network In:" + netIn + " OUT:" + netOut); + } catch (java.lang.StringIndexOutOfBoundsException e) { + System.out.println(e); + } + ; + + // Get Disks IO... + try { + pointB = toParse.indexOf("Disks:", pointA) + 6; + pointA = toParse.indexOf("data =", pointB) + 6; + pointB = toParse.indexOf("in,", pointA); + unitPos = lastIndexOfUnitLetter(toParse, pointB); + diskIn = toParse.substring(pointA, unitPos).trim(); + pointA = toParse.indexOf("out", pointB); + unitPos = lastIndexOfUnitLetter(toParse, pointA); + diskOut = toParse.substring(pointB + 3, unitPos).trim(); + + //System.out.println("diskIO In:" + diskIn + " Out:" + diskOut); + diskIO = diskOut; + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + } + public Hashtable getProcessesState() { Hashtable states = null; String output = execute.executeCommandReality("ps -e -A -o state", ""); if (output != null && !output.equals("")) { - + parser.parse(output); String line = parser.nextLine(); int nr = 0; - + states = new Hashtable(); states.put("D", new Integer(0)); states.put("R", new Integer(0)); states.put("S", new Integer(0)); states.put("T", new Integer(0)); states.put("Z", new Integer(0)); - + while (line != null) { if (line != null && (line.startsWith(" ") || line.startsWith("\t"))) { line = parser.nextLine(); continue; } Enumeration e = states.keys(); - while(e.hasMoreElements()){ - String key = (String)e.nextElement(); - if(line.startsWith(key)){ - int x = ((Integer)states.get(key)).intValue(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + if (line.startsWith(key)) { + int x = ((Integer) states.get(key)).intValue(); x++; states.put(key, new Integer(x)); } @@ -494,100 +505,100 @@ public Hashtable getProcessesState() { return states; } - public String getCpuUsage() { - return cpuUsage; - } + public String getCpuUsage() { + return cpuUsage; + } - public String getCpuUSR() { - return cpuUSR; - } + public String getCpuUSR() { + return cpuUSR; + } - public String getCpuSYS() { - return cpuSYS; - } + public String getCpuSYS() { + return cpuSYS; + } - public String getCpuNICE() { - return "0"; - } + public String getCpuNICE() { + return "0"; + } - public String getCpuIDLE() { - return cpuIDLE; - } + public String getCpuIDLE() { + return cpuIDLE; + } - public String getPagesIn() { - return pagesIn; - } + public String getPagesIn() { + return pagesIn; + } - public String getPagesOut() { - return pagesOut; - } + public String getPagesOut() { + return pagesOut; + } - public String getMemUsage() { - return memUsage; - } + public String getMemUsage() { + return memUsage; + } - public String getMemUsed() { - return memUsed; - } + public String getMemUsed() { + return memUsed; + } - public String getMemFree() { - return memFree; - } + public String getMemFree() { + return memFree; + } + + public String getDiskIO() { + return diskIO; + } - public String getDiskIO() { - return diskIO; - } + public String getDiskTotal() { + return diskTotal; + } - public String getDiskTotal() { - return diskTotal; - } + public String getDiskUsed() { + return diskUsed; + } - public String getDiskUsed() { - return diskUsed; - } + public String getDiskFree() { + return diskFree; + } - public String getDiskFree() { - return diskFree; - } + public String getNoProcesses() { + return nbProcesses; + } - public String getNoProcesses() { - return nbProcesses; - } - public Hashtable getNetSockets() { return netSockets; } - + public Hashtable getTcpDetails() { return netTcpDetails; } - public String getLoad1() { - return load1; - } + public String getLoad1() { + return load1; + } - public String getLoad5() { - return load5; - } + public String getLoad5() { + return load5; + } - public String getLoad15() { - return load15; - } + public String getLoad15() { + return load15; + } - public String[] getNetInterfaces() { - return networkInterfaces; - } + public String[] getNetInterfaces() { + return networkInterfaces; + } - public String getNetIn(String ifName) { - if (ifName.equalsIgnoreCase(activeInterface)) - return netIn; - else - return "0"; - } + public String getNetIn(String ifName) { + if (ifName.equalsIgnoreCase(activeInterface)) + return netIn; + else + return "0"; + } - public String getNetOut(String ifName) { - if (ifName.equalsIgnoreCase(activeInterface)) - return netOut; - return "0"; - } + public String getNetOut(String ifName) { + if (ifName.equalsIgnoreCase(activeInterface)) + return netOut; + return "0"; + } } diff --git a/src/apmon/host/Parser.java b/src/apmon/host/Parser.java index 8ecc626..24732ea 100644 --- a/src/apmon/host/Parser.java +++ b/src/apmon/host/Parser.java @@ -106,7 +106,7 @@ public String nextToken(String token) { public boolean hasMoreAuxTokens() { return (auxSt != null && auxSt.hasMoreTokens()); } - + public String nextAuxToken() { if (auxSt == null) diff --git a/src/apmon/host/ProcReader.java b/src/apmon/host/ProcReader.java index 9ee8717..1f3843e 100644 --- a/src/apmon/host/ProcReader.java +++ b/src/apmon/host/ProcReader.java @@ -1,23 +1,23 @@ package apmon.host; +import apmon.ApMonMonitoringConstants; + import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.StringTokenizer; -import apmon.ApMonMonitoringConstants; - //import lisa.core.util.cmdExec; /** * Designated class that reads from the proc different parameter values. */ public class ProcReader { - - static int cnt = 0; + + static int cnt = 0; private HashMap hm = null; - + private cmdExec exec = null; private Parser parser = null; @@ -71,9 +71,9 @@ public class ProcReader { private String diskIO = null; private long ddiskIO = 0; - + private long dblkRead = 0; - + private long dblkWrite = 0; private String diskTotal = null; @@ -99,11 +99,11 @@ public class ProcReader { private Hashtable netOut = null; private Hashtable dnetOut = null; - + private Hashtable states = null; - + private Hashtable netSockets = null; - + private Hashtable netTcpDetails = null; private long lastCall = 0; @@ -118,10 +118,64 @@ public ProcReader() { states = new Hashtable(); netSockets = new Hashtable(); netTcpDetails = new Hashtable(); - + update(); } + public static void main(String[] args) { + + ProcReader reader = new ProcReader(); + while (true) { + reader.update(); + System.out.println(""); + System.out.println("CPU Sys: " + reader.getCPUSys()); + System.out.println("CPU Usr: " + reader.getCPUUsr()); + System.out.println("CPU Nice: " + reader.getCPUNice()); + System.out.println("CPU Idle: " + reader.getCPUIdle()); + System.out.println("CPU Usage: " + reader.getCPUUsage()); + System.out.println(""); + System.out.println("Pages in: " + reader.getPagesIn()); + System.out.println("Pages out: " + reader.getPagesOut()); + System.out.println(""); + System.out.println("Mem usage: " + reader.getMemUsage()); + System.out.println("Mem used: " + reader.getMemUsed()); + System.out.println("Mem free: " + reader.getMemFree()); + System.out.println(""); + System.out.println("Disk total: " + reader.getDiskTotal()); + System.out.println("Disk used: " + reader.getDiskUsed()); + System.out.println("Disk free: " + reader.getDiskFree()); + System.out.println("Disk usage: " + reader.getDiskUsage()); + System.out.println("Disk IO: " + reader.getDiskIO()); + System.out.println(""); + System.out.println("Processes: " + reader.getNoProcesses()); + System.out.println("Load1: " + reader.getLoad1()); + System.out.println("Load5: " + reader.getLoad5()); + System.out.println("Load15: " + reader.getLoad15()); + System.out.println(""); + System.out.println("MAC: " + reader.getMacAddress()); + System.out.println("Net IFS"); + String netIfs[] = reader.getNetInterfaces(); + if (netIfs != null) { + for (int i = 0; i < netIfs.length; i++) + System.out.print(netIfs[i] + " "); + System.out.println(""); + System.out.println("Net in"); + for (int i = 0; i < netIfs.length; i++) + System.out.print(reader.getNetIn(netIfs[i]) + " "); + System.out.println(""); + System.out.println("Net out"); + for (int i = 0; i < netIfs.length; i++) + System.out.print(reader.getNetOut(netIfs[i]) + " "); + System.out.println(""); + } + System.out.println(""); + try { + Thread.sleep(1000); + } catch (Exception e) { + } + } + } + private void addNetInterface(String netInterface) { if (netInterface == null || netInterface.equals("")) return; @@ -141,8 +195,8 @@ private void addNetInterface(String netInterface) { public synchronized void update() { - cnt++; - + cnt++; + long newCall = System.currentTimeMillis(); double diffCall = (newCall - lastCall) / 1000.0; // in seconds @@ -217,9 +271,9 @@ public synchronized void update() { parser.parseAux(line); long dcUsr = 0, dcSys = 0, dcNice = 0, dcIdle = 0; line = parser.nextAuxToken(); // cpu usr - try { + try { dcUsr = Long.parseLong(line); - + } catch (Exception e) { dcUsr = -1; } @@ -241,7 +295,7 @@ public synchronized void update() { } catch (Exception e) { dcIdle = -1; } - + double tmpUsr = diffWithOverflowCheck(dcUsr, dcpuUsr) / diffCall; double tmpSys = (diffWithOverflowCheck(dcSys, dcpuSys)) / diffCall; double tmpIdle = (diffWithOverflowCheck(dcIdle, dcpuIdle)) / diffCall; @@ -318,9 +372,9 @@ public synchronized void update() { if (str == null) break; str = parser.nextToken(" \t\n"); // skip tps str = parser.nextToken(" \t\n"); // skip KB read / - // sec + // sec str = parser.nextToken(" \t\n"); // skip KB write / - // sec + // sec str = parser.nextToken(" \t\n"); // blk read / sec long l = 0; try { @@ -338,13 +392,13 @@ public synchronized void update() { } if (l >= 0.0) blkWrite += l; } - - double dRead = (diffWithOverflowCheck(blkRead, dblkRead)) /diffCall; - double dWrite = (diffWithOverflowCheck(blkWrite, dblkWrite)) /diffCall; + + double dRead = (diffWithOverflowCheck(blkRead, dblkRead)) / diffCall; + double dWrite = (diffWithOverflowCheck(blkWrite, dblkWrite)) / diffCall; diskIO = "" + (dRead + dWrite); //ddiskIO = blkRead + blkWrite; - dblkRead = blkRead; - dblkWrite = blkWrite; + dblkRead = blkRead; + dblkWrite = blkWrite; } } else if (line != null) { str = parser.nextToken(" \t\n"); @@ -359,9 +413,9 @@ public synchronized void update() { if (str == null) break; str = parser.nextToken(" \t\n"); // skip tps str = parser.nextToken(" \t\n"); // skip KB read / - // sec + // sec str = parser.nextToken(" \t\n"); // skip KB write / - // sec + // sec str = parser.nextToken(" \t\n"); // blk read / sec long l = 0; try { @@ -379,13 +433,13 @@ public synchronized void update() { } if (l >= 0) blkWrite += l; } - double dRead = (diffWithOverflowCheck(blkRead, dblkRead)) /diffCall; - double dWrite = (diffWithOverflowCheck(blkWrite, dblkWrite)) /diffCall; - diskIO = ""+(dRead + dWrite); - ddiskIO = blkRead + blkWrite; + double dRead = (diffWithOverflowCheck(blkRead, dblkRead)) / diffCall; + double dWrite = (diffWithOverflowCheck(blkWrite, dblkWrite)) / diffCall; + diskIO = "" + (dRead + dWrite); + ddiskIO = blkRead + blkWrite; - dblkRead = blkRead; - dblkWrite = blkWrite; + dblkRead = blkRead; + dblkWrite = blkWrite; } } } @@ -426,17 +480,17 @@ public synchronized void update() { parser.parseAux(line); swapFree = parser.nextAuxToken(); } - - + + line = parser.nextLine(); } - + double dst = Double.valueOf(swapTotal).doubleValue(); double dsf = Double.valueOf(swapFree).doubleValue(); double dsu = dst - dsf; - swapUsed = ""+dsu; - swapUsage = ""+(1.0 - dsu/dst)*100; + swapUsed = "" + dsu; + swapUsage = "" + (1.0 - dsu / dst) * 100; memFree = "" + (dmemFree / 1024.0); memUsed = "" + ((dmemTotal - dmemFree) / 1024.0); @@ -498,7 +552,7 @@ public synchronized void update() { diskTotal = "" + (size / (1024.0 * 1024.0)); // total size (GB) diskUsed = "" + (used / (1024.0 * 1024.0)); // used size (GB) diskFree = "" + (available / (1024.0 * 1024.0)); // free size - // (GB) + // (GB) diskUsage = "" + (usage * 1.0 / nr); // usage (%) } else { // read from /proc/ide String files[] = parser.listFiles("/proc/ide"); @@ -517,29 +571,29 @@ public synchronized void update() { diskTotal = "" + (size / (1024.0 * 1024.0)); // disk total (GB) diskFree = diskTotal; } - - /** + + /** * old version - * - String[] files = parser.listFiles("/proc"); - processesNo = null; - if (files != null && files.length != 0) { - int nr = 0; - for (int i = 0; i < files.length; i++) { - char[] chars = files[i].toCharArray(); - boolean isProc = true; - for (int j = 0; j < chars.length; j++) - if (!Character.isDigit(chars[j])) { - isProc = false; - break; - } - if (isProc) nr++; - } - processesNo = "" + nr; - } - */ + * + String[] files = parser.listFiles("/proc"); + processesNo = null; + if (files != null && files.length != 0) { + int nr = 0; + for (int i = 0; i < files.length; i++) { + char[] chars = files[i].toCharArray(); + boolean isProc = true; + for (int j = 0; j < chars.length; j++) + if (!Character.isDigit(chars[j])) { + isProc = false; + break; + } + if (isProc) nr++; + } + processesNo = "" + nr; + } + */ - /** + /** * new version */ output = exec.executeCommandReality("ps -e -A -o state", ""); @@ -548,7 +602,7 @@ public synchronized void update() { line = parser.nextLine(); processesNo = null; int pNo = 0; - + states.put("D", new Integer(0)); states.put("R", new Integer(0)); states.put("S", new Integer(0)); @@ -561,10 +615,10 @@ public synchronized void update() { continue; } Enumeration e = states.keys(); - while(e.hasMoreElements()){ - String key = (String)e.nextElement(); - if(line.startsWith(key)){ - int value = ((Integer)states.get(key)).intValue(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + if (line.startsWith(key)) { + int value = ((Integer) states.get(key)).intValue(); value++; pNo++; states.put(key, new Integer(value)); @@ -575,14 +629,14 @@ public synchronized void update() { processesNo = "" + pNo; } - /** + /** * run netstat */ output = exec.executeCommandReality("netstat -an", ""); if (output != null && !output.equals("")) { parser.parse(output); line = parser.nextLine(); - + netSockets.put("tcp", new Integer(0)); netSockets.put("udp", new Integer(0)); netSockets.put("unix", new Integer(0)); @@ -600,8 +654,8 @@ public synchronized void update() { netTcpDetails.put("LISTEN", new Integer(0)); netTcpDetails.put("CLOSING", new Integer(0)); netTcpDetails.put("UNKNOWN", new Integer(0)); - - try{ + + try { String key = null; int value = 0; while (line != null) { @@ -609,47 +663,47 @@ public synchronized void update() { line = parser.nextLine(); continue; } - if(line.startsWith("tcp")){ + if (line.startsWith("tcp")) { key = "tcp"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); - StringTokenizer st = new StringTokenizer(line, " []"); - while(st.hasMoreTokens()){ + StringTokenizer st = new StringTokenizer(line, " []"); + while (st.hasMoreTokens()) { String element = st.nextToken(); key = element; - if(netTcpDetails.containsKey(element)){ - value = ((Integer)netTcpDetails.get(key)).intValue(); + if (netTcpDetails.containsKey(element)) { + value = ((Integer) netTcpDetails.get(key)).intValue(); value++; netTcpDetails.put(key, new Integer(value)); } } } - if(line.startsWith("udp")){ + if (line.startsWith("udp")) { key = "udp"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); } - if(line.startsWith("unix")){ + if (line.startsWith("unix")) { key = "unix"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); } - if(line.startsWith("icm")){ + if (line.startsWith("icm")) { key = "icm"; - value = ((Integer)netSockets.get(key)).intValue(); + value = ((Integer) netSockets.get(key)).intValue(); value++; netSockets.put(key, new Integer(value)); } line = parser.nextLine(); } - }catch (Exception e) { + } catch (Exception e) { } } - - + + parser.parseFromFile("/proc/loadavg"); load1 = load5 = load15 = null; line = parser.nextToken(" \t\n"); // load1 @@ -702,7 +756,7 @@ public synchronized void update() { d = -1; } if (oldReceived >= 0 && d >= 0) { - double in = (diffWithOverflowCheck(d,oldReceived)) / diffCall; + double in = (diffWithOverflowCheck(d, oldReceived)) / diffCall; //double in = (d - oldReceived) / diffCall; in = in / (1024.0 * 1024.0); oldReceived = d; @@ -739,7 +793,7 @@ public synchronized void update() { d = -1; } if (oldSent >= 0 && d >= 0) { - double out = (diffWithOverflowCheck(d, oldSent)) / diffCall; + double out = (diffWithOverflowCheck(d, oldSent)) / diffCall; //double out = (d - oldSent) / diffCall; out = out / (1024.0 * 1024.0); oldSent = d; @@ -788,7 +842,7 @@ public synchronized void update() { d = -1; } if (oldReceived >= 0 && d >= 0) { - double in = (diffWithOverflowCheck(d, oldReceived)) / diffCall; + double in = (diffWithOverflowCheck(d, oldReceived)) / diffCall; //double in = (d - oldReceived) / diffCall; in = in / (1024.0 * 1024.0); oldReceived = d; @@ -823,10 +877,10 @@ public synchronized void update() { d = Long.parseLong(line); } catch (Exception e) { d = -1; - } - + } + if (oldSent >= 0 && d >= 0) { - double out = (diffWithOverflowCheck(d, oldSent)) / diffCall; + double out = (diffWithOverflowCheck(d, oldSent)) / diffCall; //double out = (d - oldSent) / diffCall; out = out / (1024.0 * 1024.0); oldSent = d; @@ -861,7 +915,7 @@ public synchronized void update() { hm.put(ApMonMonitoringConstants.LSYS_CPU_SYS, cpuSys); hm.put(ApMonMonitoringConstants.LSYS_CPU_IDLE, cpuIdle); hm.put(ApMonMonitoringConstants.LSYS_CPU_USAGE, cpuUsage); - + hm.put(ApMonMonitoringConstants.LSYS_MEM_FREE, memFree); hm.put(ApMonMonitoringConstants.LSYS_MEM_USED, memUsed); hm.put(ApMonMonitoringConstants.LSYS_MEM_USAGE, memUsage); @@ -881,7 +935,7 @@ public synchronized void update() { public synchronized HashMap getHashedValues() { return hm; } - + public synchronized String getMacAddress() { if (hwAddress != null) hwAddress = hwAddress.trim(); @@ -1013,15 +1067,15 @@ public synchronized String getLoad5() { if (load5 != null) load5 = load5.trim(); return load5; } - + public synchronized Hashtable getProcessesState() { return states; } - + public synchronized Hashtable getNetSockets() { return netSockets; } - + public synchronized Hashtable getTcpDetails() { return netTcpDetails; } @@ -1055,85 +1109,31 @@ public synchronized String getNetOut(String netInterface) { } return null; } - + public void stopIt() { - exec.stopIt(); + exec.stopIt(); } - + /** - * Computes the difference between the new value and the old value of a + * Computes the difference between the new value and the old value of a * counter, considering the case when the counter reaches its maximum * value and is reset. We assume the counter has 32 bits. */ public long diffWithOverflowCheck(long newVal, long oldVal) { if (newVal >= oldVal) { - return newVal - oldVal; + return newVal - oldVal; } else { - long vmax; - long p32 = 1L << 32; - long p64 = 1L << 64; - if (oldVal < p32) - vmax = p32; // 32 bits - else - vmax = p64; // 64 bits - - return newVal - oldVal + vmax; + long vmax; + long p32 = 1L << 32; + long p64 = 1L << 64; + if (oldVal < p32) + vmax = p32; // 32 bits + else + vmax = p64; // 64 bits + + return newVal - oldVal + vmax; } } - - public static void main(String[] args) { - ProcReader reader = new ProcReader(); - while (true) { - reader.update(); - System.out.println(""); - System.out.println("CPU Sys: " + reader.getCPUSys()); - System.out.println("CPU Usr: " + reader.getCPUUsr()); - System.out.println("CPU Nice: " + reader.getCPUNice()); - System.out.println("CPU Idle: " + reader.getCPUIdle()); - System.out.println("CPU Usage: " + reader.getCPUUsage()); - System.out.println(""); - System.out.println("Pages in: " + reader.getPagesIn()); - System.out.println("Pages out: " + reader.getPagesOut()); - System.out.println(""); - System.out.println("Mem usage: " + reader.getMemUsage()); - System.out.println("Mem used: " + reader.getMemUsed()); - System.out.println("Mem free: " + reader.getMemFree()); - System.out.println(""); - System.out.println("Disk total: " + reader.getDiskTotal()); - System.out.println("Disk used: " + reader.getDiskUsed()); - System.out.println("Disk free: " + reader.getDiskFree()); - System.out.println("Disk usage: " + reader.getDiskUsage()); - System.out.println("Disk IO: " + reader.getDiskIO()); - System.out.println(""); - System.out.println("Processes: " + reader.getNoProcesses()); - System.out.println("Load1: " + reader.getLoad1()); - System.out.println("Load5: " + reader.getLoad5()); - System.out.println("Load15: " + reader.getLoad15()); - System.out.println(""); - System.out.println("MAC: " + reader.getMacAddress()); - System.out.println("Net IFS"); - String netIfs[] = reader.getNetInterfaces(); - if (netIfs != null) { - for (int i = 0; i < netIfs.length; i++) - System.out.print(netIfs[i] + " "); - System.out.println(""); - System.out.println("Net in"); - for (int i = 0; i < netIfs.length; i++) - System.out.print(reader.getNetIn(netIfs[i]) + " "); - System.out.println(""); - System.out.println("Net out"); - for (int i = 0; i < netIfs.length; i++) - System.out.print(reader.getNetOut(netIfs[i]) + " "); - System.out.println(""); - } - System.out.println(""); - try { - Thread.sleep(1000); - } catch (Exception e) { - } - } - } - } diff --git a/src/apmon/host/cmdExec.java b/src/apmon/host/cmdExec.java index f2d606d..a61dd63 100644 --- a/src/apmon/host/cmdExec.java +++ b/src/apmon/host/cmdExec.java @@ -9,781 +9,781 @@ public class cmdExec { - public String full_cmd; - public Process pro; - String osname; - String exehome = ""; - private long timeout = 60 * 1000; // 1 min - - protected LinkedList streams = null; - protected LinkedList streamsReal = null; - - protected boolean isError = false; - - /* These varibles are set to true when we want to destroy the streams pool */ - protected boolean stopStreams = false; - protected boolean stopStreamsReal = false; - - public cmdExec() { - osname = System.getProperty("os.name"); - exehome = System.getProperty("user.dir"); - String tot = System.getProperty("iperf.timeout"); - double dd = -1.0; - try { - dd = Double.parseDouble(tot); - } catch (Exception e) { - dd = -1.0; - } - if (dd >= 0.0) timeout = (long)(dd * 1000); - streams = new LinkedList(); - streamsReal = new LinkedList(); - } - - public void setCmd(String cmd) { - osname = System.getProperty("os.name"); - full_cmd = cmd; // local - } - - public void setTimeout(long timeout) { - - this.timeout = timeout; - } - - public BufferedReader procOutput(String cmd) { - try { - - if (osname.startsWith("Linux") || osname.startsWith("Mac")) { - pro = - Runtime.getRuntime().exec( - new String[] { "/bin/sh", "-c", cmd }); - } else if (osname.startsWith("Windows")) { - pro = Runtime.getRuntime().exec(exehome + cmd); - } - - InputStream out = pro.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(out)); - BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); - - String buffer = ""; - String ret = ""; - while((buffer = err.readLine())!= null) { - ret += buffer+"\n'"; - } - - if (ret.length() != 0){ - return null; - } - - return br; - - } catch (Exception e) { - System.out.println("FAILED to execute cmd = " + exehome + cmd); - Thread.currentThread().interrupt(); - } - - return null; - } - - public BufferedReader exeHomeOutput(String cmd) { - - try { - - pro = - Runtime.getRuntime().exec( - new String[] { "/bin/sh", "-c", exehome + cmd}); + public String full_cmd; + public Process pro; + protected LinkedList streams = null; + protected LinkedList streamsReal = null; + protected boolean isError = false; + /* These varibles are set to true when we want to destroy the streams pool */ + protected boolean stopStreams = false; + protected boolean stopStreamsReal = false; + String osname; + String exehome = ""; + private long timeout = 60 * 1000; // 1 min + + public cmdExec() { + osname = System.getProperty("os.name"); + exehome = System.getProperty("user.dir"); + String tot = System.getProperty("iperf.timeout"); + double dd = -1.0; + try { + dd = Double.parseDouble(tot); + } catch (Exception e) { + dd = -1.0; + } + if (dd >= 0.0) timeout = (long) (dd * 1000); + streams = new LinkedList(); + streamsReal = new LinkedList(); + } + + public void setCmd(String cmd) { + osname = System.getProperty("os.name"); + full_cmd = cmd; // local + } + + public void setTimeout(long timeout) { + + this.timeout = timeout; + } + + public BufferedReader procOutput(String cmd) { + try { + + if (osname.startsWith("Linux") || osname.startsWith("Mac")) { + pro = + Runtime.getRuntime().exec( + new String[]{"/bin/sh", "-c", cmd}); + } else if (osname.startsWith("Windows")) { + pro = Runtime.getRuntime().exec(exehome + cmd); + } + + InputStream out = pro.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(out)); + BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); + + String buffer = ""; + String ret = ""; + while ((buffer = err.readLine()) != null) { + ret += buffer + "\n'"; + } + + if (ret.length() != 0) { + return null; + } + + return br; + + } catch (Exception e) { + System.out.println("FAILED to execute cmd = " + exehome + cmd); + Thread.currentThread().interrupt(); + } + + return null; + } + + public BufferedReader exeHomeOutput(String cmd) { + + try { + + pro = + Runtime.getRuntime().exec( + new String[]{"/bin/sh", "-c", exehome + cmd}); // System.out.println("/bin/sh -c "+exehome + cmd); - InputStream out = pro.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(out)); - - BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); - - String buffer = ""; - String ret = ""; - while((buffer = err.readLine())!= null) { - ret += buffer+"\n'"; - } - - if (ret.length() != 0){ + InputStream out = pro.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(out)); + + BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); + + String buffer = ""; + String ret = ""; + while ((buffer = err.readLine()) != null) { + ret += buffer + "\n'"; + } + + if (ret.length() != 0) { // System.out.println(ret); - return null; - } - - - return br; - - } catch (Exception e) { - System.out.println("FAILED to execute cmd = " + exehome + cmd); - Thread.currentThread().interrupt(); - } - - return null; - } - - public void stopModule() { - - if (this.pro != null) - this.pro.destroy(); - - } - - public BufferedReader readProc(String filePath) { - - try { - return new BufferedReader(new FileReader(filePath)); - } catch (Exception e) { - - return null; - } - } - - public boolean isError() { - - return isError; - } - - public String executeCommand(String command, String expect) { - - StreamGobbler output = null; - StreamGobbler error = null; - - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - isError = true; - return null; - } - - error = getStreamGobbler(); - output = getStreamGobbler(); - - // any error message? - error.setInputStream(proc.getErrorStream()); - - // any output? - output.setInputStream(proc.getInputStream()); - - String out = ""; - - // any error??? - long startTime = new Date().getTime(); - while (true) { - out = error.getOutput(); - try { - if (!out.equals("") && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.getOutput(); - if (out != "" && out.indexOf(expect) != -1) { - isError = false; - break; - } - } - long endTime = new Date().getTime(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.equals("")) - out = output.getOutput(); - + return null; + } + + + return br; + + } catch (Exception e) { + System.out.println("FAILED to execute cmd = " + exehome + cmd); + Thread.currentThread().interrupt(); + } + + return null; + } + + public void stopModule() { + + if (this.pro != null) + this.pro.destroy(); + + } + + public BufferedReader readProc(String filePath) { + + try { + return new BufferedReader(new FileReader(filePath)); + } catch (Exception e) { + + return null; + } + } + + public boolean isError() { + + return isError; + } + + public String executeCommand(String command, String expect) { + + StreamGobbler output = null; + StreamGobbler error = null; + + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + isError = true; + return null; + } + + error = getStreamGobbler(); + output = getStreamGobbler(); + + // any error message? + error.setInputStream(proc.getErrorStream()); + + // any output? + output.setInputStream(proc.getInputStream()); + + String out = ""; + + // any error??? + long startTime = new Date().getTime(); + while (true) { + out = error.getOutput(); + try { + if (!out.equals("") && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.getOutput(); + if (out != "" && out.indexOf(expect) != -1) { + isError = false; + break; + } + } + long endTime = new Date().getTime(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.equals("")) + out = output.getOutput(); + // String ret = ""; // // if (!error.getOutput().equals("")) // ret = error.getOutput(); // // ret = output.getOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamGobbler(error); - addStreamGobbler(output); - - error = null; - output = null; - - return out; - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamGobbler(output); - output.stopIt(); - output = null; - } - isError = true; - return ""; - } - } - - - public String executeCommand(String command, String expect, int howManyTimes) { - - StreamGobbler output = null; - StreamGobbler error = null; - int nr = 0; // how many times the expect string occured - - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - isError = true; - return null; - } - - error = getStreamGobbler(); - output = getStreamGobbler(); - - error.setInputStream(proc.getErrorStream()); - - output.setInputStream(proc.getInputStream()); - - String out = ""; - - long startTime = new Date().getTime(); - while (true) { - out = error.getOutput(); - try { - if (!out.equals("") && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.getOutput(); - if (out != "" && out.indexOf(expect) != -1) { - nr = getStringOccurences(out, expect); - if (nr >= howManyTimes) { - isError = false; - break; - } - } - } - long endTime = new Date().getTime(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.equals("")) - out = output.getOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamGobbler(error); - addStreamGobbler(output); - - error = null; - output = null; - - return out; - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamGobbler(output); - output.stopIt(); - output = null; - } - isError = true; - return ""; - } - } - - protected int getStringOccurences(String text, String token) { - - if (text.indexOf(token) < 0) return 0; - int nr = 0; - String str = text; - while (str.indexOf(token) >= 0) { - str = str.substring(str.indexOf(token)+token.length()); - nr++; - } - return nr; - } - - // cipsm -> new execute command - it shows the output exactly as it is, by lines - public String executeCommandReality(String command, String expect) { - - StreamRealGobbler error = null; - StreamRealGobbler output = null; - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - isError = true; - return null; - } - - error = getStreamRealGobbler(); - output = getStreamRealGobbler(); - - // any error message? - error.setInputStream(proc.getErrorStream()); - - // any output? - output.setInputStream(proc.getInputStream()); - - String out = ""; - - // any error??? - long startTime = new Date().getTime(); - while (true) { - out = error.forceAllOutput(); - try { - if (!out.equals("") && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.forceAllOutput(); - if (out != "" && out.indexOf(expect) != -1) { - isError = false; - break; - } - } - long endTime = new Date().getTime(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.equals("")) - out = output.forceAllOutput(); - + + error.stopIt(); + output.stopIt(); + + addStreamGobbler(error); + addStreamGobbler(output); + + error = null; + output = null; + + return out; + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamGobbler(output); + output.stopIt(); + output = null; + } + isError = true; + return ""; + } + } + + + public String executeCommand(String command, String expect, int howManyTimes) { + + StreamGobbler output = null; + StreamGobbler error = null; + int nr = 0; // how many times the expect string occured + + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + isError = true; + return null; + } + + error = getStreamGobbler(); + output = getStreamGobbler(); + + error.setInputStream(proc.getErrorStream()); + + output.setInputStream(proc.getInputStream()); + + String out = ""; + + long startTime = new Date().getTime(); + while (true) { + out = error.getOutput(); + try { + if (!out.equals("") && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.getOutput(); + if (out != "" && out.indexOf(expect) != -1) { + nr = getStringOccurences(out, expect); + if (nr >= howManyTimes) { + isError = false; + break; + } + } + } + long endTime = new Date().getTime(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.equals("")) + out = output.getOutput(); + + error.stopIt(); + output.stopIt(); + + addStreamGobbler(error); + addStreamGobbler(output); + + error = null; + output = null; + + return out; + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamGobbler(output); + output.stopIt(); + output = null; + } + isError = true; + return ""; + } + } + + protected int getStringOccurences(String text, String token) { + + if (text.indexOf(token) < 0) return 0; + int nr = 0; + String str = text; + while (str.indexOf(token) >= 0) { + str = str.substring(str.indexOf(token) + token.length()); + nr++; + } + return nr; + } + + // cipsm -> new execute command - it shows the output exactly as it is, by lines + public String executeCommandReality(String command, String expect) { + + StreamRealGobbler error = null; + StreamRealGobbler output = null; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + isError = true; + return null; + } + + error = getStreamRealGobbler(); + output = getStreamRealGobbler(); + + // any error message? + error.setInputStream(proc.getErrorStream()); + + // any output? + output.setInputStream(proc.getInputStream()); + + String out = ""; + + // any error??? + long startTime = new Date().getTime(); + while (true) { + out = error.forceAllOutput(); + try { + if (!out.equals("") && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.forceAllOutput(); + if (out != "" && out.indexOf(expect) != -1) { + isError = false; + break; + } + } + long endTime = new Date().getTime(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.equals("")) + out = output.forceAllOutput(); + // String ret = ""; // // if (!error.getOutput().equals("")) // ret = error.forceAllOutput(); // // ret = output.forceAllOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamRealGobbler(error); - addStreamRealGobbler(output); - - error = null; - output = null; - - return out; - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamRealGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamRealGobbler(output); - output.stopIt(); - output = null; - } - isError = true; - - return ""; - } - } - - public String executeCommandReality(String command, String expect, int howManyTimes) { - - StreamRealGobbler error = null; - StreamRealGobbler output = null; - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - isError = true; - return null; - } - - error = getStreamRealGobbler(); - output = getStreamRealGobbler(); - - error.setInputStream(proc.getErrorStream()); - - output.setInputStream(proc.getInputStream()); - - String out = ""; - - long startTime = new Date().getTime(); - while (true) { - out = error.forceAllOutput(); - try { - if (!out.equals("") && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.forceAllOutput(); - if (out != "" && out.indexOf(expect) != -1) { - int nr = getStringOccurences(out, expect); - if (nr >= howManyTimes) { - isError = false; - break; - } - } - } - long endTime = new Date().getTime(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.equals("")) - out = output.forceAllOutput(); - + + error.stopIt(); + output.stopIt(); + + addStreamRealGobbler(error); + addStreamRealGobbler(output); + + error = null; + output = null; + + return out; + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamRealGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamRealGobbler(output); + output.stopIt(); + output = null; + } + isError = true; + + return ""; + } + } + + public String executeCommandReality(String command, String expect, int howManyTimes) { + + StreamRealGobbler error = null; + StreamRealGobbler output = null; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + isError = true; + return null; + } + + error = getStreamRealGobbler(); + output = getStreamRealGobbler(); + + error.setInputStream(proc.getErrorStream()); + + output.setInputStream(proc.getInputStream()); + + String out = ""; + + long startTime = new Date().getTime(); + while (true) { + out = error.forceAllOutput(); + try { + if (!out.equals("") && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.forceAllOutput(); + if (out != "" && out.indexOf(expect) != -1) { + int nr = getStringOccurences(out, expect); + if (nr >= howManyTimes) { + isError = false; + break; + } + } + } + long endTime = new Date().getTime(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.equals("")) + out = output.forceAllOutput(); + // String ret = ""; // // if (!error.getOutput().equals("")) // ret = error.forceAllOutput(); // // ret = output.forceAllOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamRealGobbler(error); - addStreamRealGobbler(output); - - error = null; - output = null; - - return out; - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamRealGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamRealGobbler(output); - output.stopIt(); - output = null; - } - isError = true; - - return ""; - } - } - - public StreamGobbler getStreamGobbler() { - - synchronized (streams) { - if (streams.size() == 0) { - StreamGobbler stream = new StreamGobbler(null); - stream.start(); - return stream; - } - return (StreamGobbler)streams.removeFirst(); - } - } - - public void addStreamGobbler(StreamGobbler stream) { - - synchronized (streams) { - if (!stopStreams) - streams.addLast(stream); - else - stream.stopItForever(); - } - } - - public StreamRealGobbler getStreamRealGobbler() { - synchronized (streamsReal) { - if (streamsReal.size() == 0) { - StreamRealGobbler stream = new StreamRealGobbler(null); - stream.start(); - return stream; - } - return (StreamRealGobbler)streamsReal.removeFirst(); - } - } - - public void addStreamRealGobbler(StreamRealGobbler stream) { - - synchronized (streamsReal) { - if (!stopStreamsReal) - streamsReal.addLast(stream); - else - stream.stopItForever(); - } - } - - public void stopIt() { - //System.out.println("### cmdExec stopIt streams.size = " + streams.size()); - synchronized(streams) { - stopStreams = true; - - while (streams.size() > 0) { - StreamGobbler sg = (StreamGobbler)(streams.removeFirst()); - sg.stopItForever(); - } - } - synchronized(streamsReal) { - stopStreamsReal = true; - - while (streamsReal.size() > 0) { - StreamRealGobbler sg = (StreamRealGobbler)(streamsReal.removeFirst()); - sg.stopItForever(); - } - } - } - - class StreamGobbler extends Thread { - - InputStream is; - String output = ""; - boolean stop = false; - boolean stopForever = false; - boolean doneReading = false; - - public StreamGobbler(InputStream is) { - - super("Stream Gobler"); - this.is = is; - this.setDaemon(true); - } - - public void setInputStream(InputStream is) { - - this.is = is; - output = ""; - stop = false; - synchronized (this) { - doneReading = false; - notify(); - } - } - - public String getOutput() { - - return output; - } - - public synchronized String forceAllOutput() { - - if (!doneReading) - return ""; - doneReading = false; - return output; - } - - public void stopIt() { - stop = true; - } - - public void stopItForever() { - synchronized(this) { - stopForever = true; - notify(); - } - } - - public void run() { - - while (true) { - - synchronized (this) { - while (is == null && !stopForever) { - try { - wait(); - } catch (Exception e) { } - } - } - - if (stopForever) { - break; - } - try { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - String line=null; - while (!stop && (line = br.readLine()) != null) { - output += line; - } - synchronized (this) { - doneReading = true; - } - is.close(); - } catch (Exception ioe) { - output = ""; - } - is = null; - } - } - } - - class StreamRealGobbler extends Thread { - - InputStream is; - String output = ""; - boolean stop = false; - boolean doneReading = false; - boolean stopForever = false; - - public StreamRealGobbler(InputStream is) { - - super("Stream Real Gobbler"); - this.is = is; - this.setDaemon(true); - } - - public void setInputStream(InputStream is) { - - this.is = is; - output = ""; - stop = false; - synchronized (this) { - doneReading = false; - notify(); - } - } - - public String getOutput() { - - return output; - } - - public synchronized String forceAllOutput() { - - if (!doneReading) - return ""; - return output; - } - - public void stopIt() { - stop = true; - } - - public void stopItForever() { - synchronized(this) { - stopForever = true; - notify(); - } - } - public void run() { - - while (true) { - - synchronized (this) { - while (is == null && !stopForever) { - try { - wait(); - } catch (Exception e) { } - } - } - - if (stopForever) { - break; - } - - try { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - String line=null; - while (!stop && (line = br.readLine()) != null) { - output += line+"\n"; - } - synchronized (this) { - doneReading = true; - } - } catch (Exception ioe) { - output = ""; - } - is = null; - } - } - } - + + error.stopIt(); + output.stopIt(); + + addStreamRealGobbler(error); + addStreamRealGobbler(output); + + error = null; + output = null; + + return out; + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamRealGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamRealGobbler(output); + output.stopIt(); + output = null; + } + isError = true; + + return ""; + } + } + + public StreamGobbler getStreamGobbler() { + + synchronized (streams) { + if (streams.size() == 0) { + StreamGobbler stream = new StreamGobbler(null); + stream.start(); + return stream; + } + return (StreamGobbler) streams.removeFirst(); + } + } + + public void addStreamGobbler(StreamGobbler stream) { + + synchronized (streams) { + if (!stopStreams) + streams.addLast(stream); + else + stream.stopItForever(); + } + } + + public StreamRealGobbler getStreamRealGobbler() { + synchronized (streamsReal) { + if (streamsReal.size() == 0) { + StreamRealGobbler stream = new StreamRealGobbler(null); + stream.start(); + return stream; + } + return (StreamRealGobbler) streamsReal.removeFirst(); + } + } + + public void addStreamRealGobbler(StreamRealGobbler stream) { + + synchronized (streamsReal) { + if (!stopStreamsReal) + streamsReal.addLast(stream); + else + stream.stopItForever(); + } + } + + public void stopIt() { + //System.out.println("### cmdExec stopIt streams.size = " + streams.size()); + synchronized (streams) { + stopStreams = true; + + while (streams.size() > 0) { + StreamGobbler sg = (StreamGobbler) (streams.removeFirst()); + sg.stopItForever(); + } + } + synchronized (streamsReal) { + stopStreamsReal = true; + + while (streamsReal.size() > 0) { + StreamRealGobbler sg = (StreamRealGobbler) (streamsReal.removeFirst()); + sg.stopItForever(); + } + } + } + + class StreamGobbler extends Thread { + + InputStream is; + String output = ""; + boolean stop = false; + boolean stopForever = false; + boolean doneReading = false; + + public StreamGobbler(InputStream is) { + + super("Stream Gobler"); + this.is = is; + this.setDaemon(true); + } + + public void setInputStream(InputStream is) { + + this.is = is; + output = ""; + stop = false; + synchronized (this) { + doneReading = false; + notify(); + } + } + + public String getOutput() { + + return output; + } + + public synchronized String forceAllOutput() { + + if (!doneReading) + return ""; + doneReading = false; + return output; + } + + public void stopIt() { + stop = true; + } + + public void stopItForever() { + synchronized (this) { + stopForever = true; + notify(); + } + } + + public void run() { + + while (true) { + + synchronized (this) { + while (is == null && !stopForever) { + try { + wait(); + } catch (Exception e) { + } + } + } + + if (stopForever) { + break; + } + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line = null; + while (!stop && (line = br.readLine()) != null) { + output += line; + } + synchronized (this) { + doneReading = true; + } + is.close(); + } catch (Exception ioe) { + output = ""; + } + is = null; + } + } + } + + class StreamRealGobbler extends Thread { + + InputStream is; + String output = ""; + boolean stop = false; + boolean doneReading = false; + boolean stopForever = false; + + public StreamRealGobbler(InputStream is) { + + super("Stream Real Gobbler"); + this.is = is; + this.setDaemon(true); + } + + public void setInputStream(InputStream is) { + + this.is = is; + output = ""; + stop = false; + synchronized (this) { + doneReading = false; + notify(); + } + } + + public String getOutput() { + + return output; + } + + public synchronized String forceAllOutput() { + + if (!doneReading) + return ""; + return output; + } + + public void stopIt() { + stop = true; + } + + public void stopItForever() { + synchronized (this) { + stopForever = true; + notify(); + } + } + + public void run() { + + while (true) { + + synchronized (this) { + while (is == null && !stopForever) { + try { + wait(); + } catch (Exception e) { + } + } + } + + if (stopForever) { + break; + } + + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line = null; + while (!stop && (line = br.readLine()) != null) { + output += line + "\n"; + } + synchronized (this) { + doneReading = true; + } + } catch (Exception ioe) { + output = ""; + } + is = null; + } + } + } + } diff --git a/src/ch/ethz/ssh2/ChannelCondition.java b/src/ch/ethz/ssh2/ChannelCondition.java index 44fafbb..be9ba8f 100644 --- a/src/ch/ethz/ssh2/ChannelCondition.java +++ b/src/ch/ethz/ssh2/ChannelCondition.java @@ -1,61 +1,58 @@ - package ch.ethz.ssh2; /** * Contains constants that can be used to specify what conditions to wait for on * a SSH-2 channel (e.g., represented by a {@link Session}). * - * @see Session#waitForCondition(int, long) - * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ChannelCondition.java,v 1.6 2006/08/11 12:24:00 cplattne Exp $ + * @see Session#waitForCondition(int, long) */ -public abstract interface ChannelCondition -{ - /** - * A timeout has occurred, none of your requested conditions is fulfilled. - * However, other conditions may be true - therefore, NEVER use the "==" - * operator to test for this (or any other) condition. Always use - * something like ((cond & ChannelCondition.CLOSED) != 0). - */ - public static final int TIMEOUT = 1; - - /** - * The underlying SSH-2 channel, however not necessarily the whole connection, - * has been closed. This implies EOF. Note that there may still - * be unread stdout or stderr data in the local window, i.e, STDOUT_DATA - * or/and STDERR_DATA may be set at the same time. - */ - public static final int CLOSED = 2; - - /** - * There is stdout data available that is ready to be consumed. - */ - public static final int STDOUT_DATA = 4; - - /** - * There is stderr data available that is ready to be consumed. - */ - public static final int STDERR_DATA = 8; - - /** - * EOF on has been reached, no more _new_ stdout or stderr data will arrive - * from the remote server. However, there may be unread stdout or stderr - * data, i.e, STDOUT_DATA or/and STDERR_DATA - * may be set at the same time. - */ - public static final int EOF = 16; - - /** - * The exit status of the remote process is available. - * Some servers never send the exist status, or occasionally "forget" to do so. - */ - public static final int EXIT_STATUS = 32; - - /** - * The exit signal of the remote process is available. - */ - public static final int EXIT_SIGNAL = 64; +public abstract interface ChannelCondition { + /** + * A timeout has occurred, none of your requested conditions is fulfilled. + * However, other conditions may be true - therefore, NEVER use the "==" + * operator to test for this (or any other) condition. Always use + * something like ((cond & ChannelCondition.CLOSED) != 0). + */ + public static final int TIMEOUT = 1; + + /** + * The underlying SSH-2 channel, however not necessarily the whole connection, + * has been closed. This implies EOF. Note that there may still + * be unread stdout or stderr data in the local window, i.e, STDOUT_DATA + * or/and STDERR_DATA may be set at the same time. + */ + public static final int CLOSED = 2; + + /** + * There is stdout data available that is ready to be consumed. + */ + public static final int STDOUT_DATA = 4; + + /** + * There is stderr data available that is ready to be consumed. + */ + public static final int STDERR_DATA = 8; + + /** + * EOF on has been reached, no more _new_ stdout or stderr data will arrive + * from the remote server. However, there may be unread stdout or stderr + * data, i.e, STDOUT_DATA or/and STDERR_DATA + * may be set at the same time. + */ + public static final int EOF = 16; + + /** + * The exit status of the remote process is available. + * Some servers never send the exist status, or occasionally "forget" to do so. + */ + public static final int EXIT_STATUS = 32; + + /** + * The exit signal of the remote process is available. + */ + public static final int EXIT_SIGNAL = 64; } diff --git a/src/ch/ethz/ssh2/Connection.java b/src/ch/ethz/ssh2/Connection.java index cc07790..dbee385 100644 --- a/src/ch/ethz/ssh2/Connection.java +++ b/src/ch/ethz/ssh2/Connection.java @@ -1,14 +1,5 @@ - package ch.ethz.ssh2; -import java.io.CharArrayWriter; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.SocketTimeoutException; -import java.security.SecureRandom; -import java.util.Vector; - import ch.ethz.ssh2.auth.AuthenticationManager; import ch.ethz.ssh2.channel.ChannelManager; import ch.ethz.ssh2.crypto.CryptoWishList; @@ -19,6 +10,14 @@ import ch.ethz.ssh2.util.TimeoutService; import ch.ethz.ssh2.util.TimeoutService.TimeoutToken; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.security.SecureRandom; +import java.util.Vector; + /** * A Connection is used to establish an encrypted TCP/IP * connection to a SSH-2 server. @@ -31,570 +30,507 @@ *

  • calls one or several times the {@link #openSession() openSession()} method.
  • *
  • finally, one must close the connection and release resources with the {@link #close() close()} method.
  • * - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Connection.java,v 1.28 2006/09/12 15:35:26 cplattne Exp $ */ -public class Connection -{ - /** - * The identifier presented to the SSH-2 server. - */ - public final static String identification = "Ganymed Build_210beta8"; +public class Connection { + /** + * The identifier presented to the SSH-2 server. + */ + public final static String identification = "Ganymed Build_210beta8"; /* Will be used to generate all random data needed for the current connection. - * Note: SecureRandom.nextBytes() is thread safe. - */ - - private SecureRandom generator; - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported cipher algorithms by this implementation. - */ - public static synchronized String[] getAvailableCiphers() - { - return BlockCipherFactory.getDefaultCipherList(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported MAC algorthims by this implementation. - */ - public static synchronized String[] getAvailableMACs() - { - return MAC.getMacList(); - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @return The list of supported server host key algorthims by this implementation. - */ - public static synchronized String[] getAvailableServerHostKeyAlgorithms() - { - return KexManager.getDefaultServerHostkeyAlgorithmList(); - } - - private AuthenticationManager am; - - private boolean authenticated = false; - private ChannelManager cm; - - private CryptoWishList cryptoWishList = new CryptoWishList(); - - private DHGexParameters dhgexpara = new DHGexParameters(); - - private final String hostname; - - private final int port; - - private TransportManager tm; - - private boolean tcpNoDelay = false; - - private ProxyData proxyData = null; - - private Vector connectionMonitors = new Vector(); - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - *

    - * Same as {@link #Connection(String, int) Connection(hostname, 22)}. - * - * @param hostname the hostname of the SSH-2 server. - */ - public Connection(String hostname) - { - this(hostname, 22); - } - - /** - * Prepares a fresh Connection object which can then be used - * to establish a connection to the specified SSH-2 server. - * - * @param hostname - * the host where we later want to connect to. - * @param port - * port on the server, normally 22. - */ - public Connection(String hostname, int port) - { - this.hostname = hostname; - this.port = port; - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * is based on DSA (it uses DSA to sign a challenge sent by the server). - *

    - * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - * - * @param user - * A String holding the username. - * @param pem - * A String containing the DSA private key of the - * user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain - * linefeeds. - * @param password - * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you - * must specify the password. Otherwise, this argument will be - * ignored and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - * - * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} - * methods, this method is just a wrapper for it and will - * disappear in future builds. - * - */ - public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pem == null) - throw new IllegalArgumentException("pem argument is null"); - - authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); - - return authenticated; - } - - /** - * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) - * authenticateWithKeyboardInteractivewith} a null submethod list. - * - * @param user - * A String holding the username. - * @param cb - * An InteractiveCallback which will be used to - * determine the responses to the questions asked by the server. - * @return whether the connection is now authenticated. - * @throws IOException + * Note: SecureRandom.nextBytes() is thread safe. */ - public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) - throws IOException - { - return authenticateWithKeyboardInteractive(user, null, cb); - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * is based on "keyboard-interactive", specified in - * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a - * callback object which will be feeded with challenges generated by the - * server. Answers are then sent back to the server. It is possible that the - * callback will be called several times during the invocation of this - * method (e.g., if the server replies to the callback's answer(s) with - * another challenge...) - *

    - * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

    - * Note: some SSH servers advertise "keyboard-interactive", however, any - * interactive request will be denied (without having sent any challenge to - * the client). - * - * @param user - * A String holding the username. - * @param submethods - * An array of submethod names, see - * draft-ietf-secsh-auth-kbdinteract-XX. May be null - * to indicate an empty list. - * @param cb - * An InteractiveCallback which will be used to - * determine the responses to the questions asked by the server. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, - InteractiveCallback cb) throws IOException - { - if (cb == null) - throw new IllegalArgumentException("Callback may not ne NULL!"); - - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - authenticated = am.authenticateInteractive(user, submethods, cb); - - return authenticated; - } - - /** - * After a successfull connect, one has to authenticate oneself. This method - * sends username and password to the server. - *

    - * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

    - * Note: if this method fails, then please double-check that it is actually - * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. - *

    - * Often, password authentication is disabled, but users are not aware of it. - * Many servers only offer "publickey" and "keyboard-interactive". However, - * even though "keyboard-interactive" *feels* like password authentication - * (e.g., when using the putty or openssh clients) it is *not* the same mechanism. - * - * @param user - * @param password - * @return if the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithPassword(String user, String password) throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (password == null) - throw new IllegalArgumentException("password argument is null"); - - authenticated = am.authenticatePassword(user, password); - - return authenticated; - } - - /** - * After a successful connect, one has to authenticate oneself. - * The authentication method "publickey" works by signing a challenge - * sent by the server. The signature is either DSA or RSA based - it - * just depends on the type of private key you specify, either a DSA - * or RSA private key in PEM format. And yes, this is may seem to be a - * little confusing, the method is called "publickey" in the SSH-2 protocol - * specification, however since we need to generate a signature, you - * actually have to supply a private key =). - *

    - * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED"). - * The library supports DES-CBC and DES-EDE3-CBC encryption, as well - * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC. - *

    - * If the authentication phase is complete, true will be - * returned. If the server does not accept the request (or if further - * authentication steps are needed), false is returned and - * one can retry either by using this or any other authentication method - * (use the getRemainingAuthMethods method to get a list of - * the remaining possible methods). - *

    - * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." - * it is not in the expected format. You have to convert it to the OpenSSH - * key format by using the "puttygen" tool (can be downloaded from the Putty - * website). Simply load your key and then use the "Conversions/Export OpenSSH key" - * functionality to get a proper PEM file. - * - * @param user - * A String holding the username. - * @param pemPrivateKey - * A char[] containing a DSA or RSA private key of the - * user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" - * tag). The char array may contain linebreaks/linefeeds. - * @param password - * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then - * you must specify a password. Otherwise, this argument will be ignored - * and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) - throws IOException - { - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - if (user == null) - throw new IllegalArgumentException("user argument is null"); - - if (pemPrivateKey == null) - throw new IllegalArgumentException("pemPrivateKey argument is null"); - - authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); - - return authenticated; - } - - /** - * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA) - * and then calls authenticateWithPublicKey(String, char[], String). - *

    - * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." - * it is not in the expected format. You have to convert it to the OpenSSH - * key format by using the "puttygen" tool (can be downloaded from the Putty - * website). Simply load your key and then use the "Conversions/Export OpenSSH key" - * functionality to get a proper PEM file. - * - * @param user - * A String holding the username. - * @param pemFile - * A File object pointing to a file containing a DSA or RSA - * private key of the user in OpenSSH key format (PEM, you can't miss the - * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" - * tag). - * @param password - * If the PEM file is encrypted then you must specify the password. - * Otherwise, this argument will be ignored and can be set to null. - * - * @return whether the connection is now authenticated. - * @throws IOException - */ - public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) - throws IOException - { - if (pemFile == null) - throw new IllegalArgumentException("pemFile argument is null"); - - char[] buff = new char[256]; - - CharArrayWriter cw = new CharArrayWriter(); - - FileReader fr = new FileReader(pemFile); - - while (true) - { - int len = fr.read(buff); - if (len < 0) - break; - cw.write(buff, 0, len); - } - - fr.close(); - - return authenticateWithPublicKey(user, cw.toCharArray(), password); - } - - /** - * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time, - * but it is best to add connection monitors before invoking - * connect() to avoid glitches (e.g., you add a connection monitor after - * a successful connect(), but the connection has died in the mean time. Then, - * your connection monitor won't be notified.) - *

    - * You can add as many monitors as you like. - * - * @see ConnectionMonitor - * - * @param cmon An object implementing the ConnectionMonitor interface. - */ - public synchronized void addConnectionMonitor(ConnectionMonitor cmon) - { - if (cmon == null) - throw new IllegalArgumentException("cmon argument is null"); - - connectionMonitors.addElement(cmon); - - if (tm != null) - tm.setConnectionMonitors(connectionMonitors); - } - - /** - * Close the connection to the SSH-2 server. All assigned sessions will be - * closed, too. Can be called at any time. Don't forget to call this once - * you don't need a connection anymore - otherwise the receiver thread may - * run forever. - */ - public synchronized void close() - { - Throwable t = new Throwable("Closed due to user request."); - close(t, false); - } - - private void close(Throwable t, boolean hard) - { - if (cm != null) - cm.closeAllChannels(); - - if (tm != null) - { - tm.close(t, hard == false); - tm = null; - } - am = null; - cm = null; - authenticated = false; - } - - /** - * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. - * - * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. - * @throws IOException - */ - public synchronized ConnectionInfo connect() throws IOException - { - return connect(null, 0, 0); - } - - /** - * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. - * - * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. - * @throws IOException - */ - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException - { - return connect(verifier, 0, 0); - } - - /** - * Connect to the SSH-2 server and, as soon as the server has presented its - * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String, - * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} - * method of the verifier to ask for permission to proceed. - * If verifier is null, then any host key will be - * accepted - this is NOT recommended, since it makes man-in-the-middle attackes - * VERY easy (somebody could put a proxy SSH server between you and the real server). - *

    - * Note: The verifier will be called before doing any crypto calculations - * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then - * no CPU cycles are wasted (and the evil server has less information about us). - *

    - * However, it is still possible that the server presented a fake host key: the server - * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate - * a signature that matches its host key. Don't worry, the library will detect such - * a scenario later when checking the signature (the signature cannot be checked before - * having completed the diffie-hellman exchange). - *

    - * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, - * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method - * will *NOT* be called from the current thread, the call is being made from a - * background thread (there is a background dispatcher thread for every - * established connection). - *

    - * Note 3: This method will block as long as the key exchange of the underlying connection - * has not been completed (and you have not specified any timeouts). - *

    - * Note 4: If you want to re-use a connection object that was successfully connected, - * then you must call the {@link #close()} method before invoking connect() again. - * - * @param verifier - * An object that implements the - * {@link ServerHostKeyVerifier} interface. Pass null - * to accept any server host key - NOT recommended. - * - * @param connectTimeout - * Connect the underlying TCP socket to the server with the given timeout - * value (non-negative, in milliseconds). Zero means no timeout. If a proxy is being - * used (see {@link #setProxyData(ProxyData)}), then this timeout is used for the - * connection establishment to the proxy. - * - * @param kexTimeout - * Timeout for complete connection establishment (non-negative, - * in milliseconds). Zero means no timeout. The timeout counts from the - * moment you invoke the connect() method and is cancelled as soon as the - * first key-exchange round has finished. It is possible that - * the timeout event will be fired during the invocation of the - * verifier callback, but it will only have an effect after - * the verifier returns. - * - * @return A {@link ConnectionInfo} object containing the details of - * the established connection. - * - * @throws IOException - * If any problem occurs, e.g., the server's host key is not - * accepted by the verifier or there is problem during - * the initial crypto setup (e.g., the signature sent by the server is wrong). - *

    - * In case of a timeout (either connectTimeout or kexTimeout) - * a SocketTimeoutException is thrown. - *

    - * An exception may also be thrown if the connection was already successfully - * connected (no matter if the connection broke in the mean time) and you invoke - * connect() again without having called {@link #close()} first. - *

    - * If a HTTP proxy is being used and the proxy refuses the connection, - * then a {@link HTTPProxyException} may be thrown, which - * contains the details returned by the proxy. If the proxy is buggy and does - * not return a proper HTTP response, then a normal IOException is thrown instead. - */ - public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) - throws IOException - { - final class TimeoutState - { - boolean isCancelled = false; - boolean timeoutSocketClosed = false; - } - - if (tm != null) - throw new IOException("Connection to " + hostname + " is already in connected state!"); - - if (connectTimeout < 0) - throw new IllegalArgumentException("connectTimeout must be non-negative!"); - - if (kexTimeout < 0) - throw new IllegalArgumentException("kexTimeout must be non-negative!"); - - final TimeoutState state = new TimeoutState(); - - tm = new TransportManager(hostname, port); - - tm.setConnectionMonitors(connectionMonitors); + private final String hostname; + private final int port; + private SecureRandom generator; + private AuthenticationManager am; + private boolean authenticated = false; + private ChannelManager cm; + private CryptoWishList cryptoWishList = new CryptoWishList(); + private DHGexParameters dhgexpara = new DHGexParameters(); + private TransportManager tm; + private boolean tcpNoDelay = false; + private ProxyData proxyData = null; + private Vector connectionMonitors = new Vector(); + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + *

    + * Same as {@link #Connection(String, int) Connection(hostname, 22)}. + * + * @param hostname the hostname of the SSH-2 server. + */ + public Connection(String hostname) { + this(hostname, 22); + } + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + * + * @param hostname the host where we later want to connect to. + * @param port port on the server, normally 22. + */ + public Connection(String hostname, int port) { + this.hostname = hostname; + this.port = port; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported cipher algorithms by this implementation. + */ + public static synchronized String[] getAvailableCiphers() { + return BlockCipherFactory.getDefaultCipherList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported MAC algorthims by this implementation. + */ + public static synchronized String[] getAvailableMACs() { + return MAC.getMacList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported server host key algorthims by this implementation. + */ + public static synchronized String[] getAvailableServerHostKeyAlgorithms() { + return KexManager.getDefaultServerHostkeyAlgorithmList(); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on DSA (it uses DSA to sign a challenge sent by the server). + *

    + * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user A String holding the username. + * @param pem A String containing the DSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain + * linefeeds. + * @param password If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you + * must specify the password. Otherwise, this argument will be + * ignored and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} + * methods, this method is just a wrapper for it and will + * disappear in future builds. + */ + public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pem == null) + throw new IllegalArgumentException("pem argument is null"); + + authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); + + return authenticated; + } + + /** + * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) + * authenticateWithKeyboardInteractivewith} a null submethod list. + * + * @param user A String holding the username. + * @param cb An InteractiveCallback which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) + throws IOException { + return authenticateWithKeyboardInteractive(user, null, cb); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on "keyboard-interactive", specified in + * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a + * callback object which will be feeded with challenges generated by the + * server. Answers are then sent back to the server. It is possible that the + * callback will be called several times during the invocation of this + * method (e.g., if the server replies to the callback's answer(s) with + * another challenge...) + *

    + * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

    + * Note: some SSH servers advertise "keyboard-interactive", however, any + * interactive request will be denied (without having sent any challenge to + * the client). + * + * @param user A String holding the username. + * @param submethods An array of submethod names, see + * draft-ietf-secsh-auth-kbdinteract-XX. May be null + * to indicate an empty list. + * @param cb An InteractiveCallback which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, + InteractiveCallback cb) throws IOException { + if (cb == null) + throw new IllegalArgumentException("Callback may not ne NULL!"); + + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + authenticated = am.authenticateInteractive(user, submethods, cb); + + return authenticated; + } + + /** + * After a successfull connect, one has to authenticate oneself. This method + * sends username and password to the server. + *

    + * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

    + * Note: if this method fails, then please double-check that it is actually + * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. + *

    + * Often, password authentication is disabled, but users are not aware of it. + * Many servers only offer "publickey" and "keyboard-interactive". However, + * even though "keyboard-interactive" *feels* like password authentication + * (e.g., when using the putty or openssh clients) it is *not* the same mechanism. + * + * @param user + * @param password + * @return if the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPassword(String user, String password) throws IOException { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (password == null) + throw new IllegalArgumentException("password argument is null"); + + authenticated = am.authenticatePassword(user, password); + + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. + * The authentication method "publickey" works by signing a challenge + * sent by the server. The signature is either DSA or RSA based - it + * just depends on the type of private key you specify, either a DSA + * or RSA private key in PEM format. And yes, this is may seem to be a + * little confusing, the method is called "publickey" in the SSH-2 protocol + * specification, however since we need to generate a signature, you + * actually have to supply a private key =). + *

    + * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED"). + * The library supports DES-CBC and DES-EDE3-CBC encryption, as well + * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC. + *

    + * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

    + * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." + * it is not in the expected format. You have to convert it to the OpenSSH + * key format by using the "puttygen" tool (can be downloaded from the Putty + * website). Simply load your key and then use the "Conversions/Export OpenSSH key" + * functionality to get a proper PEM file. + * + * @param user A String holding the username. + * @param pemPrivateKey A char[] containing a DSA or RSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" + * tag). The char array may contain linebreaks/linefeeds. + * @param password If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then + * you must specify a password. Otherwise, this argument will be ignored + * and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) + throws IOException { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pemPrivateKey == null) + throw new IllegalArgumentException("pemPrivateKey argument is null"); + + authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); + + return authenticated; + } + + /** + * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA) + * and then calls authenticateWithPublicKey(String, char[], String). + *

    + * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." + * it is not in the expected format. You have to convert it to the OpenSSH + * key format by using the "puttygen" tool (can be downloaded from the Putty + * website). Simply load your key and then use the "Conversions/Export OpenSSH key" + * functionality to get a proper PEM file. + * + * @param user A String holding the username. + * @param pemFile A File object pointing to a file containing a DSA or RSA + * private key of the user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" + * tag). + * @param password If the PEM file is encrypted then you must specify the password. + * Otherwise, this argument will be ignored and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + */ + public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) + throws IOException { + if (pemFile == null) + throw new IllegalArgumentException("pemFile argument is null"); + + char[] buff = new char[256]; + + CharArrayWriter cw = new CharArrayWriter(); + + FileReader fr = new FileReader(pemFile); + + while (true) { + int len = fr.read(buff); + if (len < 0) + break; + cw.write(buff, 0, len); + } + + fr.close(); + + return authenticateWithPublicKey(user, cw.toCharArray(), password); + } + + /** + * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time, + * but it is best to add connection monitors before invoking + * connect() to avoid glitches (e.g., you add a connection monitor after + * a successful connect(), but the connection has died in the mean time. Then, + * your connection monitor won't be notified.) + *

    + * You can add as many monitors as you like. + * + * @param cmon An object implementing the ConnectionMonitor interface. + * @see ConnectionMonitor + */ + public synchronized void addConnectionMonitor(ConnectionMonitor cmon) { + if (cmon == null) + throw new IllegalArgumentException("cmon argument is null"); + + connectionMonitors.addElement(cmon); + + if (tm != null) + tm.setConnectionMonitors(connectionMonitors); + } + + /** + * Close the connection to the SSH-2 server. All assigned sessions will be + * closed, too. Can be called at any time. Don't forget to call this once + * you don't need a connection anymore - otherwise the receiver thread may + * run forever. + */ + public synchronized void close() { + Throwable t = new Throwable("Closed due to user request."); + close(t, false); + } + + private void close(Throwable t, boolean hard) { + if (cm != null) + cm.closeAllChannels(); + + if (tm != null) { + tm.close(t, hard == false); + tm = null; + } + am = null; + cm = null; + authenticated = false; + } + + /** + * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. + * + * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. + * @throws IOException + */ + public synchronized ConnectionInfo connect() throws IOException { + return connect(null, 0, 0); + } + + /** + * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. + * + * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. + * @throws IOException + */ + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException { + return connect(verifier, 0, 0); + } + + /** + * Connect to the SSH-2 server and, as soon as the server has presented its + * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String, + * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} + * method of the verifier to ask for permission to proceed. + * If verifier is null, then any host key will be + * accepted - this is NOT recommended, since it makes man-in-the-middle attackes + * VERY easy (somebody could put a proxy SSH server between you and the real server). + *

    + * Note: The verifier will be called before doing any crypto calculations + * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then + * no CPU cycles are wasted (and the evil server has less information about us). + *

    + * However, it is still possible that the server presented a fake host key: the server + * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate + * a signature that matches its host key. Don't worry, the library will detect such + * a scenario later when checking the signature (the signature cannot be checked before + * having completed the diffie-hellman exchange). + *

    + * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, + * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method + * will *NOT* be called from the current thread, the call is being made from a + * background thread (there is a background dispatcher thread for every + * established connection). + *

    + * Note 3: This method will block as long as the key exchange of the underlying connection + * has not been completed (and you have not specified any timeouts). + *

    + * Note 4: If you want to re-use a connection object that was successfully connected, + * then you must call the {@link #close()} method before invoking connect() again. + * + * @param verifier An object that implements the + * {@link ServerHostKeyVerifier} interface. Pass null + * to accept any server host key - NOT recommended. + * @param connectTimeout Connect the underlying TCP socket to the server with the given timeout + * value (non-negative, in milliseconds). Zero means no timeout. If a proxy is being + * used (see {@link #setProxyData(ProxyData)}), then this timeout is used for the + * connection establishment to the proxy. + * @param kexTimeout Timeout for complete connection establishment (non-negative, + * in milliseconds). Zero means no timeout. The timeout counts from the + * moment you invoke the connect() method and is cancelled as soon as the + * first key-exchange round has finished. It is possible that + * the timeout event will be fired during the invocation of the + * verifier callback, but it will only have an effect after + * the verifier returns. + * @return A {@link ConnectionInfo} object containing the details of + * the established connection. + * @throws IOException If any problem occurs, e.g., the server's host key is not + * accepted by the verifier or there is problem during + * the initial crypto setup (e.g., the signature sent by the server is wrong). + *

    + * In case of a timeout (either connectTimeout or kexTimeout) + * a SocketTimeoutException is thrown. + *

    + * An exception may also be thrown if the connection was already successfully + * connected (no matter if the connection broke in the mean time) and you invoke + * connect() again without having called {@link #close()} first. + *

    + * If a HTTP proxy is being used and the proxy refuses the connection, + * then a {@link HTTPProxyException} may be thrown, which + * contains the details returned by the proxy. If the proxy is buggy and does + * not return a proper HTTP response, then a normal IOException is thrown instead. + */ + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) + throws IOException { + final class TimeoutState { + boolean isCancelled = false; + boolean timeoutSocketClosed = false; + } + + if (tm != null) + throw new IOException("Connection to " + hostname + " is already in connected state!"); + + if (connectTimeout < 0) + throw new IllegalArgumentException("connectTimeout must be non-negative!"); + + if (kexTimeout < 0) + throw new IllegalArgumentException("kexTimeout must be non-negative!"); + + final TimeoutState state = new TimeoutState(); + + tm = new TransportManager(hostname, port); + + tm.setConnectionMonitors(connectionMonitors); /* Make sure that the runnable below will observe the new value of "tm" * and "state" (the runnable will be executed in a different thread, which @@ -608,630 +544,577 @@ final class TimeoutState * (there is a synchronized block in TimeoutService.addTimeoutHandler). */ - synchronized (tm) - { + synchronized (tm) { /* We could actually synchronize on anything. */ - } - - try - { - TimeoutToken token = null; - - if (kexTimeout > 0) - { - final Runnable timeoutHandler = new Runnable() - { - public void run() - { - synchronized (state) - { - if (state.isCancelled) - return; - state.timeoutSocketClosed = true; - tm.close(new SocketTimeoutException("The connect timeout expired"), false); - } - } - }; - - long timeoutHorizont = System.currentTimeMillis() + kexTimeout; - - token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); - } - - try - { - tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData); - } - catch (SocketTimeoutException se) - { - throw (SocketTimeoutException) new SocketTimeoutException( - "The connect() operation on the socket timed out.").initCause(se); - } - - tm.setTcpNoDelay(tcpNoDelay); + } + + try { + TimeoutToken token = null; + + if (kexTimeout > 0) { + final Runnable timeoutHandler = new Runnable() { + public void run() { + synchronized (state) { + if (state.isCancelled) + return; + state.timeoutSocketClosed = true; + tm.close(new SocketTimeoutException("The connect timeout expired"), false); + } + } + }; + + long timeoutHorizont = System.currentTimeMillis() + kexTimeout; + + token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); + } + + try { + tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData); + } catch (SocketTimeoutException se) { + throw (SocketTimeoutException) new SocketTimeoutException( + "The connect() operation on the socket timed out.").initCause(se); + } + + tm.setTcpNoDelay(tcpNoDelay); /* Wait until first KEX has finished */ - ConnectionInfo ci = tm.getConnectionInfo(1); + ConnectionInfo ci = tm.getConnectionInfo(1); /* Now try to cancel the timeout, if needed */ - if (token != null) - { - TimeoutService.cancelTimeoutHandler(token); + if (token != null) { + TimeoutService.cancelTimeoutHandler(token); /* Were we too late? */ - synchronized (state) - { - if (state.timeoutSocketClosed) - throw new IOException("This exception will be replaced by the one below =)"); + synchronized (state) { + if (state.timeoutSocketClosed) + throw new IOException("This exception will be replaced by the one below =)"); /* Just in case the "cancelTimeoutHandler" invocation came just a little bit * too late but the handler did not enter the semaphore yet - we can * still stop it. */ - state.isCancelled = true; - } - } - - return ci; - } - catch (SocketTimeoutException ste) - { - throw ste; - } - catch (IOException e1) - { + state.isCancelled = true; + } + } + + return ci; + } catch (SocketTimeoutException ste) { + throw ste; + } catch (IOException e1) { /* This will also invoke any registered connection monitors */ - close(new Throwable("There was a problem during connect."), false); + close(new Throwable("There was a problem during connect."), false); - synchronized (state) - { + synchronized (state) { /* Show a clean exception, not something like "the socket is closed!?!" */ - if (state.timeoutSocketClosed) - throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired."); - } + if (state.timeoutSocketClosed) + throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired."); + } /* Do not wrap a HTTPProxyException */ - if (e1 instanceof HTTPProxyException) - throw e1; - - throw (IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port) - .initCause(e1); - } - } - - /** - * Creates a new {@link LocalPortForwarder}. - * A LocalPortForwarder forwards TCP/IP connections that arrive at a local - * port via the secure tunnel to another host (which may or may not be - * identical to the remote SSH-2 server). - *

    - * This method must only be called after one has passed successfully the authentication step. - * There is no limit on the number of concurrent forwardings. - * - * @param local_port the local port the LocalPortForwarder shall bind to. - * @param host_to_connect target address (IP or hostname) - * @param port_to_connect target port - * @return A {@link LocalPortForwarder} object. - * @throws IOException - */ - public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, - int port_to_connect) throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); - - return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); - } - - /** - * Creates a new {@link LocalStreamForwarder}. - * A LocalStreamForwarder manages an Input/Outputstream pair - * that is being forwarded via the secure tunnel into a TCP/IP connection to another host - * (which may or may not be identical to the remote SSH-2 server). - * - * @param host_to_connect - * @param port_to_connect - * @return A {@link LocalStreamForwarder} object. - * @throws IOException - */ - public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) - throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot forward, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot forward, connection is not authenticated."); - - return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); - } - - /** - * Create a very basic {@link SCPClient} that can be used to copy - * files from/to the SSH-2 server. - *

    - * Works only after one has passed successfully the authentication step. - * There is no limit on the number of concurrent SCP clients. - *

    - * Note: This factory method will probably disappear in the future. - * - * @return A {@link SCPClient} object. - * @throws IOException - */ - public synchronized SCPClient createSCPClient() throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot create SCP client, connection is not authenticated."); - - return new SCPClient(this); - } - - /** - * Force an asynchronous key re-exchange (the call does not block). The - * latest values set for MAC, Cipher and DH group exchange parameters will - * be used. If a key exchange is currently in progress, then this method has - * the only effect that the so far specified parameters will be used for the - * next (server driven) key exchange. - *

    - * Note: This implementation will never start a key exchange (other than the initial one) - * unless you or the SSH-2 server ask for it. - * - * @throws IOException - * In case of any failure behind the scenes. - */ - public synchronized void forceKeyExchange() throws IOException - { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - tm.forceKeyExchange(cryptoWishList, dhgexpara); - } - - /** - * Returns the hostname that was passed to the constructor. - * - * @return the hostname - */ - public synchronized String getHostname() - { - return hostname; - } - - /** - * Returns the port that was passed to the constructor. - * - * @return the TCP port - */ - public synchronized int getPort() - { - return port; - } - - /** - * Returns a {@link ConnectionInfo} object containing the details of - * the connection. Can be called as soon as the connection has been - * established (successfully connected). - * - * @return A {@link ConnectionInfo} object. - * @throws IOException - * In case of any failure behind the scenes. - */ - public synchronized ConnectionInfo getConnectionInfo() throws IOException - { - if (tm == null) - throw new IllegalStateException( - "Cannot get details of connection, you need to establish a connection first."); - return tm.getConnectionInfo(1); - } - - /** - * After a successful connect, one has to authenticate oneself. This method - * can be used to tell which authentication methods are supported by the - * server at a certain stage of the authentication process (for the given - * username). - *

    - * Note 1: the username will only be used if no authentication step was done - * so far (it will be used to ask the server for a list of possible - * authentication methods). Otherwise, this method ignores the user name and - * returns a cached method list (which is based on the information contained - * in the last negative server response). - *

    - * Note 2: the server may return method names that are not supported by this - * implementation. - *

    - * After a successful authentication, this method must not be called - * anymore. - * - * @param user - * A String holding the username. - * - * @return a (possibly emtpy) array holding authentication method names. - * @throws IOException - */ - public synchronized String[] getRemainingAuthMethods(String user) throws IOException - { - if (user == null) - throw new IllegalArgumentException("user argument may not be NULL!"); - - if (tm == null) - throw new IllegalStateException("Connection is not established!"); - - if (authenticated) - throw new IllegalStateException("Connection is already authenticated!"); - - if (am == null) - am = new AuthenticationManager(tm); - - if (cm == null) - cm = new ChannelManager(tm); - - return am.getRemainingMethods(user); - } - - /** - * Determines if the authentication phase is complete. Can be called at any - * time. - * - * @return true if no further authentication steps are - * needed. - */ - public synchronized boolean isAuthenticationComplete() - { - return authenticated; - } - - /** - * Returns true if there was at least one failed authentication request and - * the last failed authentication request was marked with "partial success" - * by the server. This is only needed in the rare case of SSH-2 server setups - * that cannot be satisfied with a single successful authentication request - * (i.e., multiple authentication steps are needed.) - *

    - * If you are interested in the details, then have a look at - * draft-ietf-secsh-userauth-XX.txt. - * - * @return if the there was a failed authentication step and the last one - * was marked as a "partial success". - */ - public synchronized boolean isAuthenticationPartialSuccess() - { - if (am == null) - return false; - - return am.getPartialSuccess(); - } - - /** - * Checks if a specified authentication method is available. This method is - * actually just a wrapper for {@link #getRemainingAuthMethods(String) - * getRemainingAuthMethods()}. - * - * @param user - * A String holding the username. - * @param method - * An authentication method name (e.g., "publickey", "password", - * "keyboard-interactive") as specified by the SSH-2 standard. - * @return if the specified authentication method is currently available. - * @throws IOException - */ - public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException - { - if (method == null) - throw new IllegalArgumentException("method argument may not be NULL!"); - - String methods[] = getRemainingAuthMethods(user); - - for (int i = 0; i < methods.length; i++) - { - if (methods[i].compareTo(method) == 0) - return true; - } - - return false; - } - - private final SecureRandom getOrCreateSecureRND() - { - if (generator == null) - generator = new SecureRandom(); - - return generator; - } - - /** - * Open a new {@link Session} on this connection. Works only after one has passed - * successfully the authentication step. There is no limit on the number of - * concurrent sessions. - * - * @return A {@link Session} object. - * @throws IOException - */ - public synchronized Session openSession() throws IOException - { - if (tm == null) - throw new IllegalStateException("Cannot open session, you need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("Cannot open session, connection is not authenticated."); - - return new Session(cm, getOrCreateSecureRND()); - } - - /** - * Removes duplicates from a String array, keeps only first occurence - * of each element. Does not destroy order of elements; can handle nulls. - * Uses a very efficient O(N^2) algorithm =) - * - * @param list a String array. - * @return a cleaned String array. - */ - private String[] removeDuplicates(String[] list) - { - if ((list == null) || (list.length < 2)) - return list; - - String[] list2 = new String[list.length]; - - int count = 0; - - for (int i = 0; i < list.length; i++) - { - boolean duplicate = false; - - String element = list[i]; - - for (int j = 0; j < count; j++) - { - if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j])))) - { - duplicate = true; - break; - } - } - - if (duplicate) - continue; - - list2[count++] = list[i]; - } - - if (count == list2.length) - return list2; - - String[] tmp = new String[count]; - System.arraycopy(list2, 0, tmp, 0, count); - - return tmp; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param ciphers - */ - public synchronized void setClient2ServerCiphers(String[] ciphers) - { - if ((ciphers == null) || (ciphers.length == 0)) - throw new IllegalArgumentException(); - ciphers = removeDuplicates(ciphers); - BlockCipherFactory.checkCipherList(ciphers); - cryptoWishList.c2s_enc_algos = ciphers; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param macs - */ - public synchronized void setClient2ServerMACs(String[] macs) - { - if ((macs == null) || (macs.length == 0)) - throw new IllegalArgumentException(); - macs = removeDuplicates(macs); - MAC.checkMacList(macs); - cryptoWishList.c2s_mac_algos = macs; - } - - /** - * Sets the parameters for the diffie-hellman group exchange. Unless you - * know what you are doing, you will never need this. Default values are - * defined in the {@link DHGexParameters} class. - * - * @param dgp {@link DHGexParameters}, non null. - * - */ - public synchronized void setDHGexParameters(DHGexParameters dgp) - { - if (dgp == null) - throw new IllegalArgumentException(); - - dhgexpara = dgp; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param ciphers - */ - public synchronized void setServer2ClientCiphers(String[] ciphers) - { - if ((ciphers == null) || (ciphers.length == 0)) - throw new IllegalArgumentException(); - ciphers = removeDuplicates(ciphers); - BlockCipherFactory.checkCipherList(ciphers); - cryptoWishList.s2c_enc_algos = ciphers; - } - - /** - * Unless you know what you are doing, you will never need this. - * - * @param macs - */ - public synchronized void setServer2ClientMACs(String[] macs) - { - if ((macs == null) || (macs.length == 0)) - throw new IllegalArgumentException(); - - macs = removeDuplicates(macs); - MAC.checkMacList(macs); - cryptoWishList.s2c_mac_algos = macs; - } - - /** - * Define the set of allowed server host key algorithms to be used for - * the following key exchange operations. - *

    - * Unless you know what you are doing, you will never need this. - * - * @param algos An array of allowed server host key algorithms. - * SSH-2 defines ssh-dss and ssh-rsa. - * The entries of the array must be ordered after preference, i.e., - * the entry at index 0 is the most preferred one. You must specify - * at least one entry. - */ - public synchronized void setServerHostKeyAlgorithms(String[] algos) - { - if ((algos == null) || (algos.length == 0)) - throw new IllegalArgumentException(); - - algos = removeDuplicates(algos); - KexManager.checkServerHostkeyAlgorithmsList(algos); - cryptoWishList.serverHostKeyAlgorithms = algos; - } - - /** - * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket. - *

    - * Can be called at any time. If the connection has not yet been established - * then the passed value will be stored and set after the socket has been set up. - * The default value that will be used is false. - * - * @param enable the argument passed to the Socket.setTCPNoDelay() method. - * @throws IOException - */ - public synchronized void setTCPNoDelay(boolean enable) throws IOException - { - tcpNoDelay = enable; - - if (tm != null) - tm.setTcpNoDelay(enable); - } - - /** - * Used to tell the library that the connection shall be established through a proxy server. - * It only makes sense to call this method before calling the {@link #connect() connect()} - * method. - *

    - * At the moment, only HTTP proxies are supported. - *

    - * Note: This method can be called any number of times. The {@link #connect() connect()} - * method will use the value set in the last preceding invocation of this method. - * - * @see HTTPProxyData - * - * @param proxyData Connection information about the proxy. If null, then - * no proxy will be used (non surprisingly, this is also the default). - */ - public synchronized void setProxyData(ProxyData proxyData) - { - this.proxyData = proxyData; - } - - /** - * Request a remote port forwarding. - * If successful, then forwarded connections will be redirected to the given target address. - * You can cancle a requested remote port forwarding by calling - * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. - *

    - * A call of this method will block until the peer either agreed or disagreed to your request- - *

    - * Note 1: this method typically fails if you - *

      - *
    • pass a port number for which the used remote user has not enough permissions (i.e., port - * < 1024)
    • - *
    • or pass a port number that is already in use on the remote server
    • - *
    • or if remote port forwarding is disabled on the server.
    • - *
    - *

    - * Note 2: (from the openssh man page): By default, the listening socket on the server will be - * bound to the loopback interface only. This may be overriden by specifying a bind address. - * Specifying a remote bind address will only succeed if the server's GatewayPorts option - * is enabled (see sshd_config(5)). - * - * @param bindAddress address to bind to on the server: - *

      - *
    • "" means that connections are to be accepted on all protocol families - * supported by the SSH implementation
    • - *
    • "0.0.0.0" means to listen on all IPv4 addresses
    • - *
    • "::" means to listen on all IPv6 addresses
    • - *
    • "localhost" means to listen on all protocol families supported by the SSH - * implementation on loopback addresses only, [RFC3330] and RFC3513]
    • - *
    • "127.0.0.1" and "::1" indicate listening on the loopback interfaces for - * IPv4 and IPv6 respectively
    • - *
    - * @param bindPort port number to bind on the server (must be > 0) - * @param targetAddress the target address (IP or hostname) - * @param targetPort the target port - * @throws IOException - */ - public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, - int targetPort) throws IOException - { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) - throw new IllegalArgumentException(); - - cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); - } - - /** - * Cancel an earlier requested remote port forwarding. - * Currently active forwardings will not be affected (e.g., disrupted). - * Note that further connection forwarding requests may be received until - * this method has returned. - * - * @param bindPort the allocated port number on the server - * @throws IOException if the remote side refuses the cancel request or another low - * level error occurs (e.g., the underlying connection is closed) - */ - public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException - { - if (tm == null) - throw new IllegalStateException("You need to establish a connection first."); - - if (!authenticated) - throw new IllegalStateException("The connection is not authenticated."); - - cm.requestCancelGlobalForward(bindPort); - } - - /** - * Provide your own instance of SecureRandom. Can be used, e.g., if you - * want to seed the used SecureRandom generator manually. - *

    - * The SecureRandom instance is used during key exchanges, public key authentication, - * x11 cookie generation and the like. - * - * @param rnd a SecureRandom instance - */ - public synchronized void setSecureRandom(SecureRandom rnd) - { - if (rnd == null) - throw new IllegalArgumentException(); - - this.generator = rnd; - } + if (e1 instanceof HTTPProxyException) + throw e1; + + throw (IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port) + .initCause(e1); + } + } + + /** + * Creates a new {@link LocalPortForwarder}. + * A LocalPortForwarder forwards TCP/IP connections that arrive at a local + * port via the secure tunnel to another host (which may or may not be + * identical to the remote SSH-2 server). + *

    + * This method must only be called after one has passed successfully the authentication step. + * There is no limit on the number of concurrent forwardings. + * + * @param local_port the local port the LocalPortForwarder shall bind to. + * @param host_to_connect target address (IP or hostname) + * @param port_to_connect target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, + int port_to_connect) throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalStreamForwarder}. + * A LocalStreamForwarder manages an Input/Outputstream pair + * that is being forwarded via the secure tunnel into a TCP/IP connection to another host + * (which may or may not be identical to the remote SSH-2 server). + * + * @param host_to_connect + * @param port_to_connect + * @return A {@link LocalStreamForwarder} object. + * @throws IOException + */ + public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) + throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot forward, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward, connection is not authenticated."); + + return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); + } + + /** + * Create a very basic {@link SCPClient} that can be used to copy + * files from/to the SSH-2 server. + *

    + * Works only after one has passed successfully the authentication step. + * There is no limit on the number of concurrent SCP clients. + *

    + * Note: This factory method will probably disappear in the future. + * + * @return A {@link SCPClient} object. + * @throws IOException + */ + public synchronized SCPClient createSCPClient() throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot create SCP client, connection is not authenticated."); + + return new SCPClient(this); + } + + /** + * Force an asynchronous key re-exchange (the call does not block). The + * latest values set for MAC, Cipher and DH group exchange parameters will + * be used. If a key exchange is currently in progress, then this method has + * the only effect that the so far specified parameters will be used for the + * next (server driven) key exchange. + *

    + * Note: This implementation will never start a key exchange (other than the initial one) + * unless you or the SSH-2 server ask for it. + * + * @throws IOException In case of any failure behind the scenes. + */ + public synchronized void forceKeyExchange() throws IOException { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + tm.forceKeyExchange(cryptoWishList, dhgexpara); + } + + /** + * Returns the hostname that was passed to the constructor. + * + * @return the hostname + */ + public synchronized String getHostname() { + return hostname; + } + + /** + * Returns the port that was passed to the constructor. + * + * @return the TCP port + */ + public synchronized int getPort() { + return port; + } + + /** + * Returns a {@link ConnectionInfo} object containing the details of + * the connection. Can be called as soon as the connection has been + * established (successfully connected). + * + * @return A {@link ConnectionInfo} object. + * @throws IOException In case of any failure behind the scenes. + */ + public synchronized ConnectionInfo getConnectionInfo() throws IOException { + if (tm == null) + throw new IllegalStateException( + "Cannot get details of connection, you need to establish a connection first."); + return tm.getConnectionInfo(1); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * can be used to tell which authentication methods are supported by the + * server at a certain stage of the authentication process (for the given + * username). + *

    + * Note 1: the username will only be used if no authentication step was done + * so far (it will be used to ask the server for a list of possible + * authentication methods). Otherwise, this method ignores the user name and + * returns a cached method list (which is based on the information contained + * in the last negative server response). + *

    + * Note 2: the server may return method names that are not supported by this + * implementation. + *

    + * After a successful authentication, this method must not be called + * anymore. + * + * @param user A String holding the username. + * @return a (possibly emtpy) array holding authentication method names. + * @throws IOException + */ + public synchronized String[] getRemainingAuthMethods(String user) throws IOException { + if (user == null) + throw new IllegalArgumentException("user argument may not be NULL!"); + + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + return am.getRemainingMethods(user); + } + + /** + * Determines if the authentication phase is complete. Can be called at any + * time. + * + * @return true if no further authentication steps are + * needed. + */ + public synchronized boolean isAuthenticationComplete() { + return authenticated; + } + + /** + * Returns true if there was at least one failed authentication request and + * the last failed authentication request was marked with "partial success" + * by the server. This is only needed in the rare case of SSH-2 server setups + * that cannot be satisfied with a single successful authentication request + * (i.e., multiple authentication steps are needed.) + *

    + * If you are interested in the details, then have a look at + * draft-ietf-secsh-userauth-XX.txt. + * + * @return if the there was a failed authentication step and the last one + * was marked as a "partial success". + */ + public synchronized boolean isAuthenticationPartialSuccess() { + if (am == null) + return false; + + return am.getPartialSuccess(); + } + + /** + * Checks if a specified authentication method is available. This method is + * actually just a wrapper for {@link #getRemainingAuthMethods(String) + * getRemainingAuthMethods()}. + * + * @param user A String holding the username. + * @param method An authentication method name (e.g., "publickey", "password", + * "keyboard-interactive") as specified by the SSH-2 standard. + * @return if the specified authentication method is currently available. + * @throws IOException + */ + public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException { + if (method == null) + throw new IllegalArgumentException("method argument may not be NULL!"); + + String methods[] = getRemainingAuthMethods(user); + + for (int i = 0; i < methods.length; i++) { + if (methods[i].compareTo(method) == 0) + return true; + } + + return false; + } + + private final SecureRandom getOrCreateSecureRND() { + if (generator == null) + generator = new SecureRandom(); + + return generator; + } + + /** + * Open a new {@link Session} on this connection. Works only after one has passed + * successfully the authentication step. There is no limit on the number of + * concurrent sessions. + * + * @return A {@link Session} object. + * @throws IOException + */ + public synchronized Session openSession() throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot open session, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot open session, connection is not authenticated."); + + return new Session(cm, getOrCreateSecureRND()); + } + + /** + * Removes duplicates from a String array, keeps only first occurence + * of each element. Does not destroy order of elements; can handle nulls. + * Uses a very efficient O(N^2) algorithm =) + * + * @param list a String array. + * @return a cleaned String array. + */ + private String[] removeDuplicates(String[] list) { + if ((list == null) || (list.length < 2)) + return list; + + String[] list2 = new String[list.length]; + + int count = 0; + + for (int i = 0; i < list.length; i++) { + boolean duplicate = false; + + String element = list[i]; + + for (int j = 0; j < count; j++) { + if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j])))) { + duplicate = true; + break; + } + } + + if (duplicate) + continue; + + list2[count++] = list[i]; + } + + if (count == list2.length) + return list2; + + String[] tmp = new String[count]; + System.arraycopy(list2, 0, tmp, 0, count); + + return tmp; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param ciphers + */ + public synchronized void setClient2ServerCiphers(String[] ciphers) { + if ((ciphers == null) || (ciphers.length == 0)) + throw new IllegalArgumentException(); + ciphers = removeDuplicates(ciphers); + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.c2s_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param macs + */ + public synchronized void setClient2ServerMACs(String[] macs) { + if ((macs == null) || (macs.length == 0)) + throw new IllegalArgumentException(); + macs = removeDuplicates(macs); + MAC.checkMacList(macs); + cryptoWishList.c2s_mac_algos = macs; + } + + /** + * Sets the parameters for the diffie-hellman group exchange. Unless you + * know what you are doing, you will never need this. Default values are + * defined in the {@link DHGexParameters} class. + * + * @param dgp {@link DHGexParameters}, non null. + */ + public synchronized void setDHGexParameters(DHGexParameters dgp) { + if (dgp == null) + throw new IllegalArgumentException(); + + dhgexpara = dgp; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param ciphers + */ + public synchronized void setServer2ClientCiphers(String[] ciphers) { + if ((ciphers == null) || (ciphers.length == 0)) + throw new IllegalArgumentException(); + ciphers = removeDuplicates(ciphers); + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.s2c_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @param macs + */ + public synchronized void setServer2ClientMACs(String[] macs) { + if ((macs == null) || (macs.length == 0)) + throw new IllegalArgumentException(); + + macs = removeDuplicates(macs); + MAC.checkMacList(macs); + cryptoWishList.s2c_mac_algos = macs; + } + + /** + * Define the set of allowed server host key algorithms to be used for + * the following key exchange operations. + *

    + * Unless you know what you are doing, you will never need this. + * + * @param algos An array of allowed server host key algorithms. + * SSH-2 defines ssh-dss and ssh-rsa. + * The entries of the array must be ordered after preference, i.e., + * the entry at index 0 is the most preferred one. You must specify + * at least one entry. + */ + public synchronized void setServerHostKeyAlgorithms(String[] algos) { + if ((algos == null) || (algos.length == 0)) + throw new IllegalArgumentException(); + + algos = removeDuplicates(algos); + KexManager.checkServerHostkeyAlgorithmsList(algos); + cryptoWishList.serverHostKeyAlgorithms = algos; + } + + /** + * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket. + *

    + * Can be called at any time. If the connection has not yet been established + * then the passed value will be stored and set after the socket has been set up. + * The default value that will be used is false. + * + * @param enable the argument passed to the Socket.setTCPNoDelay() method. + * @throws IOException + */ + public synchronized void setTCPNoDelay(boolean enable) throws IOException { + tcpNoDelay = enable; + + if (tm != null) + tm.setTcpNoDelay(enable); + } + + /** + * Used to tell the library that the connection shall be established through a proxy server. + * It only makes sense to call this method before calling the {@link #connect() connect()} + * method. + *

    + * At the moment, only HTTP proxies are supported. + *

    + * Note: This method can be called any number of times. The {@link #connect() connect()} + * method will use the value set in the last preceding invocation of this method. + * + * @param proxyData Connection information about the proxy. If null, then + * no proxy will be used (non surprisingly, this is also the default). + * @see HTTPProxyData + */ + public synchronized void setProxyData(ProxyData proxyData) { + this.proxyData = proxyData; + } + + /** + * Request a remote port forwarding. + * If successful, then forwarded connections will be redirected to the given target address. + * You can cancle a requested remote port forwarding by calling + * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. + *

    + * A call of this method will block until the peer either agreed or disagreed to your request- + *

    + * Note 1: this method typically fails if you + *

      + *
    • pass a port number for which the used remote user has not enough permissions (i.e., port + * < 1024)
    • + *
    • or pass a port number that is already in use on the remote server
    • + *
    • or if remote port forwarding is disabled on the server.
    • + *
    + *

    + * Note 2: (from the openssh man page): By default, the listening socket on the server will be + * bound to the loopback interface only. This may be overriden by specifying a bind address. + * Specifying a remote bind address will only succeed if the server's GatewayPorts option + * is enabled (see sshd_config(5)). + * + * @param bindAddress address to bind to on the server: + *

      + *
    • "" means that connections are to be accepted on all protocol families + * supported by the SSH implementation
    • + *
    • "0.0.0.0" means to listen on all IPv4 addresses
    • + *
    • "::" means to listen on all IPv6 addresses
    • + *
    • "localhost" means to listen on all protocol families supported by the SSH + * implementation on loopback addresses only, [RFC3330] and RFC3513]
    • + *
    • "127.0.0.1" and "::1" indicate listening on the loopback interfaces for + * IPv4 and IPv6 respectively
    • + *
    + * @param bindPort port number to bind on the server (must be > 0) + * @param targetAddress the target address (IP or hostname) + * @param targetPort the target port + * @throws IOException + */ + public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, + int targetPort) throws IOException { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("The connection is not authenticated."); + + if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) + throw new IllegalArgumentException(); + + cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); + } + + /** + * Cancel an earlier requested remote port forwarding. + * Currently active forwardings will not be affected (e.g., disrupted). + * Note that further connection forwarding requests may be received until + * this method has returned. + * + * @param bindPort the allocated port number on the server + * @throws IOException if the remote side refuses the cancel request or another low + * level error occurs (e.g., the underlying connection is closed) + */ + public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException { + if (tm == null) + throw new IllegalStateException("You need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("The connection is not authenticated."); + + cm.requestCancelGlobalForward(bindPort); + } + + /** + * Provide your own instance of SecureRandom. Can be used, e.g., if you + * want to seed the used SecureRandom generator manually. + *

    + * The SecureRandom instance is used during key exchanges, public key authentication, + * x11 cookie generation and the like. + * + * @param rnd a SecureRandom instance + */ + public synchronized void setSecureRandom(SecureRandom rnd) { + if (rnd == null) + throw new IllegalArgumentException(); + + this.generator = rnd; + } } diff --git a/src/ch/ethz/ssh2/ConnectionInfo.java b/src/ch/ethz/ssh2/ConnectionInfo.java index 63c594b..3a73ba5 100644 --- a/src/ch/ethz/ssh2/ConnectionInfo.java +++ b/src/ch/ethz/ssh2/ConnectionInfo.java @@ -1,53 +1,51 @@ - package ch.ethz.ssh2; /** * In most cases you probably do not need the information contained in here. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ConnectionInfo.java,v 1.3 2005/08/24 17:54:10 cplattne Exp $ */ -public class ConnectionInfo -{ - /** - * The used key exchange (KEX) algorithm in the latest key exchange. - */ - public String keyExchangeAlgorithm; +public class ConnectionInfo { + /** + * The used key exchange (KEX) algorithm in the latest key exchange. + */ + public String keyExchangeAlgorithm; - /** - * The currently used crypto algorithm for packets from to the client to the - * server. - */ - public String clientToServerCryptoAlgorithm; - /** - * The currently used crypto algorithm for packets from to the server to the - * client. - */ - public String serverToClientCryptoAlgorithm; + /** + * The currently used crypto algorithm for packets from to the client to the + * server. + */ + public String clientToServerCryptoAlgorithm; + /** + * The currently used crypto algorithm for packets from to the server to the + * client. + */ + public String serverToClientCryptoAlgorithm; - /** - * The currently used MAC algorithm for packets from to the client to the - * server. - */ - public String clientToServerMACAlgorithm; - /** - * The currently used MAC algorithm for packets from to the server to the - * client. - */ - public String serverToClientMACAlgorithm; + /** + * The currently used MAC algorithm for packets from to the client to the + * server. + */ + public String clientToServerMACAlgorithm; + /** + * The currently used MAC algorithm for packets from to the server to the + * client. + */ + public String serverToClientMACAlgorithm; - /** - * The type of the server host key (currently either "ssh-dss" or - * "ssh-rsa"). - */ - public String serverHostKeyAlgorithm; - /** - * The server host key that was sent during the latest key exchange. - */ - public byte[] serverHostKey; + /** + * The type of the server host key (currently either "ssh-dss" or + * "ssh-rsa"). + */ + public String serverHostKeyAlgorithm; + /** + * The server host key that was sent during the latest key exchange. + */ + public byte[] serverHostKey; - /** - * Number of kex exchanges performed on this connection so far. - */ - public int keyExchangeCounter = 0; + /** + * Number of kex exchanges performed on this connection so far. + */ + public int keyExchangeCounter = 0; } diff --git a/src/ch/ethz/ssh2/ConnectionMonitor.java b/src/ch/ethz/ssh2/ConnectionMonitor.java index 86d09b0..cd0aa07 100644 --- a/src/ch/ethz/ssh2/ConnectionMonitor.java +++ b/src/ch/ethz/ssh2/ConnectionMonitor.java @@ -1,34 +1,31 @@ - package ch.ethz.ssh2; /** * A ConnectionMonitor is used to get notified when the - * underlying socket of a connection is closed. - * + * underlying socket of a connection is closed. + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ConnectionMonitor.java,v 1.3 2006/08/11 17:44:37 cplattne Exp $ */ -public interface ConnectionMonitor -{ - /** - * This method is called after the connection's underlying - * socket has been closed. E.g., due to the {@link Connection#close()} request of the - * user, if the peer closed the connection, due to a fatal error during connect() - * (also if the socket cannot be established) or if a fatal error occured on - * an established connection. - *

    - * This is an experimental feature. - *

    - * You MUST NOT make any assumption about the thread that invokes this method. - *

    - * Please note: if the connection is not connected (e.g., there was no successful - * connect() call), then the invocation of {@link Connection#close()} will NOT trigger - * this method. - * - * @see Connection#addConnectionMonitor(ConnectionMonitor) - * - * @param reason Includes an indication why the socket was closed. - */ - public void connectionLost(Throwable reason); +public interface ConnectionMonitor { + /** + * This method is called after the connection's underlying + * socket has been closed. E.g., due to the {@link Connection#close()} request of the + * user, if the peer closed the connection, due to a fatal error during connect() + * (also if the socket cannot be established) or if a fatal error occured on + * an established connection. + *

    + * This is an experimental feature. + *

    + * You MUST NOT make any assumption about the thread that invokes this method. + *

    + * Please note: if the connection is not connected (e.g., there was no successful + * connect() call), then the invocation of {@link Connection#close()} will NOT trigger + * this method. + * + * @param reason Includes an indication why the socket was closed. + * @see Connection#addConnectionMonitor(ConnectionMonitor) + */ + public void connectionLost(Throwable reason); } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/DHGexParameters.java b/src/ch/ethz/ssh2/DHGexParameters.java index 8830459..0ded2a6 100644 --- a/src/ch/ethz/ssh2/DHGexParameters.java +++ b/src/ch/ethz/ssh2/DHGexParameters.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2; /** @@ -8,114 +7,105 @@ * Depending on which constructor is used, either the use of a * SSH_MSG_KEX_DH_GEX_REQUEST or SSH_MSG_KEX_DH_GEX_REQUEST_OLD * can be forced. - * - * @see Connection#setDHGexParameters(DHGexParameters) + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DHGexParameters.java,v 1.3 2006/09/20 12:51:37 cplattne Exp $ + * @see Connection#setDHGexParameters(DHGexParameters) */ -public class DHGexParameters -{ - private final int min_group_len; - private final int pref_group_len; - private final int max_group_len; - - private static final int MIN_ALLOWED = 1024; - private static final int MAX_ALLOWED = 8192; - - /** - * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}. - * This is also the default used by the Connection class. - * - */ - public DHGexParameters() - { - this(1024, 1024, 4096); - } - - /** - * This constructor can be used to force the sending of a - * SSH_MSG_KEX_DH_GEX_REQUEST_OLD request. - * Internally, the minimum and maximum group lengths will - * be set to zero. - * - * @param pref_group_len has to be >= 1024 and <= 8192 - */ - public DHGexParameters(int pref_group_len) - { - if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("pref_group_len out of range!"); - - this.pref_group_len = pref_group_len; - this.min_group_len = 0; - this.max_group_len = 0; - } - - /** - * This constructor can be used to force the sending of a - * SSH_MSG_KEX_DH_GEX_REQUEST request. - *

    - * Note: older OpenSSH servers don't understand this request, in which - * case you should use the {@link #DHGexParameters(int)} constructor. - *

    - * All values have to be >= 1024 and <= 8192. Furthermore, - * min_group_len <= pref_group_len <= max_group_len. - * - * @param min_group_len - * @param pref_group_len - * @param max_group_len - */ - public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len) - { - if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("min_group_len out of range!"); - - if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("pref_group_len out of range!"); - - if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED)) - throw new IllegalArgumentException("max_group_len out of range!"); - - if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len)) - throw new IllegalArgumentException("pref_group_len is incompatible with min and max!"); - - if (max_group_len < min_group_len) - throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!"); - - this.min_group_len = min_group_len; - this.pref_group_len = pref_group_len; - this.max_group_len = max_group_len; - } - - /** - * Get the maximum group length. - * - * @return the maximum group length, may be zero if - * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested - */ - public int getMax_group_len() - { - return max_group_len; - } - - /** - * Get the minimum group length. - * - * @return minimum group length, may be zero if - * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested - */ - public int getMin_group_len() - { - return min_group_len; - } - - /** - * Get the preferred group length. - * - * @return the preferred group length - */ - public int getPref_group_len() - { - return pref_group_len; - } +public class DHGexParameters { + private static final int MIN_ALLOWED = 1024; + private static final int MAX_ALLOWED = 8192; + private final int min_group_len; + private final int pref_group_len; + private final int max_group_len; + + /** + * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}. + * This is also the default used by the Connection class. + */ + public DHGexParameters() { + this(1024, 1024, 4096); + } + + /** + * This constructor can be used to force the sending of a + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD request. + * Internally, the minimum and maximum group lengths will + * be set to zero. + * + * @param pref_group_len has to be >= 1024 and <= 8192 + */ + public DHGexParameters(int pref_group_len) { + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + this.pref_group_len = pref_group_len; + this.min_group_len = 0; + this.max_group_len = 0; + } + + /** + * This constructor can be used to force the sending of a + * SSH_MSG_KEX_DH_GEX_REQUEST request. + *

    + * Note: older OpenSSH servers don't understand this request, in which + * case you should use the {@link #DHGexParameters(int)} constructor. + *

    + * All values have to be >= 1024 and <= 8192. Furthermore, + * min_group_len <= pref_group_len <= max_group_len. + * + * @param min_group_len + * @param pref_group_len + * @param max_group_len + */ + public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len) { + if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("min_group_len out of range!"); + + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("max_group_len out of range!"); + + if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len)) + throw new IllegalArgumentException("pref_group_len is incompatible with min and max!"); + + if (max_group_len < min_group_len) + throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!"); + + this.min_group_len = min_group_len; + this.pref_group_len = pref_group_len; + this.max_group_len = max_group_len; + } + + /** + * Get the maximum group length. + * + * @return the maximum group length, may be zero if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMax_group_len() { + return max_group_len; + } + + /** + * Get the minimum group length. + * + * @return minimum group length, may be zero if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMin_group_len() { + return min_group_len; + } + + /** + * Get the preferred group length. + * + * @return the preferred group length + */ + public int getPref_group_len() { + return pref_group_len; + } } diff --git a/src/ch/ethz/ssh2/HTTPProxyData.java b/src/ch/ethz/ssh2/HTTPProxyData.java index bce82bc..83c14cd 100644 --- a/src/ch/ethz/ssh2/HTTPProxyData.java +++ b/src/ch/ethz/ssh2/HTTPProxyData.java @@ -1,83 +1,77 @@ - package ch.ethz.ssh2; /** * A HTTPProxyData object is used to specify the needed connection data - * to connect through a HTTP proxy. - * - * @see Connection#setProxyData(ProxyData) - * + * to connect through a HTTP proxy. + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: HTTPProxyData.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ + * @see Connection#setProxyData(ProxyData) */ -public class HTTPProxyData implements ProxyData -{ - public final String proxyHost; - public final int proxyPort; - public final String proxyUser; - public final String proxyPass; - public final String[] requestHeaderLines; +public class HTTPProxyData implements ProxyData { + public final String proxyHost; + public final int proxyPort; + public final String proxyUser; + public final String proxyPass; + public final String[] requestHeaderLines; - /** - * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, null, null)} - * - * @param proxyHost Proxy hostname. - * @param proxyPort Proxy port. - */ - public HTTPProxyData(String proxyHost, int proxyPort) - { - this(proxyHost, proxyPort, null, null); - } + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, null, null)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + */ + public HTTPProxyData(String proxyHost, int proxyPort) { + this(proxyHost, proxyPort, null, null); + } - /** - * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, null, null, null)} - * - * @param proxyHost Proxy hostname. - * @param proxyPort Proxy port. - * @param proxyUser Username for basic authentication (null if no authentication is needed). - * @param proxyPass Password for basic authentication (null if no authentication is needed). - */ - public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass) - { - this(proxyHost, proxyPort, proxyUser, proxyPass, null); - } + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, null, null, null)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (null if no authentication is needed). + * @param proxyPass Password for basic authentication (null if no authentication is needed). + */ + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { + this(proxyHost, proxyPort, proxyUser, proxyPass, null); + } - /** - * Connection data for a HTTP proxy. It is possible to specify a username and password - * if the proxy requires basic authentication. Also, additional request header lines can - * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3"). - *

    - * Please note: if you want to use basic authentication, then both proxyUser - * and proxyPass must be non-null. - *

    - * Here is an example: - *

    - * - * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: GanymedBasedClient/1.0", "X-My-Proxy-Option: something"}); - * - * - * @param proxyHost Proxy hostname. - * @param proxyPort Proxy port. - * @param proxyUser Username for basic authentication (null if no authentication is needed). - * @param proxyPass Password for basic authentication (null if no authentication is needed). - * @param requestHeaderLines An array with additional request header lines (without end-of-line markers) - * that have to be sent to the server. May be null. - */ + /** + * Connection data for a HTTP proxy. It is possible to specify a username and password + * if the proxy requires basic authentication. Also, additional request header lines can + * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3"). + *

    + * Please note: if you want to use basic authentication, then both proxyUser + * and proxyPass must be non-null. + *

    + * Here is an example: + *

    + * + * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: GanymedBasedClient/1.0", "X-My-Proxy-Option: something"}); + * + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (null if no authentication is needed). + * @param proxyPass Password for basic authentication (null if no authentication is needed). + * @param requestHeaderLines An array with additional request header lines (without end-of-line markers) + * that have to be sent to the server. May be null. + */ - public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass, - String[] requestHeaderLines) - { - if (proxyHost == null) - throw new IllegalArgumentException("proxyHost must be non-null"); + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass, + String[] requestHeaderLines) { + if (proxyHost == null) + throw new IllegalArgumentException("proxyHost must be non-null"); - if (proxyPort < 0) - throw new IllegalArgumentException("proxyPort must be non-negative"); + if (proxyPort < 0) + throw new IllegalArgumentException("proxyPort must be non-negative"); - this.proxyHost = proxyHost; - this.proxyPort = proxyPort; - this.proxyUser = proxyUser; - this.proxyPass = proxyPass; - this.requestHeaderLines = requestHeaderLines; - } + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + this.requestHeaderLines = requestHeaderLines; + } } diff --git a/src/ch/ethz/ssh2/HTTPProxyException.java b/src/ch/ethz/ssh2/HTTPProxyException.java index b5021b4..f495834 100644 --- a/src/ch/ethz/ssh2/HTTPProxyException.java +++ b/src/ch/ethz/ssh2/HTTPProxyException.java @@ -1,29 +1,25 @@ - package ch.ethz.ssh2; import java.io.IOException; /** * May be thrown upon connect() if a HTTP proxy is being used. - * - * @see Connection#connect() - * @see Connection#setProxyData(ProxyData) - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: HTTPProxyException.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ + * @see Connection#connect() + * @see Connection#setProxyData(ProxyData) */ -public class HTTPProxyException extends IOException -{ - private static final long serialVersionUID = 2241537397104426186L; +public class HTTPProxyException extends IOException { + private static final long serialVersionUID = 2241537397104426186L; - public final String httpResponse; - public final int httpErrorCode; + public final String httpResponse; + public final int httpErrorCode; - public HTTPProxyException(String httpResponse, int httpErrorCode) - { - super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")"); - this.httpResponse = httpResponse; - this.httpErrorCode = httpErrorCode; - } + public HTTPProxyException(String httpResponse, int httpErrorCode) { + super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")"); + this.httpResponse = httpResponse; + this.httpErrorCode = httpErrorCode; + } } diff --git a/src/ch/ethz/ssh2/InteractiveCallback.java b/src/ch/ethz/ssh2/InteractiveCallback.java index 5fd321e..6d56a6a 100644 --- a/src/ch/ethz/ssh2/InteractiveCallback.java +++ b/src/ch/ethz/ssh2/InteractiveCallback.java @@ -1,54 +1,46 @@ - package ch.ethz.ssh2; /** * An InteractiveCallback is used to respond to challenges sent * by the server if authentication mode "keyboard-interactive" is selected. - * - * @see Connection#authenticateWithKeyboardInteractive(String, - * String[], InteractiveCallback) - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: InteractiveCallback.java,v 1.3 2006/02/13 21:19:25 cplattne Exp $ + * @see Connection#authenticateWithKeyboardInteractive(String, + * String[], InteractiveCallback) */ -public interface InteractiveCallback -{ - /** - * This callback interface is used during a "keyboard-interactive" - * authentication. Every time the server sends a set of challenges (however, - * most often just one challenge at a time), this callback function will be - * called to give your application a chance to talk to the user and to - * determine the response(s). - *

    - * Some copy-paste information from the standard: a command line interface - * (CLI) client SHOULD print the name and instruction (if non-empty), adding - * newlines. Then for each prompt in turn, the client SHOULD display the - * prompt and read the user input. The name and instruction fields MAY be - * empty strings, the client MUST be prepared to handle this correctly. The - * prompt field(s) MUST NOT be empty strings. - *

    - * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details. - *

    - * Note: clients SHOULD use control character filtering as discussed in - * [draft-ietf-secsh-architecture-XX.txt] to avoid attacks by including - * terminal control characters in the fields to be displayed. - * - * @param name - * the name String sent by the server. - * @param instruction - * the instruction String sent by the server. - * @param numPrompts - * number of prompts - may be zero (in this case, you should just - * return a String array of length zero). - * @param prompt - * an array (length numPrompts) of Strings - * @param echo - * an array (length numPrompts) of booleans. For - * each prompt, the corresponding echo field indicates whether or - * not the user input should be echoed as characters are typed. - * @return an array of reponses - the array size must match the parameter - * numPrompts. - */ - public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) throws Exception; +public interface InteractiveCallback { + /** + * This callback interface is used during a "keyboard-interactive" + * authentication. Every time the server sends a set of challenges (however, + * most often just one challenge at a time), this callback function will be + * called to give your application a chance to talk to the user and to + * determine the response(s). + *

    + * Some copy-paste information from the standard: a command line interface + * (CLI) client SHOULD print the name and instruction (if non-empty), adding + * newlines. Then for each prompt in turn, the client SHOULD display the + * prompt and read the user input. The name and instruction fields MAY be + * empty strings, the client MUST be prepared to handle this correctly. The + * prompt field(s) MUST NOT be empty strings. + *

    + * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details. + *

    + * Note: clients SHOULD use control character filtering as discussed in + * [draft-ietf-secsh-architecture-XX.txt] to avoid attacks by including + * terminal control characters in the fields to be displayed. + * + * @param name the name String sent by the server. + * @param instruction the instruction String sent by the server. + * @param numPrompts number of prompts - may be zero (in this case, you should just + * return a String array of length zero). + * @param prompt an array (length numPrompts) of Strings + * @param echo an array (length numPrompts) of booleans. For + * each prompt, the corresponding echo field indicates whether or + * not the user input should be echoed as characters are typed. + * @return an array of reponses - the array size must match the parameter + * numPrompts. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) throws Exception; } diff --git a/src/ch/ethz/ssh2/KnownHosts.java b/src/ch/ethz/ssh2/KnownHosts.java index fa4ac98..8a0d967 100644 --- a/src/ch/ethz/ssh2/KnownHosts.java +++ b/src/ch/ethz/ssh2/KnownHosts.java @@ -1,20 +1,5 @@ - package ch.ethz.ssh2; -import java.io.BufferedReader; -import java.io.CharArrayReader; -import java.io.CharArrayWriter; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.SecureRandom; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Vector; - import ch.ethz.ssh2.crypto.Base64; import ch.ethz.ssh2.crypto.digest.Digest; import ch.ethz.ssh2.crypto.digest.HMAC; @@ -25,6 +10,14 @@ import ch.ethz.ssh2.signature.RSAPublicKey; import ch.ethz.ssh2.signature.RSASHA1Verify; +import java.io.*; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Vector; + /** * The KnownHosts class is a handy tool to verify received server hostkeys * based on the information in known_hosts files (the ones used by OpenSSH). @@ -36,524 +29,628 @@ *

    * It is a thread safe implementation, therefore, you need only to instantiate one * KnownHosts for your whole application. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: KnownHosts.java,v 1.5 2006/07/30 21:59:29 cplattne Exp $ */ -public class KnownHosts -{ - public static final int HOSTKEY_IS_OK = 0; - public static final int HOSTKEY_IS_NEW = 1; - public static final int HOSTKEY_HAS_CHANGED = 2; - - private class KnownHostsEntry - { - String[] patterns; - Object key; - - KnownHostsEntry(String[] patterns, Object key) - { - this.patterns = patterns; - this.key = key; - } - } - - private LinkedList publicKeys = new LinkedList(); - - public KnownHosts() - { - } - - public KnownHosts(char[] knownHostsData) throws IOException - { - initialize(knownHostsData); - } - - public KnownHosts(File knownHosts) throws IOException - { - initialize(knownHosts); - } - - /** - * Adds a single public key entry to the database. Note: this will NOT add the public key - * to any physical file (e.g., "~/.ssh/known_hosts") - use addHostkeyToFile() for that purpose. - * This method is designed to be used in a {@link ServerHostKeyVerifier}. - * - * @param hostnames a list of hostname patterns - at least one most be specified. Check out the - * OpenSSH sshd man page for a description of the pattern matching algorithm. - * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. - * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. - * @throws IOException - */ - public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException - { - if (hostnames == null) - throw new IllegalArgumentException("hostnames may not be null"); - - if ("ssh-rsa".equals(serverHostKeyAlgorithm)) - { - RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); - - synchronized (publicKeys) - { - publicKeys.add(new KnownHostsEntry(hostnames, rpk)); - } - } - else if ("ssh-dss".equals(serverHostKeyAlgorithm)) - { - DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); - - synchronized (publicKeys) - { - publicKeys.add(new KnownHostsEntry(hostnames, dpk)); - } - } - else - throw new IOException("Unknwon host key type (" + serverHostKeyAlgorithm + ")"); - } - - /** - * Parses the given known_hosts data and adds entries to the database. - * - * @param knownHostsData - * @throws IOException - */ - public void addHostkeys(char[] knownHostsData) throws IOException - { - initialize(knownHostsData); - } - - /** - * Parses the given known_hosts file and adds entries to the database. - * - * @param knownHosts - * @throws IOException - */ - public void addHostkeys(File knownHosts) throws IOException - { - initialize(knownHosts); - } - - /** - * Generate the hashed representation of the given hostname. Useful for adding entries - * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen). - * - * @param hostname - * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" - */ - public static final String createHashedHostname(String hostname) - { - SHA1 sha1 = new SHA1(); - - byte[] salt = new byte[sha1.getDigestLength()]; - - new SecureRandom().nextBytes(salt); - - byte[] hash = hmacSha1Hash(salt, hostname); - - String base64_salt = new String(Base64.encode(salt)); - String base64_hash = new String(Base64.encode(hash)); - - return new String("|1|" + base64_salt + "|" + base64_hash); - } - - private static final byte[] hmacSha1Hash(byte[] salt, String hostname) - { - SHA1 sha1 = new SHA1(); - - if (salt.length != sha1.getDigestLength()) - throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); - - HMAC hmac = new HMAC(sha1, salt, salt.length); - - hmac.update(hostname.getBytes()); - - byte[] dig = new byte[hmac.getDigestLength()]; - - hmac.digest(dig); - - return dig; - } - - private final boolean checkHashed(String entry, String hostname) - { - if (entry.startsWith("|1|") == false) - return false; - - int delim_idx = entry.indexOf('|', 3); - - if (delim_idx == -1) - return false; - - String salt_base64 = entry.substring(3, delim_idx); - String hash_base64 = entry.substring(delim_idx + 1); - - byte[] salt = null; - byte[] hash = null; - - try - { - salt = Base64.decode(salt_base64.toCharArray()); - hash = Base64.decode(hash_base64.toCharArray()); - } - catch (IOException e) - { - return false; - } - - SHA1 sha1 = new SHA1(); - - if (salt.length != sha1.getDigestLength()) - return false; - - byte[] dig = hmacSha1Hash(salt, hostname); - - for (int i = 0; i < dig.length; i++) - if (dig[i] != hash[i]) - return false; - - return true; - } - - private int checkKey(String remoteHostname, Object remoteKey) - { - int result = HOSTKEY_IS_NEW; - - synchronized (publicKeys) - { - Iterator i = publicKeys.iterator(); - - while (i.hasNext()) - { - KnownHostsEntry ke = (KnownHostsEntry) i.next(); +public class KnownHosts { + public static final int HOSTKEY_IS_OK = 0; + public static final int HOSTKEY_IS_NEW = 1; + public static final int HOSTKEY_HAS_CHANGED = 2; + private LinkedList publicKeys = new LinkedList(); + + public KnownHosts() { + } + + public KnownHosts(char[] knownHostsData) throws IOException { + initialize(knownHostsData); + } + + public KnownHosts(File knownHosts) throws IOException { + initialize(knownHosts); + } + + /** + * Generate the hashed representation of the given hostname. Useful for adding entries + * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen). + * + * @param hostname + * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" + */ + public static final String createHashedHostname(String hostname) { + SHA1 sha1 = new SHA1(); + + byte[] salt = new byte[sha1.getDigestLength()]; + + new SecureRandom().nextBytes(salt); + + byte[] hash = hmacSha1Hash(salt, hostname); + + String base64_salt = new String(Base64.encode(salt)); + String base64_hash = new String(Base64.encode(hash)); + + return new String("|1|" + base64_salt + "|" + base64_hash); + } + + private static final byte[] hmacSha1Hash(byte[] salt, String hostname) { + SHA1 sha1 = new SHA1(); + + if (salt.length != sha1.getDigestLength()) + throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); + + HMAC hmac = new HMAC(sha1, salt, salt.length); + + hmac.update(hostname.getBytes()); + + byte[] dig = new byte[hmac.getDigestLength()]; + + hmac.digest(dig); + + return dig; + } + + /** + * Adds a single public key entry to the a known_hosts file. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param knownHosts the file where the publickey entry will be appended. + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public final static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, + byte[] serverHostKey) throws IOException { + if ((hostnames == null) || (hostnames.length == 0)) + throw new IllegalArgumentException("Need at least one hostname specification"); + + if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) + throw new IllegalArgumentException(); + + CharArrayWriter writer = new CharArrayWriter(); + + for (int i = 0; i < hostnames.length; i++) { + if (i != 0) + writer.write(','); + writer.write(hostnames[i]); + } + + writer.write(' '); + writer.write(serverHostKeyAlgorithm); + writer.write(' '); + writer.write(Base64.encode(serverHostKey)); + writer.write("\n"); + + char[] entry = writer.toCharArray(); + + RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); + + long len = raf.length(); + + if (len > 0) { + raf.seek(len - 1); + int last = raf.read(); + if (last != '\n') + raf.write('\n'); + } + + raf.write(new String(entry).getBytes()); + raf.close(); + } + + /** + * Generates a "raw" fingerprint of a hostkey. + * + * @param type either "md5" or "sha1" + * @param keyType either "ssh-rsa" or "ssh-dss" + * @param hostkey the hostkey + * @return the raw fingerprint + */ + static final private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) { + Digest dig = null; + + if ("md5".equals(type)) { + dig = new MD5(); + } else if ("sha1".equals(type)) { + dig = new SHA1(); + } else + throw new IllegalArgumentException("Unknown hash type " + type); + + if ("ssh-rsa".equals(keyType)) { + } else if ("ssh-dss".equals(keyType)) { + } else + throw new IllegalArgumentException("Unknown key type " + keyType); + + if (hostkey == null) + throw new IllegalArgumentException("hostkey is null"); + + dig.update(hostkey); + byte[] res = new byte[dig.getDigestLength()]; + dig.digest(res); + return res; + } + + /** + * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). + * + * @param fingerprint raw fingerprint + * @return the hex representation + */ + static final private String rawToHexFingerprint(byte[] fingerprint) { + final char[] alpha = "0123456789abcdef".toCharArray(); + + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < fingerprint.length; i++) { + if (i != 0) + sb.append(':'); + int b = fingerprint[i] & 0xff; + sb.append(alpha[b >> 4]); + sb.append(alpha[b & 15]); + } + + return sb.toString(); + } + + /** + * Convert a raw fingerprint to bubblebabble representation. + * + * @param raw raw fingerprint + * @return the bubblebabble representation + */ + static final private String rawToBubblebabbleFingerprint(byte[] raw) { + final char[] v = "aeiouy".toCharArray(); + final char[] c = "bcdfghklmnprstvzx".toCharArray(); + + StringBuffer sb = new StringBuffer(); + + int seed = 1; + + int rounds = (raw.length / 2) + 1; + + sb.append('x'); + + for (int i = 0; i < rounds; i++) { + if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) { + sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); + sb.append(c[(raw[2 * i] >> 2) & 15]); + sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); + + if ((i + 1) < rounds) { + sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); + sb.append('-'); + sb.append(c[(((raw[(2 * i) + 1]))) & 15]); + // As long as seed >= 0, seed will be >= 0 afterwards + seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; + } + } else { + sb.append(v[seed % 6]); // seed >= 0, therefore index positive + sb.append('x'); + sb.append(v[seed / 6]); + } + } + + sb.append('x'); + + return sb.toString(); + } + + /** + * Convert a ssh2 key-blob into a human readable hex fingerprint. + * Generated fingerprints are identical to those generated by OpenSSH. + *

    + * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47. + * + * @param keytype either "ssh-rsa" or "ssh-dss" + * @param publickey key blob + * @return Hex fingerprint + */ + public final static String createHexFingerprint(String keytype, byte[] publickey) { + byte[] raw = rawFingerPrint("md5", keytype, publickey); + return rawToHexFingerprint(raw); + } + + /** + * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint. + * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints + * that are easier to remember for humans. + *

    + * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux. + * + * @param keytype either "ssh-rsa" or "ssh-dss" + * @param publickey key data + * @return Bubblebabble fingerprint + */ + public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey) { + byte[] raw = rawFingerPrint("sha1", keytype, publickey); + return rawToBubblebabbleFingerprint(raw); + } + + /** + * Adds a single public key entry to the database. Note: this will NOT add the public key + * to any physical file (e.g., "~/.ssh/known_hosts") - use addHostkeyToFile() for that purpose. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + if (hostnames == null) + throw new IllegalArgumentException("hostnames may not be null"); + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, rpk)); + } + } else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, dpk)); + } + } else + throw new IOException("Unknwon host key type (" + serverHostKeyAlgorithm + ")"); + } + + /** + * Parses the given known_hosts data and adds entries to the database. + * + * @param knownHostsData + * @throws IOException + */ + public void addHostkeys(char[] knownHostsData) throws IOException { + initialize(knownHostsData); + } + + /** + * Parses the given known_hosts file and adds entries to the database. + * + * @param knownHosts + * @throws IOException + */ + public void addHostkeys(File knownHosts) throws IOException { + initialize(knownHosts); + } + + private final boolean checkHashed(String entry, String hostname) { + if (entry.startsWith("|1|") == false) + return false; + + int delim_idx = entry.indexOf('|', 3); + + if (delim_idx == -1) + return false; + + String salt_base64 = entry.substring(3, delim_idx); + String hash_base64 = entry.substring(delim_idx + 1); + + byte[] salt = null; + byte[] hash = null; + + try { + salt = Base64.decode(salt_base64.toCharArray()); + hash = Base64.decode(hash_base64.toCharArray()); + } catch (IOException e) { + return false; + } + + SHA1 sha1 = new SHA1(); + + if (salt.length != sha1.getDigestLength()) + return false; + + byte[] dig = hmacSha1Hash(salt, hostname); + + for (int i = 0; i < dig.length; i++) + if (dig[i] != hash[i]) + return false; + + return true; + } + + private int checkKey(String remoteHostname, Object remoteKey) { + int result = HOSTKEY_IS_NEW; + + synchronized (publicKeys) { + Iterator i = publicKeys.iterator(); + + while (i.hasNext()) { + KnownHostsEntry ke = (KnownHostsEntry) i.next(); + + if (hostnameMatches(ke.patterns, remoteHostname) == false) + continue; + + boolean res = matchKeys(ke.key, remoteKey); + + if (res == true) + return HOSTKEY_IS_OK; + + result = HOSTKEY_HAS_CHANGED; + } + } + return result; + } + + private Vector getAllKeys(String hostname) { + Vector keys = new Vector(); + + synchronized (publicKeys) { + Iterator i = publicKeys.iterator(); + + while (i.hasNext()) { + KnownHostsEntry ke = (KnownHostsEntry) i.next(); + + if (hostnameMatches(ke.patterns, hostname) == false) + continue; + + keys.addElement(ke.key); + } + } - if (hostnameMatches(ke.patterns, remoteHostname) == false) - continue; + return keys; + } - boolean res = matchKeys(ke.key, remoteKey); + /** + * Try to find the preferred order of hostkey algorithms for the given hostname. + * Based on the type of hostkey that is present in the internal database + * (i.e., either ssh-rsa or ssh-dss) + * an ordered list of hostkey algorithms is returned which can be passed + * to Connection.setServerHostKeyAlgorithms. + * + * @param hostname + * @return null if no key for the given hostname is present or + * there are keys of multiple types present for the given hostname. Otherwise, + * an array with hostkey algorithms is returned (i.e., an array of length 2). + */ + public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) { + String[] algos = recommendHostkeyAlgorithms(hostname); - if (res == true) - return HOSTKEY_IS_OK; + if (algos != null) + return algos; - result = HOSTKEY_HAS_CHANGED; - } - } - return result; - } + InetAddress[] ipAdresses = null; - private Vector getAllKeys(String hostname) - { - Vector keys = new Vector(); + try { + ipAdresses = InetAddress.getAllByName(hostname); + } catch (UnknownHostException e) { + return null; + } - synchronized (publicKeys) - { - Iterator i = publicKeys.iterator(); + for (int i = 0; i < ipAdresses.length; i++) { + algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); - while (i.hasNext()) - { - KnownHostsEntry ke = (KnownHostsEntry) i.next(); + if (algos != null) + return algos; + } - if (hostnameMatches(ke.patterns, hostname) == false) - continue; + return null; + } - keys.addElement(ke.key); - } - } + private final boolean hostnameMatches(String[] hostpatterns, String hostname) { + boolean isMatch = false; + boolean negate = false; - return keys; - } + hostname = hostname.toLowerCase(); - /** - * Try to find the preferred order of hostkey algorithms for the given hostname. - * Based on the type of hostkey that is present in the internal database - * (i.e., either ssh-rsa or ssh-dss) - * an ordered list of hostkey algorithms is returned which can be passed - * to Connection.setServerHostKeyAlgorithms. - * - * @param hostname - * @return null if no key for the given hostname is present or - * there are keys of multiple types present for the given hostname. Otherwise, - * an array with hostkey algorithms is returned (i.e., an array of length 2). - */ - public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) - { - String[] algos = recommendHostkeyAlgorithms(hostname); - - if (algos != null) - return algos; - - InetAddress[] ipAdresses = null; - - try - { - ipAdresses = InetAddress.getAllByName(hostname); - } - catch (UnknownHostException e) - { - return null; - } - - for (int i = 0; i < ipAdresses.length; i++) - { - algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); - - if (algos != null) - return algos; - } + for (int k = 0; k < hostpatterns.length; k++) { + if (hostpatterns[k] == null) + continue; - return null; - } - - private final boolean hostnameMatches(String[] hostpatterns, String hostname) - { - boolean isMatch = false; - boolean negate = false; - - hostname = hostname.toLowerCase(); - - for (int k = 0; k < hostpatterns.length; k++) - { - if (hostpatterns[k] == null) - continue; - - String pattern = null; + String pattern = null; /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed - * entries in lines with multiple entries). + * entries in lines with multiple entries). */ - if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) - { - pattern = hostpatterns[k].substring(1); - negate = true; - } - else - { - pattern = hostpatterns[k]; - negate = false; - } + if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) { + pattern = hostpatterns[k].substring(1); + negate = true; + } else { + pattern = hostpatterns[k]; + negate = false; + } /* Optimize, no need to check this entry */ - if ((isMatch) && (negate == false)) - continue; + if ((isMatch) && (negate == false)) + continue; /* Now compare */ - if (pattern.charAt(0) == '|') - { - if (checkHashed(pattern, hostname)) - { - if (negate) - return false; - isMatch = true; - } - } - else - { - pattern = pattern.toLowerCase(); - - if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) - { - if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) - { - if (negate) - return false; - isMatch = true; - } - } - else if (pattern.compareTo(hostname) == 0) - { - if (negate) - return false; - isMatch = true; - } - } - } - - return isMatch; - } - - private void initialize(char[] knownHostsData) throws IOException - { - BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); - - while (true) - { - String line = br.readLine(); - - if (line == null) - break; - - line = line.trim(); - - if (line.startsWith("#")) - continue; - - String[] arr = line.split(" "); - - if (arr.length >= 3) - { - if ((arr[1].compareTo("ssh-rsa") == 0) || (arr[1].compareTo("ssh-dss") == 0)) - { - String[] hostnames = arr[0].split(","); - - byte[] msg = Base64.decode(arr[2].toCharArray()); - - addHostkey(hostnames, arr[1], msg); - } - } - } - } - - private void initialize(File knownHosts) throws IOException - { - char[] buff = new char[512]; - - CharArrayWriter cw = new CharArrayWriter(); - - knownHosts.createNewFile(); - - FileReader fr = new FileReader(knownHosts); - - while (true) - { - int len = fr.read(buff); - if (len < 0) - break; - cw.write(buff, 0, len); - } - - fr.close(); - - initialize(cw.toCharArray()); - } - - private final boolean matchKeys(Object key1, Object key2) - { - if ((key1 instanceof RSAPublicKey) && (key2 instanceof RSAPublicKey)) - { - RSAPublicKey savedRSAKey = (RSAPublicKey) key1; - RSAPublicKey remoteRSAKey = (RSAPublicKey) key2; - - if (savedRSAKey.getE().equals(remoteRSAKey.getE()) == false) - return false; - - if (savedRSAKey.getN().equals(remoteRSAKey.getN()) == false) - return false; - - return true; - } - - if ((key1 instanceof DSAPublicKey) && (key2 instanceof DSAPublicKey)) - { - DSAPublicKey savedDSAKey = (DSAPublicKey) key1; - DSAPublicKey remoteDSAKey = (DSAPublicKey) key2; + if (pattern.charAt(0) == '|') { + if (checkHashed(pattern, hostname)) { + if (negate) + return false; + isMatch = true; + } + } else { + pattern = pattern.toLowerCase(); + + if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) { + if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) { + if (negate) + return false; + isMatch = true; + } + } else if (pattern.compareTo(hostname) == 0) { + if (negate) + return false; + isMatch = true; + } + } + } + + return isMatch; + } + + private void initialize(char[] knownHostsData) throws IOException { + BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); + + while (true) { + String line = br.readLine(); + + if (line == null) + break; + + line = line.trim(); + + if (line.startsWith("#")) + continue; + + String[] arr = line.split(" "); + + if (arr.length >= 3) { + if ((arr[1].compareTo("ssh-rsa") == 0) || (arr[1].compareTo("ssh-dss") == 0)) { + String[] hostnames = arr[0].split(","); + + byte[] msg = Base64.decode(arr[2].toCharArray()); + + addHostkey(hostnames, arr[1], msg); + } + } + } + } + + private void initialize(File knownHosts) throws IOException { + char[] buff = new char[512]; + + CharArrayWriter cw = new CharArrayWriter(); + + knownHosts.createNewFile(); + + FileReader fr = new FileReader(knownHosts); + + while (true) { + int len = fr.read(buff); + if (len < 0) + break; + cw.write(buff, 0, len); + } + + fr.close(); + + initialize(cw.toCharArray()); + } + + private final boolean matchKeys(Object key1, Object key2) { + if ((key1 instanceof RSAPublicKey) && (key2 instanceof RSAPublicKey)) { + RSAPublicKey savedRSAKey = (RSAPublicKey) key1; + RSAPublicKey remoteRSAKey = (RSAPublicKey) key2; + + if (savedRSAKey.getE().equals(remoteRSAKey.getE()) == false) + return false; + + if (savedRSAKey.getN().equals(remoteRSAKey.getN()) == false) + return false; + + return true; + } + + if ((key1 instanceof DSAPublicKey) && (key2 instanceof DSAPublicKey)) { + DSAPublicKey savedDSAKey = (DSAPublicKey) key1; + DSAPublicKey remoteDSAKey = (DSAPublicKey) key2; - if (savedDSAKey.getG().equals(remoteDSAKey.getG()) == false) - return false; + if (savedDSAKey.getG().equals(remoteDSAKey.getG()) == false) + return false; - if (savedDSAKey.getP().equals(remoteDSAKey.getP()) == false) - return false; + if (savedDSAKey.getP().equals(remoteDSAKey.getP()) == false) + return false; - if (savedDSAKey.getQ().equals(remoteDSAKey.getQ()) == false) - return false; + if (savedDSAKey.getQ().equals(remoteDSAKey.getQ()) == false) + return false; - if (savedDSAKey.getY().equals(remoteDSAKey.getY()) == false) - return false; + if (savedDSAKey.getY().equals(remoteDSAKey.getY()) == false) + return false; - return true; - } + return true; + } - return false; - } + return false; + } - private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j) - { + private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j) { /* This matching logic is equivalent to the one present in OpenSSH 4.1 */ - while (true) - { + while (true) { /* Are we at the end of the pattern? */ - if (pattern.length == i) - return (match.length == j); - - if (pattern[i] == '*') - { - i++; - - if (pattern.length == i) - return true; - - if ((pattern[i] != '*') && (pattern[i] != '?')) - { - while (true) - { - if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) - return true; - j++; - if (match.length == j) - return false; - } - } - - while (true) - { - if (pseudoRegex(pattern, i, match, j)) - return true; - j++; - if (match.length == j) - return false; - } - } - - if (match.length == j) - return false; - - if ((pattern[i] != '?') && (pattern[i] != match[j])) - return false; - - i++; - j++; - } - } - - private String[] recommendHostkeyAlgorithms(String hostname) - { - String preferredAlgo = null; - - Vector keys = getAllKeys(hostname); - - for (int i = 0; i < keys.size(); i++) - { - String thisAlgo = null; - - if (keys.elementAt(i) instanceof RSAPublicKey) - thisAlgo = "ssh-rsa"; - else if (keys.elementAt(i) instanceof DSAPublicKey) - thisAlgo = "ssh-dss"; - else - continue; - - if (preferredAlgo != null) - { + if (pattern.length == i) + return (match.length == j); + + if (pattern[i] == '*') { + i++; + + if (pattern.length == i) + return true; + + if ((pattern[i] != '*') && (pattern[i] != '?')) { + while (true) { + if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) + return true; + j++; + if (match.length == j) + return false; + } + } + + while (true) { + if (pseudoRegex(pattern, i, match, j)) + return true; + j++; + if (match.length == j) + return false; + } + } + + if (match.length == j) + return false; + + if ((pattern[i] != '?') && (pattern[i] != match[j])) + return false; + + i++; + j++; + } + } + + private String[] recommendHostkeyAlgorithms(String hostname) { + String preferredAlgo = null; + + Vector keys = getAllKeys(hostname); + + for (int i = 0; i < keys.size(); i++) { + String thisAlgo = null; + + if (keys.elementAt(i) instanceof RSAPublicKey) + thisAlgo = "ssh-rsa"; + else if (keys.elementAt(i) instanceof DSAPublicKey) + thisAlgo = "ssh-dss"; + else + continue; + + if (preferredAlgo != null) { /* If we find different key types, then return null */ - if (preferredAlgo.compareTo(thisAlgo) != 0) - return null; + if (preferredAlgo.compareTo(thisAlgo) != 0) + return null; /* OK, we found the same algo again, optimize */ - continue; - } - } + continue; + } + } /* If we did not find anything that we know of, return null */ - if (preferredAlgo == null) - return null; + if (preferredAlgo == null) + return null; /* Now put the preferred algo to the start of the array. * You may ask yourself why we do it that way - basically, we could just @@ -567,268 +664,71 @@ else if (keys.elementAt(i) instanceof DSAPublicKey) * if he/she wants to accept the new key. */ - if (preferredAlgo.equals("ssh-rsa")) - return new String[] { "ssh-rsa", "ssh-dss" }; - - return new String[] { "ssh-dss", "ssh-rsa" }; - } - - /** - * Checks the internal hostkey database for the given hostkey. - * If no matching key can be found, then the hostname is resolved to an IP address - * and the search is repeated using that IP address. - * - * @param hostname the server's hostname, will be matched with all hostname patterns - * @param serverHostKeyAlgorithm type of hostkey, either ssh-rsa or ssh-dss - * @param serverHostKey the key blob - * @return

      - *
    • HOSTKEY_IS_OK: the given hostkey matches an entry for the given hostname
    • - *
    • HOSTKEY_IS_NEW: no entries found for this hostname and this type of hostkey
    • - *
    • HOSTKEY_HAS_CHANGED: hostname is known, but with another key of the same type - * (man-in-the-middle attack?)
    • - *
    - * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. - */ - public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException - { - Object remoteKey = null; - - if ("ssh-rsa".equals(serverHostKeyAlgorithm)) - { - remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); - } - else if ("ssh-dss".equals(serverHostKeyAlgorithm)) - { - remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); - } - else - throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); - - int result = checkKey(hostname, remoteKey); - - if (result == HOSTKEY_IS_OK) - return result; - - InetAddress[] ipAdresses = null; - - try - { - ipAdresses = InetAddress.getAllByName(hostname); - } - catch (UnknownHostException e) - { - return result; - } - - for (int i = 0; i < ipAdresses.length; i++) - { - int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); - - if (newresult == HOSTKEY_IS_OK) - return newresult; - - if (newresult == HOSTKEY_HAS_CHANGED) - result = HOSTKEY_HAS_CHANGED; - } - - return result; - } - - /** - * Adds a single public key entry to the a known_hosts file. - * This method is designed to be used in a {@link ServerHostKeyVerifier}. - * - * @param knownHosts the file where the publickey entry will be appended. - * @param hostnames a list of hostname patterns - at least one most be specified. Check out the - * OpenSSH sshd man page for a description of the pattern matching algorithm. - * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. - * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. - * @throws IOException - */ - public final static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, - byte[] serverHostKey) throws IOException - { - if ((hostnames == null) || (hostnames.length == 0)) - throw new IllegalArgumentException("Need at least one hostname specification"); - - if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) - throw new IllegalArgumentException(); - - CharArrayWriter writer = new CharArrayWriter(); - - for (int i = 0; i < hostnames.length; i++) - { - if (i != 0) - writer.write(','); - writer.write(hostnames[i]); - } - - writer.write(' '); - writer.write(serverHostKeyAlgorithm); - writer.write(' '); - writer.write(Base64.encode(serverHostKey)); - writer.write("\n"); - - char[] entry = writer.toCharArray(); - - RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); - - long len = raf.length(); - - if (len > 0) - { - raf.seek(len - 1); - int last = raf.read(); - if (last != '\n') - raf.write('\n'); - } - - raf.write(new String(entry).getBytes()); - raf.close(); - } - - /** - * Generates a "raw" fingerprint of a hostkey. - * - * @param type either "md5" or "sha1" - * @param keyType either "ssh-rsa" or "ssh-dss" - * @param hostkey the hostkey - * @return the raw fingerprint - */ - static final private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) - { - Digest dig = null; - - if ("md5".equals(type)) - { - dig = new MD5(); - } - else if ("sha1".equals(type)) - { - dig = new SHA1(); - } - else - throw new IllegalArgumentException("Unknown hash type " + type); - - if ("ssh-rsa".equals(keyType)) - { - } - else if ("ssh-dss".equals(keyType)) - { - } - else - throw new IllegalArgumentException("Unknown key type " + keyType); - - if (hostkey == null) - throw new IllegalArgumentException("hostkey is null"); - - dig.update(hostkey); - byte[] res = new byte[dig.getDigestLength()]; - dig.digest(res); - return res; - } - - /** - * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). - * @param fingerprint raw fingerprint - * @return the hex representation - */ - static final private String rawToHexFingerprint(byte[] fingerprint) - { - final char[] alpha = "0123456789abcdef".toCharArray(); - - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < fingerprint.length; i++) - { - if (i != 0) - sb.append(':'); - int b = fingerprint[i] & 0xff; - sb.append(alpha[b >> 4]); - sb.append(alpha[b & 15]); - } - - return sb.toString(); - } - - /** - * Convert a raw fingerprint to bubblebabble representation. - * @param raw raw fingerprint - * @return the bubblebabble representation - */ - static final private String rawToBubblebabbleFingerprint(byte[] raw) - { - final char[] v = "aeiouy".toCharArray(); - final char[] c = "bcdfghklmnprstvzx".toCharArray(); - - StringBuffer sb = new StringBuffer(); - - int seed = 1; - - int rounds = (raw.length / 2) + 1; - - sb.append('x'); - - for (int i = 0; i < rounds; i++) - { - if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) - { - sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); - sb.append(c[(raw[2 * i] >> 2) & 15]); - sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); - - if ((i + 1) < rounds) - { - sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); - sb.append('-'); - sb.append(c[(((raw[(2 * i) + 1]))) & 15]); - // As long as seed >= 0, seed will be >= 0 afterwards - seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; - } - } - else - { - sb.append(v[seed % 6]); // seed >= 0, therefore index positive - sb.append('x'); - sb.append(v[seed / 6]); - } - } - - sb.append('x'); - - return sb.toString(); - } - - /** - * Convert a ssh2 key-blob into a human readable hex fingerprint. - * Generated fingerprints are identical to those generated by OpenSSH. - *

    - * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47. - - * @param keytype either "ssh-rsa" or "ssh-dss" - * @param publickey key blob - * @return Hex fingerprint - */ - public final static String createHexFingerprint(String keytype, byte[] publickey) - { - byte[] raw = rawFingerPrint("md5", keytype, publickey); - return rawToHexFingerprint(raw); - } - - /** - * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint. - * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints - * that are easier to remember for humans. - *

    - * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux. - * - * @param keytype either "ssh-rsa" or "ssh-dss" - * @param publickey key data - * @return Bubblebabble fingerprint - */ - public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey) - { - byte[] raw = rawFingerPrint("sha1", keytype, publickey); - return rawToBubblebabbleFingerprint(raw); - } + if (preferredAlgo.equals("ssh-rsa")) + return new String[]{"ssh-rsa", "ssh-dss"}; + + return new String[]{"ssh-dss", "ssh-rsa"}; + } + + /** + * Checks the internal hostkey database for the given hostkey. + * If no matching key can be found, then the hostname is resolved to an IP address + * and the search is repeated using that IP address. + * + * @param hostname the server's hostname, will be matched with all hostname patterns + * @param serverHostKeyAlgorithm type of hostkey, either ssh-rsa or ssh-dss + * @param serverHostKey the key blob + * @return

      + *
    • HOSTKEY_IS_OK: the given hostkey matches an entry for the given hostname
    • + *
    • HOSTKEY_IS_NEW: no entries found for this hostname and this type of hostkey
    • + *
    • HOSTKEY_HAS_CHANGED: hostname is known, but with another key of the same type + * (man-in-the-middle attack?)
    • + *
    + * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. + */ + public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + Object remoteKey = null; + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { + remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + } else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { + remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + } else + throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); + + int result = checkKey(hostname, remoteKey); + + if (result == HOSTKEY_IS_OK) + return result; + + InetAddress[] ipAdresses = null; + + try { + ipAdresses = InetAddress.getAllByName(hostname); + } catch (UnknownHostException e) { + return result; + } + + for (int i = 0; i < ipAdresses.length; i++) { + int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); + + if (newresult == HOSTKEY_IS_OK) + return newresult; + + if (newresult == HOSTKEY_HAS_CHANGED) + result = HOSTKEY_HAS_CHANGED; + } + + return result; + } + + private class KnownHostsEntry { + String[] patterns; + Object key; + + KnownHostsEntry(String[] patterns, Object key) { + this.patterns = patterns; + this.key = key; + } + } } diff --git a/src/ch/ethz/ssh2/LocalPortForwarder.java b/src/ch/ethz/ssh2/LocalPortForwarder.java index b81866b..34a3655 100644 --- a/src/ch/ethz/ssh2/LocalPortForwarder.java +++ b/src/ch/ethz/ssh2/LocalPortForwarder.java @@ -1,51 +1,47 @@ - package ch.ethz.ssh2; -import java.io.IOException; - import ch.ethz.ssh2.channel.ChannelManager; import ch.ethz.ssh2.channel.LocalAcceptThread; +import java.io.IOException; + /** * A LocalPortForwarder forwards TCP/IP connections to a local * port via the secure tunnel to another host (which may or may not be identical * to the remote SSH-2 server). - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: LocalPortForwarder.java,v 1.5 2006/02/14 19:43:16 cplattne Exp $ */ -public class LocalPortForwarder -{ - ChannelManager cm; - - int local_port; - - String host_to_connect; - - int port_to_connect; - - LocalAcceptThread lat; - - LocalPortForwarder(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) - throws IOException - { - this.cm = cm; - this.local_port = local_port; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - - lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect); - lat.setDaemon(true); - lat.start(); - } - - /** - * Stop TCP/IP forwarding of newly arriving connections. - * - * @throws IOException - */ - public void close() throws IOException - { - lat.stopWorking(); - } +public class LocalPortForwarder { + ChannelManager cm; + + int local_port; + + String host_to_connect; + + int port_to_connect; + + LocalAcceptThread lat; + + LocalPortForwarder(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.local_port = local_port; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException { + lat.stopWorking(); + } } diff --git a/src/ch/ethz/ssh2/LocalStreamForwarder.java b/src/ch/ethz/ssh2/LocalStreamForwarder.java index 8924b4d..26b3f00 100644 --- a/src/ch/ethz/ssh2/LocalStreamForwarder.java +++ b/src/ch/ethz/ssh2/LocalStreamForwarder.java @@ -1,77 +1,71 @@ - package ch.ethz.ssh2; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import ch.ethz.ssh2.channel.Channel; import ch.ethz.ssh2.channel.ChannelManager; import ch.ethz.ssh2.channel.LocalAcceptThread; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + /** * A LocalStreamForwarder forwards an Input- and Outputstream * pair via the secure tunnel to another host (which may or may not be identical * to the remote SSH-2 server). - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: LocalStreamForwarder.java,v 1.6 2006/02/14 19:43:16 cplattne Exp $ */ -public class LocalStreamForwarder -{ - ChannelManager cm; +public class LocalStreamForwarder { + ChannelManager cm; - String host_to_connect; - int port_to_connect; - LocalAcceptThread lat; + String host_to_connect; + int port_to_connect; + LocalAcceptThread lat; - Channel cn; + Channel cn; - LocalStreamForwarder(ChannelManager cm, String host_to_connect, int port_to_connect) throws IOException - { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; + LocalStreamForwarder(ChannelManager cm, String host_to_connect, int port_to_connect) throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; - cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, "127.0.0.1", 0); - } + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, "127.0.0.1", 0); + } - /** - * @return An InputStream object. - * @throws IOException - */ - public InputStream getInputStream() throws IOException - { - return cn.getStdoutStream(); - } + /** + * @return An InputStream object. + * @throws IOException + */ + public InputStream getInputStream() throws IOException { + return cn.getStdoutStream(); + } - /** - * Get the OutputStream. Please be aware that the implementation MAY use an - * internal buffer. To make sure that the buffered data is sent over the - * tunnel, you have to call the flush method of the - * OutputStream. To signal EOF, please use the - * close method of the OutputStream. - * - * @return An OutputStream object. - * @throws IOException - */ - public OutputStream getOutputStream() throws IOException - { - return cn.getStdinStream(); - } + /** + * Get the OutputStream. Please be aware that the implementation MAY use an + * internal buffer. To make sure that the buffered data is sent over the + * tunnel, you have to call the flush method of the + * OutputStream. To signal EOF, please use the + * close method of the OutputStream. + * + * @return An OutputStream object. + * @throws IOException + */ + public OutputStream getOutputStream() throws IOException { + return cn.getStdinStream(); + } - /** - * Close the underlying SSH forwarding channel and free up resources. - * You can also use this method to force the shutdown of the underlying - * forwarding channel. Pending output (OutputStream not flushed) will NOT - * be sent. Pending input (InputStream) can still be read. If the shutdown - * operation is already in progress (initiated from either side), then this - * call is a no-op. - * - * @throws IOException - */ - public void close() throws IOException - { - cm.closeChannel(cn, "Closed due to user request.", true); - } + /** + * Close the underlying SSH forwarding channel and free up resources. + * You can also use this method to force the shutdown of the underlying + * forwarding channel. Pending output (OutputStream not flushed) will NOT + * be sent. Pending input (InputStream) can still be read. If the shutdown + * operation is already in progress (initiated from either side), then this + * call is a no-op. + * + * @throws IOException + */ + public void close() throws IOException { + cm.closeChannel(cn, "Closed due to user request.", true); + } } diff --git a/src/ch/ethz/ssh2/ProxyData.java b/src/ch/ethz/ssh2/ProxyData.java index 8408e9a..f44d243 100644 --- a/src/ch/ethz/ssh2/ProxyData.java +++ b/src/ch/ethz/ssh2/ProxyData.java @@ -1,15 +1,12 @@ - package ch.ethz.ssh2; /** * An abstract marker interface implemented by all proxy data implementations. - * - * @see HTTPProxyData - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ProxyData.java,v 1.3 2006/08/02 12:17:22 cplattne Exp $ + * @see HTTPProxyData */ -public abstract interface ProxyData -{ +public abstract interface ProxyData { } diff --git a/src/ch/ethz/ssh2/SCPClient.java b/src/ch/ethz/ssh2/SCPClient.java index fca1cb8..e7c6ef2 100644 --- a/src/ch/ethz/ssh2/SCPClient.java +++ b/src/ch/ethz/ssh2/SCPClient.java @@ -1,14 +1,6 @@ - package ch.ethz.ssh2; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; /** * A very basic SCPClient that can be used to copy files from/to @@ -17,695 +9,595 @@ * This scp client is thread safe - you can download (and upload) different sets * of files concurrently without any troubles. The SCPClient is * actually mapping every request to a distinct {@link Session}. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SCPClient.java,v 1.11 2006/08/02 11:57:12 cplattne Exp $ */ -public class SCPClient -{ - Connection conn; - - class LenNamePair - { - long length; - String filename; - } +public class SCPClient { + Connection conn; - public SCPClient(Connection conn) - { - if (conn == null) - throw new IllegalArgumentException("Cannot accept null argument!"); - this.conn = conn; - } + public SCPClient(Connection conn) { + if (conn == null) + throw new IllegalArgumentException("Cannot accept null argument!"); + this.conn = conn; + } - private void readResponse(InputStream is) throws IOException - { - int c = is.read(); + private void readResponse(InputStream is) throws IOException { + int c = is.read(); - if (c == 0) - return; + if (c == 0) + return; - if (c == -1) - throw new IOException("Remote scp terminated unexpectedly."); + if (c == -1) + throw new IOException("Remote scp terminated unexpectedly."); - if ((c != 1) && (c != 2)) - throw new IOException("Remote scp sent illegal error code."); + if ((c != 1) && (c != 2)) + throw new IOException("Remote scp sent illegal error code."); - if (c == 2) - throw new IOException("Remote scp terminated with error."); + if (c == 2) + throw new IOException("Remote scp terminated with error."); - String err = receiveLine(is); - throw new IOException("Remote scp terminated with error (" + err + ")."); - } + String err = receiveLine(is); + throw new IOException("Remote scp terminated with error (" + err + ")."); + } - private String receiveLine(InputStream is) throws IOException - { - StringBuffer sb = new StringBuffer(30); + private String receiveLine(InputStream is) throws IOException { + StringBuffer sb = new StringBuffer(30); - while (true) - { - /* This is a random limit - if your path names are longer, then adjust it */ + while (true) { + /* This is a random limit - if your path names are longer, then adjust it */ - if (sb.length() > 8192) - throw new IOException("Remote scp sent a too long line"); + if (sb.length() > 8192) + throw new IOException("Remote scp sent a too long line"); - int c = is.read(); + int c = is.read(); - if (c < 0) - throw new IOException("Remote scp terminated unexpectedly."); + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); - if (c == '\n') - break; + if (c == '\n') + break; - sb.append((char) c); + sb.append((char) c); - } - return sb.toString(); - } + } + return sb.toString(); + } - private LenNamePair parseCLine(String line) throws IOException - { + private LenNamePair parseCLine(String line) throws IOException { /* Minimum line: "xxxx y z" ---> 8 chars */ - long len; + long len; - if (line.length() < 8) - throw new IOException("Malformed C line sent by remote SCP binary, line too short."); + if (line.length() < 8) + throw new IOException("Malformed C line sent by remote SCP binary, line too short."); - if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) - throw new IOException("Malformed C line sent by remote SCP binary."); + if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) + throw new IOException("Malformed C line sent by remote SCP binary."); - int length_name_sep = line.indexOf(' ', 5); + int length_name_sep = line.indexOf(' ', 5); - if (length_name_sep == -1) - throw new IOException("Malformed C line sent by remote SCP binary."); + if (length_name_sep == -1) + throw new IOException("Malformed C line sent by remote SCP binary."); - String length_substring = line.substring(5, length_name_sep); - String name_substring = line.substring(length_name_sep + 1); + String length_substring = line.substring(5, length_name_sep); + String name_substring = line.substring(length_name_sep + 1); - if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) - throw new IOException("Malformed C line sent by remote SCP binary."); + if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) + throw new IOException("Malformed C line sent by remote SCP binary."); - if ((6 + length_substring.length() + name_substring.length()) != line.length()) - throw new IOException("Malformed C line sent by remote SCP binary."); + if ((6 + length_substring.length() + name_substring.length()) != line.length()) + throw new IOException("Malformed C line sent by remote SCP binary."); - try - { - len = Long.parseLong(length_substring); - } - catch (NumberFormatException e) - { - throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); - } + try { + len = Long.parseLong(length_substring); + } catch (NumberFormatException e) { + throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); + } - if (len < 0) - throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); + if (len < 0) + throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); - LenNamePair lnp = new LenNamePair(); - lnp.length = len; - lnp.filename = name_substring; + LenNamePair lnp = new LenNamePair(); + lnp.length = len; + lnp.filename = name_substring; - return lnp; - } + return lnp; + } - private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException - { - OutputStream os = sess.getStdin(); - InputStream is = new BufferedInputStream(sess.getStdout(), 512); + private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException { + OutputStream os = sess.getStdin(); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); - readResponse(is); + readResponse(is); - String cline = "C" + mode + " " + data.length + " " + fileName + "\n"; + String cline = "C" + mode + " " + data.length + " " + fileName + "\n"; - os.write(cline.getBytes()); - os.flush(); + os.write(cline.getBytes()); + os.flush(); - readResponse(is); + readResponse(is); - os.write(data, 0, data.length); - os.write(0); - os.flush(); + os.write(data, 0, data.length); + os.write(0); + os.flush(); - readResponse(is); + readResponse(is); - os.write("E\n".getBytes()); - os.flush(); - } + os.write("E\n".getBytes()); + os.flush(); + } - private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException - { - byte[] buffer = new byte[8192]; + private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException { + byte[] buffer = new byte[8192]; - OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000); - InputStream is = new BufferedInputStream(sess.getStdout(), 512); + OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); - readResponse(is); + readResponse(is); - for (int i = 0; i < files.length; i++) - { - File f = new File(files[i]); - long remain = f.length(); + for (int i = 0; i < files.length; i++) { + File f = new File(files[i]); + long remain = f.length(); - String remoteName; + String remoteName; - if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null)) - remoteName = remoteFiles[i]; - else - remoteName = f.getName(); + if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null)) + remoteName = remoteFiles[i]; + else + remoteName = f.getName(); - String cline = "C" + mode + " " + remain + " " + remoteName + "\n"; + String cline = "C" + mode + " " + remain + " " + remoteName + "\n"; - os.write(cline.getBytes()); - os.flush(); + os.write(cline.getBytes()); + os.flush(); - readResponse(is); + readResponse(is); - FileInputStream fis = null; + FileInputStream fis = null; - try - { - fis = new FileInputStream(f); + try { + fis = new FileInputStream(f); - while (remain > 0) - { - int trans; - if (remain > buffer.length) - trans = buffer.length; - else - trans = (int) remain; + while (remain > 0) { + int trans; + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; - if (fis.read(buffer, 0, trans) != trans) - throw new IOException("Cannot read enough from local file " + files[i]); + if (fis.read(buffer, 0, trans) != trans) + throw new IOException("Cannot read enough from local file " + files[i]); - os.write(buffer, 0, trans); + os.write(buffer, 0, trans); - remain -= trans; - } - } - finally - { - if (fis != null) - fis.close(); - } + remain -= trans; + } + } finally { + if (fis != null) + fis.close(); + } - os.write(0); - os.flush(); + os.write(0); + os.flush(); - readResponse(is); - } + readResponse(is); + } - os.write("E\n".getBytes()); - os.flush(); - } + os.write("E\n".getBytes()); + os.flush(); + } - private void receiveFiles(Session sess, OutputStream[] targets) throws IOException - { - byte[] buffer = new byte[8192]; + private void receiveFiles(Session sess, OutputStream[] targets) throws IOException { + byte[] buffer = new byte[8192]; - OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); - InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); - os.write(0x0); - os.flush(); + os.write(0x0); + os.flush(); - for (int i = 0; i < targets.length; i++) - { - LenNamePair lnp = null; + for (int i = 0; i < targets.length; i++) { + LenNamePair lnp = null; - while (true) - { - int c = is.read(); - if (c < 0) - throw new IOException("Remote scp terminated unexpectedly."); + while (true) { + int c = is.read(); + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); - String line = receiveLine(is); + String line = receiveLine(is); - if (c == 'T') - { + if (c == 'T') { /* Ignore modification times */ - continue; - } + continue; + } - if ((c == 1) || (c == 2)) - throw new IOException("Remote SCP error: " + line); + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); - if (c == 'C') - { - lnp = parseCLine(line); - break; + if (c == 'C') { + lnp = parseCLine(line); + break; - } - throw new IOException("Remote SCP error: " + ((char) c) + line); - } + } + throw new IOException("Remote SCP error: " + ((char) c) + line); + } - os.write(0x0); - os.flush(); + os.write(0x0); + os.flush(); - long remain = lnp.length; + long remain = lnp.length; - while (remain > 0) - { - int trans; - if (remain > buffer.length) - trans = buffer.length; - else - trans = (int) remain; + while (remain > 0) { + int trans; + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; - int this_time_received = is.read(buffer, 0, trans); + int this_time_received = is.read(buffer, 0, trans); - if (this_time_received < 0) - { - throw new IOException("Remote scp terminated connection unexpectedly"); - } + if (this_time_received < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } - targets[i].write(buffer, 0, this_time_received); + targets[i].write(buffer, 0, this_time_received); - remain -= this_time_received; - } + remain -= this_time_received; + } - readResponse(is); + readResponse(is); - os.write(0x0); - os.flush(); - } - } + os.write(0x0); + os.flush(); + } + } - private void receiveFiles(Session sess, String[] files, String target) throws IOException - { - byte[] buffer = new byte[8192]; + private void receiveFiles(Session sess, String[] files, String target) throws IOException { + byte[] buffer = new byte[8192]; - OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); - InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); - os.write(0x0); - os.flush(); + os.write(0x0); + os.flush(); - for (int i = 0; i < files.length; i++) - { - LenNamePair lnp = null; + for (int i = 0; i < files.length; i++) { + LenNamePair lnp = null; - while (true) - { - int c = is.read(); - if (c < 0) - throw new IOException("Remote scp terminated unexpectedly."); + while (true) { + int c = is.read(); + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); - String line = receiveLine(is); + String line = receiveLine(is); - if (c == 'T') - { + if (c == 'T') { /* Ignore modification times */ - continue; - } - - if ((c == 1) || (c == 2)) - throw new IOException("Remote SCP error: " + line); - - if (c == 'C') - { - lnp = parseCLine(line); - break; - - } - throw new IOException("Remote SCP error: " + ((char) c) + line); - } - - os.write(0x0); - os.flush(); - - File f = new File(target + File.separatorChar + lnp.filename); - FileOutputStream fop = null; - - try - { - fop = new FileOutputStream(f); - - long remain = lnp.length; - - while (remain > 0) - { - int trans; - if (remain > buffer.length) - trans = buffer.length; - else - trans = (int) remain; - - int this_time_received = is.read(buffer, 0, trans); - - if (this_time_received < 0) - { - throw new IOException("Remote scp terminated connection unexpectedly"); - } - - fop.write(buffer, 0, this_time_received); - - remain -= this_time_received; - } - } - finally - { - if (fop != null) - fop.close(); - } - - readResponse(is); - - os.write(0x0); - os.flush(); - } - } - - /** - * Copy a local file to a remote directory, uses mode 0600 when creating - * the file on the remote side. - * - * @param localFile - * Path and name of local file. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * - * @throws IOException - */ - public void put(String localFile, String remoteTargetDirectory) throws IOException - { - put(new String[] { localFile }, remoteTargetDirectory, "0600"); - } - - /** - * Copy a set of local files to a remote directory, uses mode 0600 when - * creating files on the remote side. - * - * @param localFiles - * Paths and names of local file names. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * - * @throws IOException - */ - - public void put(String[] localFiles, String remoteTargetDirectory) throws IOException - { - put(localFiles, remoteTargetDirectory, "0600"); - } - - /** - * Copy a local file to a remote directory, uses the specified mode when - * creating the file on the remote side. - * - * @param localFile - * Path and name of local file. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException - { - put(new String[] { localFile }, remoteTargetDirectory, mode); - } - - /** - * Copy a local file to a remote directory, uses the specified mode and remote filename - * when creating the file on the remote side. - * - * @param localFile - * Path and name of local file. - * @param remoteFileName - * The name of the file which will be created in the remote target directory. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) - throws IOException - { - put(new String[] { localFile }, new String[] { remoteFileName }, remoteTargetDirectory, mode); - } - - /** - * Create a remote file and copy the contents of the passed byte array into it. - * Uses mode 0600 for creating the remote file. - * - * @param data - * the data to be copied into the remote file. - * @param remoteFileName - * The name of the file which will be created in the remote target directory. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * @throws IOException - */ - - public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException - { - put(data, remoteFileName, remoteTargetDirectory, "0600"); - } - - /** - * Create a remote file and copy the contents of the passed byte array into it. - * The method use the specified mode when creating the file on the remote side. - * - * @param data - * the data to be copied into the remote file. - * @param remoteFileName - * The name of the file which will be created in the remote target directory. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException - { - Session sess = null; - - if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null)) - throw new IllegalArgumentException("Null argument."); - - if (mode.length() != 4) - throw new IllegalArgumentException("Invalid mode."); - - for (int i = 0; i < mode.length(); i++) - if (Character.isDigit(mode.charAt(i)) == false) - throw new IllegalArgumentException("Invalid mode."); - - remoteTargetDirectory = remoteTargetDirectory.trim(); - remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; - - String cmd = "scp -t -d " + remoteTargetDirectory; - - try - { - sess = conn.openSession(); - sess.execCommand(cmd); - sendBytes(sess, data, remoteFileName, mode); - } - catch (IOException e) - { - throw (IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally - { - if (sess != null) - sess.close(); - } - } - - /** - * Copy a set of local files to a remote directory, uses the specified mode - * when creating the files on the remote side. - * - * @param localFiles - * Paths and names of the local files. - * @param remoteTargetDirectory - * Remote target directory. Use an empty string to specify the default directory. - * @param mode - * a four digit string (e.g., 0644, see "man chmod", "man open") - * @throws IOException - */ - public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException - { - put(localFiles, null, remoteTargetDirectory, mode); - } - - public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) - throws IOException - { - Session sess = null; + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') { + lnp = parseCLine(line); + break; + + } + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + + File f = new File(target + File.separatorChar + lnp.filename); + FileOutputStream fop = null; + + try { + fop = new FileOutputStream(f); + + long remain = lnp.length; + + while (remain > 0) { + int trans; + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + fop.write(buffer, 0, this_time_received); + + remain -= this_time_received; + } + } finally { + if (fop != null) + fop.close(); + } + + readResponse(is); + + os.write(0x0); + os.flush(); + } + } + + /** + * Copy a local file to a remote directory, uses mode 0600 when creating + * the file on the remote side. + * + * @param localFile Path and name of local file. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory) throws IOException { + put(new String[]{localFile}, remoteTargetDirectory, "0600"); + } + + /** + * Copy a set of local files to a remote directory, uses mode 0600 when + * creating files on the remote side. + * + * @param localFiles Paths and names of local file names. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @throws IOException + */ + + public void put(String[] localFiles, String remoteTargetDirectory) throws IOException { + put(localFiles, remoteTargetDirectory, "0600"); + } + + /** + * Copy a local file to a remote directory, uses the specified mode when + * creating the file on the remote side. + * + * @param localFile Path and name of local file. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @param mode a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException { + put(new String[]{localFile}, remoteTargetDirectory, mode); + } + + /** + * Copy a local file to a remote directory, uses the specified mode and remote filename + * when creating the file on the remote side. + * + * @param localFile Path and name of local file. + * @param remoteFileName The name of the file which will be created in the remote target directory. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @param mode a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) + throws IOException { + put(new String[]{localFile}, new String[]{remoteFileName}, remoteTargetDirectory, mode); + } + + /** + * Create a remote file and copy the contents of the passed byte array into it. + * Uses mode 0600 for creating the remote file. + * + * @param data the data to be copied into the remote file. + * @param remoteFileName The name of the file which will be created in the remote target directory. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @throws IOException + */ + + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException { + put(data, remoteFileName, remoteTargetDirectory, "0600"); + } + + /** + * Create a remote file and copy the contents of the passed byte array into it. + * The method use the specified mode when creating the file on the remote side. + * + * @param data the data to be copied into the remote file. + * @param remoteFileName The name of the file which will be created in the remote target directory. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @param mode a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException { + Session sess = null; + + if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + + String cmd = "scp -t -d " + remoteTargetDirectory; + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + sendBytes(sess, data, remoteFileName, mode); + } catch (IOException e) { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } finally { + if (sess != null) + sess.close(); + } + } + + /** + * Copy a set of local files to a remote directory, uses the specified mode + * when creating the files on the remote side. + * + * @param localFiles Paths and names of the local files. + * @param remoteTargetDirectory Remote target directory. Use an empty string to specify the default directory. + * @param mode a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException { + put(localFiles, null, remoteTargetDirectory, mode); + } + + public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) + throws IOException { + Session sess = null; /* remoteFiles may be null, indicating that the local filenames shall be used */ - if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null)) - throw new IllegalArgumentException("Null argument."); - - if (mode.length() != 4) - throw new IllegalArgumentException("Invalid mode."); - - for (int i = 0; i < mode.length(); i++) - if (Character.isDigit(mode.charAt(i)) == false) - throw new IllegalArgumentException("Invalid mode."); - - if (localFiles.length == 0) - return; - - remoteTargetDirectory = remoteTargetDirectory.trim(); - remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; - - String cmd = "scp -t -d " + remoteTargetDirectory; - - for (int i = 0; i < localFiles.length; i++) - { - if (localFiles[i] == null) - throw new IllegalArgumentException("Cannot accept null filename."); - } - - try - { - sess = conn.openSession(); - sess.execCommand(cmd); - sendFiles(sess, localFiles, remoteFiles, mode); - } - catch (IOException e) - { - throw (IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally - { - if (sess != null) - sess.close(); - } - } - - /** - * Download a file from the remote server to a local directory. - * - * @param remoteFile - * Path and name of the remote file. - * @param localTargetDirectory - * Local directory to put the downloaded file. - * - * @throws IOException - */ - public void get(String remoteFile, String localTargetDirectory) throws IOException - { - get(new String[] { remoteFile }, localTargetDirectory); - } - - /** - * Download a file from the remote server and pipe its contents into an OutputStream. - * Please note that, to enable flexible usage of this method, the OutputStream will not - * be closed nor flushed. - * - * @param remoteFile - * Path and name of the remote file. - * @param target - * OutputStream where the contents of the file will be sent to. - * @throws IOException - */ - public void get(String remoteFile, OutputStream target) throws IOException - { - get(new String[] { remoteFile }, new OutputStream[] { target }); - } - - private void get(String remoteFiles[], OutputStream[] targets) throws IOException - { - Session sess = null; - - if ((remoteFiles == null) || (targets == null)) - throw new IllegalArgumentException("Null argument."); - - if (remoteFiles.length != targets.length) - throw new IllegalArgumentException("Length of arguments does not match."); - - if (remoteFiles.length == 0) - return; - - String cmd = "scp -f"; - - for (int i = 0; i < remoteFiles.length; i++) - { - if (remoteFiles[i] == null) - throw new IllegalArgumentException("Cannot accept null filename."); - - String tmp = remoteFiles[i].trim(); - - if (tmp.length() == 0) - throw new IllegalArgumentException("Cannot accept empty filename."); - - cmd += (" " + tmp); - } - - try - { - sess = conn.openSession(); - sess.execCommand(cmd); - receiveFiles(sess, targets); - } - catch (IOException e) - { - throw (IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally - { - if (sess != null) - sess.close(); - } - } - - /** - * Download a set of files from the remote server to a local directory. - * - * @param remoteFiles - * Paths and names of the remote files. - * @param localTargetDirectory - * Local directory to put the downloaded files. - * - * @throws IOException - */ - public void get(String remoteFiles[], String localTargetDirectory) throws IOException - { - Session sess = null; - - if ((remoteFiles == null) || (localTargetDirectory == null)) - throw new IllegalArgumentException("Null argument."); - - if (remoteFiles.length == 0) - return; - - String cmd = "scp -f"; - - for (int i = 0; i < remoteFiles.length; i++) - { - if (remoteFiles[i] == null) - throw new IllegalArgumentException("Cannot accept null filename."); - - String tmp = remoteFiles[i].trim(); - - if (tmp.length() == 0) - throw new IllegalArgumentException("Cannot accept empty filename."); - - cmd += (" " + tmp); - } - - try - { - sess = conn.openSession(); - sess.execCommand(cmd); - receiveFiles(sess, remoteFiles, localTargetDirectory); - } - catch (IOException e) - { - throw (IOException) new IOException("Error during SCP transfer.").initCause(e); - } - finally - { - if (sess != null) - sess.close(); - } - } + if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + if (localFiles.length == 0) + return; + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + + String cmd = "scp -t -d " + remoteTargetDirectory; + + for (int i = 0; i < localFiles.length; i++) { + if (localFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + sendFiles(sess, localFiles, remoteFiles, mode); + } catch (IOException e) { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } finally { + if (sess != null) + sess.close(); + } + } + + /** + * Download a file from the remote server to a local directory. + * + * @param remoteFile Path and name of the remote file. + * @param localTargetDirectory Local directory to put the downloaded file. + * @throws IOException + */ + public void get(String remoteFile, String localTargetDirectory) throws IOException { + get(new String[]{remoteFile}, localTargetDirectory); + } + + /** + * Download a file from the remote server and pipe its contents into an OutputStream. + * Please note that, to enable flexible usage of this method, the OutputStream will not + * be closed nor flushed. + * + * @param remoteFile Path and name of the remote file. + * @param target OutputStream where the contents of the file will be sent to. + * @throws IOException + */ + public void get(String remoteFile, OutputStream target) throws IOException { + get(new String[]{remoteFile}, new OutputStream[]{target}); + } + + private void get(String remoteFiles[], OutputStream[] targets) throws IOException { + Session sess = null; + + if ((remoteFiles == null) || (targets == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length != targets.length) + throw new IllegalArgumentException("Length of arguments does not match."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, targets); + } catch (IOException e) { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } finally { + if (sess != null) + sess.close(); + } + } + + /** + * Download a set of files from the remote server to a local directory. + * + * @param remoteFiles Paths and names of the remote files. + * @param localTargetDirectory Local directory to put the downloaded files. + * @throws IOException + */ + public void get(String remoteFiles[], String localTargetDirectory) throws IOException { + Session sess = null; + + if ((remoteFiles == null) || (localTargetDirectory == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, remoteFiles, localTargetDirectory); + } catch (IOException e) { + throw (IOException) new IOException("Error during SCP transfer.").initCause(e); + } finally { + if (sess != null) + sess.close(); + } + } + + class LenNamePair { + long length; + String filename; + } } diff --git a/src/ch/ethz/ssh2/SFTPException.java b/src/ch/ethz/ssh2/SFTPException.java index 53b3adc..1ecfb6f 100644 --- a/src/ch/ethz/ssh2/SFTPException.java +++ b/src/ch/ethz/ssh2/SFTPException.java @@ -1,90 +1,82 @@ - package ch.ethz.ssh2; -import java.io.IOException; - import ch.ethz.ssh2.sftp.ErrorCodes; +import java.io.IOException; + /** * Used in combination with the SFTPv3Client. This exception wraps * error messages sent by the SFTP server. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SFTPException.java,v 1.6 2006/08/18 22:26:35 cplattne Exp $ */ -public class SFTPException extends IOException -{ - private static final long serialVersionUID = 578654644222421811L; - - private final String sftpErrorMessage; - private final int sftpErrorCode; - - private static String constructMessage(String s, int errorCode) - { - String[] detail = ErrorCodes.getDescription(errorCode); - - if (detail == null) - return s + " (UNKNOW SFTP ERROR CODE)"; - - return s + " (" + detail[0] + ": " + detail[1] + ")"; - } - - SFTPException(String msg, int errorCode) - { - super(constructMessage(msg, errorCode)); - sftpErrorMessage = msg; - sftpErrorCode = errorCode; - } - - /** - * Get the error message sent by the server. Often, this - * message does not help a lot (e.g., "failure"). - * - * @return the plain string as sent by the server. - */ - public String getServerErrorMessage() - { - return sftpErrorMessage; - } - - /** - * Get the error code sent by the server. - * - * @return an error code as defined in the SFTP specs. - */ - public int getServerErrorCode() - { - return sftpErrorCode; - } - - /** - * Get the symbolic name of the error code as given in the SFTP specs. - * - * @return e.g., "SSH_FX_INVALID_FILENAME". - */ - public String getServerErrorCodeSymbol() - { - String[] detail = ErrorCodes.getDescription(sftpErrorCode); - - if (detail == null) - return "UNKNOW SFTP ERROR CODE " + sftpErrorCode; - - return detail[0]; - } - - /** - * Get the description of the error code as given in the SFTP specs. - * - * @return e.g., "The filename is not valid." - */ - public String getServerErrorCodeVerbose() - { - String[] detail = ErrorCodes.getDescription(sftpErrorCode); - - if (detail == null) - return "The error code " + sftpErrorCode + " is unknown."; - - return detail[1]; - } +public class SFTPException extends IOException { + private static final long serialVersionUID = 578654644222421811L; + + private final String sftpErrorMessage; + private final int sftpErrorCode; + + SFTPException(String msg, int errorCode) { + super(constructMessage(msg, errorCode)); + sftpErrorMessage = msg; + sftpErrorCode = errorCode; + } + + private static String constructMessage(String s, int errorCode) { + String[] detail = ErrorCodes.getDescription(errorCode); + + if (detail == null) + return s + " (UNKNOW SFTP ERROR CODE)"; + + return s + " (" + detail[0] + ": " + detail[1] + ")"; + } + + /** + * Get the error message sent by the server. Often, this + * message does not help a lot (e.g., "failure"). + * + * @return the plain string as sent by the server. + */ + public String getServerErrorMessage() { + return sftpErrorMessage; + } + + /** + * Get the error code sent by the server. + * + * @return an error code as defined in the SFTP specs. + */ + public int getServerErrorCode() { + return sftpErrorCode; + } + + /** + * Get the symbolic name of the error code as given in the SFTP specs. + * + * @return e.g., "SSH_FX_INVALID_FILENAME". + */ + public String getServerErrorCodeSymbol() { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "UNKNOW SFTP ERROR CODE " + sftpErrorCode; + + return detail[0]; + } + + /** + * Get the description of the error code as given in the SFTP specs. + * + * @return e.g., "The filename is not valid." + */ + public String getServerErrorCodeVerbose() { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "The error code " + sftpErrorCode + " is unknown."; + + return detail[1]; + } } diff --git a/src/ch/ethz/ssh2/SFTPv3Client.java b/src/ch/ethz/ssh2/SFTPv3Client.java index 4a962aa..4ebfea1 100644 --- a/src/ch/ethz/ssh2/SFTPv3Client.java +++ b/src/ch/ethz/ssh2/SFTPv3Client.java @@ -1,21 +1,16 @@ - package ch.ethz.ssh2; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Vector; - import ch.ethz.ssh2.packets.TypesReader; import ch.ethz.ssh2.packets.TypesWriter; import ch.ethz.ssh2.sftp.AttribFlags; import ch.ethz.ssh2.sftp.ErrorCodes; import ch.ethz.ssh2.sftp.Packet; +import java.io.*; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Vector; + /** * A SFTPv3Client represents a SFTP (protocol version 3) * client connection tunnelled over a SSH-2 connection. This is a very simple @@ -30,7 +25,7 @@ * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will * be thrown (a subclass of IOException). Therefore, you can implement more verbose * behavior by checking if a thrown exception if of this type. If yes, then you - * can cast the exception and access detailed information about the failure. + * can cast the exception and access detailed information about the failure. *

    * Notes about file names, directory names and paths, copy-pasted * from the specs: @@ -53,227 +48,204 @@ *

    * If you are still not tired then please go on and read the comment for * {@link #setCharset(String)}. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SFTPv3Client.java,v 1.9 2006/09/20 12:51:37 cplattne Exp $ */ -public class SFTPv3Client -{ - final Connection conn; - final Session sess; - final PrintStream debug; - - boolean flag_closed = false; - - InputStream is; - OutputStream os; - - int protocol_version = 0; - HashMap server_extensions = new HashMap(); - - int next_request_id = 1000; - - String charsetName = null; - - /** - * Create a SFTP v3 client. - * - * @param conn The underlying SSH-2 connection to be used. - * @param debug - * @throws IOException - * - * @deprecated this constructor (debug version) will disappear in the future, - * use {@link #SFTPv3Client(Connection)} instead. - */ - public SFTPv3Client(Connection conn, PrintStream debug) throws IOException - { - if (conn == null) - throw new IllegalArgumentException("Cannot accept null argument!"); - - this.conn = conn; - this.debug = debug; - - if (debug != null) - debug.println("Opening session and starting SFTP subsystem."); - - sess = conn.openSession(); - sess.startSubSystem("sftp"); - - is = sess.getStdout(); - os = new BufferedOutputStream(sess.getStdin(), 2048); - - if ((is == null) || (os == null)) - throw new IOException("There is a problem with the streams of the underlying channel."); - - init(); - } - - /** - * Create a SFTP v3 client. - * - * @param conn The underlying SSH-2 connection to be used. - * @throws IOException - */ - public SFTPv3Client(Connection conn) throws IOException - { - this(conn, null); - } - - /** - * Set the charset used to convert between Java Unicode Strings and byte encodings - * used by the server for paths and file names. Unfortunately, the SFTP v3 draft - * says NOTHING about such conversions (well, with the exception of error messages - * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names - * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3 - * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with - * filenames containing german umlauts). "windows-1252" seems to work better for Europe. - * Luckily, "windows-1252" is the platform default in my case =). - *

    - * If you don't set anything, then the platform default will be used (this is the default - * behavior). - * - * @see #getCharset() - * - * @param charset the name of the charset to be used or null to use the platform's - * default encoding. - * @throws IOException - */ - public void setCharset(String charset) throws IOException - { - if (charset == null) - { - charsetName = charset; - return; - } - - try - { - Charset.forName(charset); - } - catch (Exception e) - { - throw (IOException) new IOException("This charset is not supported").initCause(e); - } - charsetName = charset; - } - - /** - * The currently used charset for filename encoding/decoding. - * - * @see #setCharset(String) - * - * @return The name of the charset (null if the platform's default charset is being used) - */ - public String getCharset() - { - return charsetName; - } - - private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException - { - if (handle.client != this) - throw new IOException("The file handle was created with another SFTPv3FileHandle instance."); - - if (handle.isClosed == true) - throw new IOException("The file handle is closed."); - } - - private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException - { - int msglen = len + 1; - - if (type != Packet.SSH_FXP_INIT) - msglen += 4; - - os.write(msglen >> 24); - os.write(msglen >> 16); - os.write(msglen >> 8); - os.write(msglen); - os.write(type); - - if (type != Packet.SSH_FXP_INIT) - { - os.write(requestId >> 24); - os.write(requestId >> 16); - os.write(requestId >> 8); - os.write(requestId); - } - - os.write(msg, off, len); - os.flush(); - } - - private final void sendMessage(int type, int requestId, byte[] msg) throws IOException - { - sendMessage(type, requestId, msg, 0, msg.length); - } - - private final void readBytes(byte[] buff, int pos, int len) throws IOException - { - while (len > 0) - { - int count = is.read(buff, pos, len); - if (count < 0) - throw new IOException("Unexpected end of sftp stream."); - if ((count == 0) || (count > len)) - throw new IOException("Underlying stream implementation is bogus!"); - len -= count; - pos += count; - } - } - - /** - * Read a message and guarantee that the contents is not larger than - * maxlen bytes. - *

    - * Note: receiveMessage(34000) actually means that the message may be up to 34004 - * bytes (the length attribute preceeding the contents is 4 bytes). - * - * @param maxlen - * @return the message contents - * @throws IOException - */ - private final byte[] receiveMessage(int maxlen) throws IOException - { - byte[] msglen = new byte[4]; - - readBytes(msglen, 0, 4); - - int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff)); - - if ((len > maxlen) || (len <= 0)) - throw new IOException("Illegal sftp packet len: " + len); - - byte[] msg = new byte[len]; - - readBytes(msg, 0, len); - - return msg; - } - - private final int generateNextRequestID() - { - synchronized (this) - { - return next_request_id++; - } - } - - private final void closeHandle(byte[] handle) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(handle, 0, handle.length); - - sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes()); - - expectStatusOKMessage(req_id); - } - - private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException - { - /* +public class SFTPv3Client { + final Connection conn; + final Session sess; + final PrintStream debug; + + boolean flag_closed = false; + + InputStream is; + OutputStream os; + + int protocol_version = 0; + HashMap server_extensions = new HashMap(); + + int next_request_id = 1000; + + String charsetName = null; + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @param debug + * @throws IOException + * @deprecated this constructor (debug version) will disappear in the future, + * use {@link #SFTPv3Client(Connection)} instead. + */ + public SFTPv3Client(Connection conn, PrintStream debug) throws IOException { + if (conn == null) + throw new IllegalArgumentException("Cannot accept null argument!"); + + this.conn = conn; + this.debug = debug; + + if (debug != null) + debug.println("Opening session and starting SFTP subsystem."); + + sess = conn.openSession(); + sess.startSubSystem("sftp"); + + is = sess.getStdout(); + os = new BufferedOutputStream(sess.getStdin(), 2048); + + if ((is == null) || (os == null)) + throw new IOException("There is a problem with the streams of the underlying channel."); + + init(); + } + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @throws IOException + */ + public SFTPv3Client(Connection conn) throws IOException { + this(conn, null); + } + + /** + * The currently used charset for filename encoding/decoding. + * + * @return The name of the charset (null if the platform's default charset is being used) + * @see #setCharset(String) + */ + public String getCharset() { + return charsetName; + } + + /** + * Set the charset used to convert between Java Unicode Strings and byte encodings + * used by the server for paths and file names. Unfortunately, the SFTP v3 draft + * says NOTHING about such conversions (well, with the exception of error messages + * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names + * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3 + * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with + * filenames containing german umlauts). "windows-1252" seems to work better for Europe. + * Luckily, "windows-1252" is the platform default in my case =). + *

    + * If you don't set anything, then the platform default will be used (this is the default + * behavior). + * + * @param charset the name of the charset to be used or null to use the platform's + * default encoding. + * @throws IOException + * @see #getCharset() + */ + public void setCharset(String charset) throws IOException { + if (charset == null) { + charsetName = charset; + return; + } + + try { + Charset.forName(charset); + } catch (Exception e) { + throw (IOException) new IOException("This charset is not supported").initCause(e); + } + charsetName = charset; + } + + private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException { + if (handle.client != this) + throw new IOException("The file handle was created with another SFTPv3FileHandle instance."); + + if (handle.isClosed == true) + throw new IOException("The file handle is closed."); + } + + private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException { + int msglen = len + 1; + + if (type != Packet.SSH_FXP_INIT) + msglen += 4; + + os.write(msglen >> 24); + os.write(msglen >> 16); + os.write(msglen >> 8); + os.write(msglen); + os.write(type); + + if (type != Packet.SSH_FXP_INIT) { + os.write(requestId >> 24); + os.write(requestId >> 16); + os.write(requestId >> 8); + os.write(requestId); + } + + os.write(msg, off, len); + os.flush(); + } + + private final void sendMessage(int type, int requestId, byte[] msg) throws IOException { + sendMessage(type, requestId, msg, 0, msg.length); + } + + private final void readBytes(byte[] buff, int pos, int len) throws IOException { + while (len > 0) { + int count = is.read(buff, pos, len); + if (count < 0) + throw new IOException("Unexpected end of sftp stream."); + if ((count == 0) || (count > len)) + throw new IOException("Underlying stream implementation is bogus!"); + len -= count; + pos += count; + } + } + + /** + * Read a message and guarantee that the contents is not larger than + * maxlen bytes. + *

    + * Note: receiveMessage(34000) actually means that the message may be up to 34004 + * bytes (the length attribute preceeding the contents is 4 bytes). + * + * @param maxlen + * @return the message contents + * @throws IOException + */ + private final byte[] receiveMessage(int maxlen) throws IOException { + byte[] msglen = new byte[4]; + + readBytes(msglen, 0, 4); + + int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff)); + + if ((len > maxlen) || (len <= 0)) + throw new IOException("Illegal sftp packet len: " + len); + + byte[] msg = new byte[len]; + + readBytes(msg, 0, len); + + return msg; + } + + private final int generateNextRequestID() { + synchronized (this) { + return next_request_id++; + } + } + + private final void closeHandle(byte[] handle) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + + sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException { + /* * uint32 flags * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID @@ -288,1098 +260,1007 @@ private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException * so that number of pairs equals extended_count */ - SFTPv3FileAttributes fa = new SFTPv3FileAttributes(); + SFTPv3FileAttributes fa = new SFTPv3FileAttributes(); - int flags = tr.readUINT32(); + int flags = tr.readUINT32(); - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) - { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_SIZE"); - fa.size = new Long(tr.readUINT64()); - } + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_SIZE"); + fa.size = new Long(tr.readUINT64()); + } - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) - { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_V3_UIDGID"); - fa.uid = new Integer(tr.readUINT32()); - fa.gid = new Integer(tr.readUINT32()); - } + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_V3_UIDGID"); + fa.uid = new Integer(tr.readUINT32()); + fa.gid = new Integer(tr.readUINT32()); + } - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) - { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_PERMISSIONS"); - fa.permissions = new Integer(tr.readUINT32()); - } + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_PERMISSIONS"); + fa.permissions = new Integer(tr.readUINT32()); + } - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) - { - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME"); - fa.atime = new Integer(tr.readUINT32()); - fa.mtime = new Integer(tr.readUINT32()); + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) { + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME"); + fa.atime = new Integer(tr.readUINT32()); + fa.mtime = new Integer(tr.readUINT32()); - } + } - if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) - { - int count = tr.readUINT32(); + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) { + int count = tr.readUINT32(); - if (debug != null) - debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")"); + if (debug != null) + debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")"); /* Read it anyway to detect corrupt packets */ - while (count > 0) - { - tr.readByteString(); - tr.readByteString(); - count--; - } - } + while (count > 0) { + tr.readByteString(); + tr.readByteString(); + count--; + } + } + + return fa; + } + + /** + * Retrieve the file attributes of an open file. + * + * @param handle a SFTPv3FileHandle handle. + * @return a SFTPv3FileAttributes object. + * @throws IOException + */ + public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException { + checkHandleValidAndOpen(handle); + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + + if (debug != null) { + debug.println("Sending SSH_FXP_FSTAT..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + if (debug != null) { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_ATTRS) { + return readAttrs(tr); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + + if (debug != null) { + debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT..."); + debug.flush(); + } + + sendMessage(statMethod, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + if (debug != null) { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_ATTRS) { + return readAttrs(tr); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + /** + * Retrieve the file attributes of a file. This method + * follows symbolic links on the server. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + * @see #lstat(String) + */ + public SFTPv3FileAttributes stat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_STAT); + } - return fa; - } + /** + * Retrieve the file attributes of a file. This method + * does NOT follow symbolic links on the server. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + * @see #stat(String) + */ + public SFTPv3FileAttributes lstat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_LSTAT); + } - /** - * Retrieve the file attributes of an open file. - * - * @param handle a SFTPv3FileHandle handle. - * @return a SFTPv3FileAttributes object. - * @throws IOException - */ - public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException - { - checkHandleValidAndOpen(handle); + /** + * Read the target of a symbolic link. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return The target of the link. + * @throws IOException + */ + public String readLink(String path) throws IOException { + int req_id = generateNextRequestID(); - int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - - if (debug != null) - { - debug.println("Sending SSH_FXP_FSTAT..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); - - byte[] resp = receiveMessage(34000); - - if (debug != null) - { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - - int t = tr.readByte(); - - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_ATTRS) - { - return readAttrs(tr); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - throw new SFTPException(tr.readString(), errorCode); - } - - private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - - if (debug != null) - { - debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT..."); - debug.flush(); - } - - sendMessage(statMethod, req_id, tw.getBytes()); - - byte[] resp = receiveMessage(34000); - - if (debug != null) - { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - - int t = tr.readByte(); - - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_ATTRS) - { - return readAttrs(tr); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - throw new SFTPException(tr.readString(), errorCode); - } + if (debug != null) { + debug.println("Sending SSH_FXP_READLINK..."); + debug.flush(); + } - /** - * Retrieve the file attributes of a file. This method - * follows symbolic links on the server. - * - * @see #lstat(String) - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileAttributes object. - * @throws IOException - */ - public SFTPv3FileAttributes stat(String path) throws IOException - { - return statBoth(path, Packet.SSH_FXP_STAT); - } + sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes()); - /** - * Retrieve the file attributes of a file. This method - * does NOT follow symbolic links on the server. - * - * @see #stat(String) - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileAttributes object. - * @throws IOException - */ - public SFTPv3FileAttributes lstat(String path) throws IOException - { - return statBoth(path, Packet.SSH_FXP_LSTAT); - } - - /** - * Read the target of a symbolic link. - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return The target of the link. - * @throws IOException - */ - public String readLink(String path) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - - if (debug != null) - { - debug.println("Sending SSH_FXP_READLINK..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes()); - - byte[] resp = receiveMessage(34000); - - if (debug != null) - { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - - int t = tr.readByte(); - - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_NAME) - { - int count = tr.readUINT32(); - - if (count != 1) - throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); - - return tr.readString(charsetName); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - throw new SFTPException(tr.readString(), errorCode); - } - - private void expectStatusOKMessage(int id) throws IOException - { - byte[] resp = receiveMessage(34000); - - if (debug != null) - { - debug.println("Got REPLY."); - debug.flush(); - } - - TypesReader tr = new TypesReader(resp); - - int t = tr.readByte(); - - int rep_id = tr.readUINT32(); - if (rep_id != id) - throw new IOException("The server sent an invalid id field."); - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_OK) - return; - - throw new SFTPException(tr.readString(), errorCode); - } - - /** - * Modify the attributes of a file. Used for operations such as changing - * the ownership, permissions or access times, as well as for truncating a file. - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be - * made to the attributes of the file. Empty fields will be ignored. - * @throws IOException - */ - public void setstat(String path, SFTPv3FileAttributes attr) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); - tw.writeBytes(createAttrs(attr)); - - if (debug != null) - { - debug.println("Sending SSH_FXP_SETSTAT..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); - expectStatusOKMessage(req_id); - } + if (debug != null) { + debug.println("Got REPLY."); + debug.flush(); + } - /** - * Modify the attributes of a file. Used for operations such as changing - * the ownership, permissions or access times, as well as for truncating a file. - * - * @param handle a SFTPv3FileHandle handle - * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be - * made to the attributes of the file. Empty fields will be ignored. - * @throws IOException - */ - public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException - { - checkHandleValidAndOpen(handle); - - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - tw.writeBytes(createAttrs(attr)); - - if (debug != null) - { - debug.println("Sending SSH_FXP_FSETSTAT..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes()); - - expectStatusOKMessage(req_id); - } - - /** - * Create a symbolic link on the server. Creates a link "src" that points - * to "target". - * - * @param src See the {@link SFTPv3Client comment} for the class for more details. - * @param target See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void createSymlink(String src, String target) throws IOException - { - int req_id = generateNextRequestID(); + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (count != 1) + throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); + + return tr.readString(charsetName); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + throw new SFTPException(tr.readString(), errorCode); + } + + private void expectStatusOKMessage(int id) throws IOException { + byte[] resp = receiveMessage(34000); + + if (debug != null) { + debug.println("Got REPLY."); + debug.flush(); + } + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != id) + throw new IOException("The server sent an invalid id field."); + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_OK) + return; + + throw new SFTPException(tr.readString(), errorCode); + } + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + public void setstat(String path, SFTPv3FileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); + tw.writeBytes(createAttrs(attr)); + + if (debug != null) { + debug.println("Sending SSH_FXP_SETSTAT..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param handle a SFTPv3FileHandle handle + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException { + checkHandleValidAndOpen(handle); + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + tw.writeBytes(createAttrs(attr)); + + if (debug != null) { + debug.println("Sending SSH_FXP_FSETSTAT..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Create a symbolic link on the server. Creates a link "src" that points + * to "target". + * + * @param src See the {@link SFTPv3Client comment} for the class for more details. + * @param target See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); /* Either I am too stupid to understand the SFTP draft * or the OpenSSH guys changed the semantics of src and target. */ - TypesWriter tw = new TypesWriter(); - tw.writeString(target, charsetName); - tw.writeString(src, charsetName); + TypesWriter tw = new TypesWriter(); + tw.writeString(target, charsetName); + tw.writeString(src, charsetName); - if (debug != null) - { - debug.println("Sending SSH_FXP_SYMLINK..."); - debug.flush(); - } + if (debug != null) { + debug.println("Sending SSH_FXP_SYMLINK..."); + debug.flush(); + } - sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); + sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); - expectStatusOKMessage(req_id); - } + expectStatusOKMessage(req_id); + } - /** - * Have the server canonicalize any given path name to an absolute path. - * This is useful for converting path names containing ".." components or - * relative pathnames without a leading slash into absolute paths. - * - * @param path See the {@link SFTPv3Client comment} for the class for more details. - * @return An absolute path. - * @throws IOException - */ - public String canonicalPath(String path) throws IOException - { - int req_id = generateNextRequestID(); + /** + * Have the server canonicalize any given path name to an absolute path. + * This is useful for converting path names containing ".." components or + * relative pathnames without a leading slash into absolute paths. + * + * @param path See the {@link SFTPv3Client comment} for the class for more details. + * @return An absolute path. + * @throws IOException + */ + public String canonicalPath(String path) throws IOException { + int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); - if (debug != null) - { - debug.println("Sending SSH_FXP_REALPATH..."); - debug.flush(); - } + if (debug != null) { + debug.println("Sending SSH_FXP_REALPATH..."); + debug.flush(); + } - sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes()); + sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); + byte[] resp = receiveMessage(34000); - if (debug != null) - { - debug.println("Got REPLY."); - debug.flush(); - } + if (debug != null) { + debug.println("Got REPLY."); + debug.flush(); + } - TypesReader tr = new TypesReader(resp); + TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); + int t = tr.readByte(); - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); - if (t == Packet.SSH_FXP_NAME) - { - int count = tr.readUINT32(); + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); - if (count != 1) - throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); + if (count != 1) + throw new IOException("The server sent an invalid SSH_FXP_NAME packet."); - return tr.readString(charsetName); - } + return tr.readString(charsetName); + } - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - int errorCode = tr.readUINT32(); + int errorCode = tr.readUINT32(); - throw new SFTPException(tr.readString(), errorCode); - } + throw new SFTPException(tr.readString(), errorCode); + } - private final Vector scanDirectory(byte[] handle) throws IOException - { - Vector files = new Vector(); + private final Vector scanDirectory(byte[] handle) throws IOException { + Vector files = new Vector(); - while (true) - { - int req_id = generateNextRequestID(); + while (true) { + int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(handle, 0, handle.length); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); - if (debug != null) - { - debug.println("Sending SSH_FXP_READDIR..."); - debug.flush(); - } + if (debug != null) { + debug.println("Sending SSH_FXP_READDIR..."); + debug.flush(); + } - sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); + sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); + byte[] resp = receiveMessage(34000); - if (debug != null) - { - debug.println("Got REPLY."); - debug.flush(); - } + if (debug != null) { + debug.println("Got REPLY."); + debug.flush(); + } - TypesReader tr = new TypesReader(resp); + TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); + int t = tr.readByte(); - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); - if (t == Packet.SSH_FXP_NAME) - { - int count = tr.readUINT32(); + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); - if (debug != null) - debug.println("Parsing " + count + " name entries..."); + if (debug != null) + debug.println("Parsing " + count + " name entries..."); - while (count > 0) - { - SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry(); + while (count > 0) { + SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry(); - dirEnt.filename = tr.readString(charsetName); - dirEnt.longEntry = tr.readString(charsetName); + dirEnt.filename = tr.readString(charsetName); + dirEnt.longEntry = tr.readString(charsetName); - dirEnt.attributes = readAttrs(tr); - files.addElement(dirEnt); + dirEnt.attributes = readAttrs(tr); + files.addElement(dirEnt); - if (debug != null) - debug.println("File: '" + dirEnt.filename + "'"); - count--; - } - continue; - } + if (debug != null) + debug.println("File: '" + dirEnt.filename + "'"); + count--; + } + continue; + } - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - int errorCode = tr.readUINT32(); + int errorCode = tr.readUINT32(); - if (errorCode == ErrorCodes.SSH_FX_EOF) - return files; + if (errorCode == ErrorCodes.SSH_FX_EOF) + return files; - throw new SFTPException(tr.readString(), errorCode); - } - } + throw new SFTPException(tr.readString(), errorCode); + } + } - private final byte[] openDirectory(String path) throws IOException - { - int req_id = generateNextRequestID(); + private final byte[] openDirectory(String path) throws IOException { + int req_id = generateNextRequestID(); - TypesWriter tw = new TypesWriter(); - tw.writeString(path, charsetName); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charsetName); - if (debug != null) - { - debug.println("Sending SSH_FXP_OPENDIR..."); - debug.flush(); - } + if (debug != null) { + debug.println("Sending SSH_FXP_OPENDIR..."); + debug.flush(); + } - sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); + sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); - byte[] resp = receiveMessage(34000); + byte[] resp = receiveMessage(34000); - TypesReader tr = new TypesReader(resp); + TypesReader tr = new TypesReader(resp); - int t = tr.readByte(); + int t = tr.readByte(); - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); - if (t == Packet.SSH_FXP_HANDLE) - { - if (debug != null) - { - debug.println("Got SSH_FXP_HANDLE."); - debug.flush(); - } + if (t == Packet.SSH_FXP_HANDLE) { + if (debug != null) { + debug.println("Got SSH_FXP_HANDLE."); + debug.flush(); + } - byte[] handle = tr.readByteString(); - return handle; - } + byte[] handle = tr.readByteString(); + return handle; + } - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - int errorCode = tr.readUINT32(); - String errorMessage = tr.readString(); + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); - throw new SFTPException(errorMessage, errorCode); - } + throw new SFTPException(errorMessage, errorCode); + } - private final String expandString(byte[] b, int off, int len) - { - StringBuffer sb = new StringBuffer(); + private final String expandString(byte[] b, int off, int len) { + StringBuffer sb = new StringBuffer(); - for (int i = 0; i < len; i++) - { - int c = b[off + i] & 0xff; + for (int i = 0; i < len; i++) { + int c = b[off + i] & 0xff; - if ((c >= 32) && (c <= 126)) - { - sb.append((char) c); - } - else - { - sb.append("{0x" + Integer.toHexString(c) + "}"); - } - } + if ((c >= 32) && (c <= 126)) { + sb.append((char) c); + } else { + sb.append("{0x" + Integer.toHexString(c) + "}"); + } + } - return sb.toString(); - } + return sb.toString(); + } - private void init() throws IOException - { + private void init() throws IOException { /* Send SSH_FXP_INIT (version 3) */ - final int client_version = 3; + final int client_version = 3; - if (debug != null) - debug.println("Sending SSH_FXP_INIT (" + client_version + ")..."); + if (debug != null) + debug.println("Sending SSH_FXP_INIT (" + client_version + ")..."); - TypesWriter tw = new TypesWriter(); - tw.writeUINT32(client_version); - sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes()); + TypesWriter tw = new TypesWriter(); + tw.writeUINT32(client_version); + sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes()); /* Receive SSH_FXP_VERSION */ - if (debug != null) - debug.println("Waiting for SSH_FXP_VERSION..."); + if (debug != null) + debug.println("Waiting for SSH_FXP_VERSION..."); - TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */ + TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */ - int type = tr.readByte(); + int type = tr.readByte(); - if (type != Packet.SSH_FXP_VERSION) - { - throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")"); - } + if (type != Packet.SSH_FXP_VERSION) { + throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")"); + } - protocol_version = tr.readUINT32(); + protocol_version = tr.readUINT32(); - if (debug != null) - debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version); + if (debug != null) + debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version); - if (protocol_version != 3) - throw new IOException("Server version " + protocol_version + " is currently not supported"); + if (protocol_version != 3) + throw new IOException("Server version " + protocol_version + " is currently not supported"); /* Read and save extensions (if any) for later use */ - while (tr.remain() != 0) - { - String name = tr.readString(); - byte[] value = tr.readByteString(); - server_extensions.put(name, value); - - if (debug != null) - debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length) - + "'"); - } - } - - /** - * Returns the negotiated SFTP protocol version between the client and the server. - * - * @return SFTP protocol version, i.e., "3". - * - */ - public int getProtocolVersion() - { - return protocol_version; - } - - /** - * Close this SFTP session. NEVER forget to call this method to free up - * resources - even if you got an exception from one of the other methods. - * Sometimes these other methods may throw an exception, saying that the - * underlying channel is closed (this can happen, e.g., if the other server - * sent a close message.) However, as long as you have not called the - * close() method, you are likely wasting resources. - * - */ - public void close() - { - sess.close(); - } - - /** - * List the contents of a directory. - * - * @param dirName See the {@link SFTPv3Client comment} for the class for more details. - * @return A Vector containing {@link SFTPv3DirectoryEntry} objects. - * @throws IOException - */ - public Vector ls(String dirName) throws IOException - { - byte[] handle = openDirectory(dirName); - Vector result = scanDirectory(handle); - closeHandle(handle); - return result; - } - - /** - * Create a new directory. - * - * @param dirName See the {@link SFTPv3Client comment} for the class for more details. - * @param posixPermissions the permissions for this directory, e.g., "0700". The server - * will likely apply a umask. - * @throws IOException - */ - public void mkdir(String dirName, int posixPermissions) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(dirName, charsetName); - tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS); - tw.writeUINT32(posixPermissions); - - sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes()); - - expectStatusOKMessage(req_id); - } - - /** - * Remove a file. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void rm(String fileName) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(fileName, charsetName); - - sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes()); - - expectStatusOKMessage(req_id); - } - - /** - * Remove an empty directory. - * - * @param dirName See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void rmdir(String dirName) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(dirName, charsetName); - - sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes()); - - expectStatusOKMessage(req_id); - } - - /** - * Move a file or directory. - * - * @param oldPath See the {@link SFTPv3Client comment} for the class for more details. - * @param newPath See the {@link SFTPv3Client comment} for the class for more details. - * @throws IOException - */ - public void mv(String oldPath, String newPath) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(oldPath, charsetName); - tw.writeString(newPath, charsetName); - - sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes()); - - expectStatusOKMessage(req_id); - } - - /** - * Open a file for reading. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle openFileRO(String fileName) throws IOException - { - return openFile(fileName, 0x00000001, null); // SSH_FXF_READ - } - - /** - * Open a file for reading and writing. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle openFileRW(String fileName) throws IOException - { - return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE - } - - // Append is broken (already in the specification, because there is no way to - // send a write operation (what offset to use??)) - // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException - // { - // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND - // } - - /** - * Create a file and open it for reading and writing. - * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFile(String fileName) throws IOException - { - return createFile(fileName, null); - } - - /** - * Create a file and open it for reading and writing. - * You can specify the default attributes of the file (the server may or may - * not respect your wishes). - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @param attr may be null to use server defaults. Probably only - * the uid, gid and permissions - * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} - * structure make sense. You need only to set those fields where you want - * to override the server's defaults. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException - { - return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE - } - - /** - * Create a file (truncate it if it already exists) and open it for reading and writing. - * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}. - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException - { - return createFileTruncate(fileName, null); - } - - /** - * reate a file (truncate it if it already exists) and open it for reading and writing. - * You can specify the default attributes of the file (the server may or may - * not respect your wishes). - * - * @param fileName See the {@link SFTPv3Client comment} for the class for more details. - * @param attr may be null to use server defaults. Probably only - * the uid, gid and permissions - * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} - * structure make sense. You need only to set those fields where you want - * to override the server's defaults. - * @return a SFTPv3FileHandle handle - * @throws IOException - */ - public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException - { - return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE - } - - private byte[] createAttrs(SFTPv3FileAttributes attr) - { - TypesWriter tw = new TypesWriter(); - - int attrFlags = 0; - - if (attr == null) - { - tw.writeUINT32(0); - } - else - { - if (attr.size != null) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; - - if ((attr.uid != null) && (attr.gid != null)) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID; - - if (attr.permissions != null) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; - - if ((attr.atime != null) && (attr.mtime != null)) - attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME; - - tw.writeUINT32(attrFlags); - - if (attr.size != null) - tw.writeUINT64(attr.size.longValue()); - - if ((attr.uid != null) && (attr.gid != null)) - { - tw.writeUINT32(attr.uid.intValue()); - tw.writeUINT32(attr.gid.intValue()); - } - - if (attr.permissions != null) - tw.writeUINT32(attr.permissions.intValue()); - - if ((attr.atime != null) && (attr.mtime != null)) - { - tw.writeUINT32(attr.atime.intValue()); - tw.writeUINT32(attr.mtime.intValue()); - } - } - - return tw.getBytes(); - } - - private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException - { - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(fileName, charsetName); - tw.writeUINT32(flags); - tw.writeBytes(createAttrs(attr)); - - if (debug != null) - { - debug.println("Sending SSH_FXP_OPEN..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); - - byte[] resp = receiveMessage(34000); - - TypesReader tr = new TypesReader(resp); - - int t = tr.readByte(); - - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); - - if (t == Packet.SSH_FXP_HANDLE) - { - if (debug != null) - { - debug.println("Got SSH_FXP_HANDLE."); - debug.flush(); - } - - return new SFTPv3FileHandle(this, tr.readByteString()); - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - String errorMessage = tr.readString(); - - throw new SFTPException(errorMessage, errorCode); - } - - /** - * Read bytes from a file. No more than 32768 bytes may be read at once. - * Be aware that the semantics of read() are different than for Java streams. - *

    - *

      - *
    • The server will read as many bytes as it can from the file (up to len), - * and return them.
    • - *
    • If EOF is encountered before reading any data, -1 is returned. - *
    • If an error occurs, an exception is thrown
    • . - *
    • For normal disk files, it is guaranteed that the server will return the specified - * number of bytes, or up to end of file. For, e.g., device files this may return - * fewer bytes than requested.
    • - *
    - * - * @param handle a SFTPv3FileHandle handle - * @param fileOffset offset (in bytes) in the file - * @param dst the destination byte array - * @param dstoff offset in the destination byte array - * @param len how many bytes to read, 0 < len <= 32768 bytes - * @return the number of bytes that could be read, may be less than requested if - * the end of the file is reached, -1 is returned in case of EOF - * @throws IOException - */ - public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException - { - checkHandleValidAndOpen(handle); - - if ((len > 32768) || (len <= 0)) - throw new IllegalArgumentException("invalid len argument"); - - int req_id = generateNextRequestID(); - - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - tw.writeUINT64(fileOffset); - tw.writeUINT32(len); - - if (debug != null) - { - debug.println("Sending SSH_FXP_READ..."); - debug.flush(); - } - - sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes()); - - byte[] resp = receiveMessage(34000); - - TypesReader tr = new TypesReader(resp); + while (tr.remain() != 0) { + String name = tr.readString(); + byte[] value = tr.readByteString(); + server_extensions.put(name, value); + + if (debug != null) + debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length) + + "'"); + } + } + + /** + * Returns the negotiated SFTP protocol version between the client and the server. + * + * @return SFTP protocol version, i.e., "3". + */ + public int getProtocolVersion() { + return protocol_version; + } + + /** + * Close this SFTP session. NEVER forget to call this method to free up + * resources - even if you got an exception from one of the other methods. + * Sometimes these other methods may throw an exception, saying that the + * underlying channel is closed (this can happen, e.g., if the other server + * sent a close message.) However, as long as you have not called the + * close() method, you are likely wasting resources. + */ + public void close() { + sess.close(); + } + + /** + * List the contents of a directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @return A Vector containing {@link SFTPv3DirectoryEntry} objects. + * @throws IOException + */ + public Vector ls(String dirName) throws IOException { + byte[] handle = openDirectory(dirName); + Vector result = scanDirectory(handle); + closeHandle(handle); + return result; + } + + /** + * Create a new directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @param posixPermissions the permissions for this directory, e.g., "0700". The server + * will likely apply a umask. + * @throws IOException + */ + public void mkdir(String dirName, int posixPermissions) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, charsetName); + tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS); + tw.writeUINT32(posixPermissions); + + sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Remove a file. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void rm(String fileName) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(fileName, charsetName); + + sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Remove an empty directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void rmdir(String dirName) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, charsetName); + + sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Move a file or directory. + * + * @param oldPath See the {@link SFTPv3Client comment} for the class for more details. + * @param newPath See the {@link SFTPv3Client comment} for the class for more details. + * @throws IOException + */ + public void mv(String oldPath, String newPath) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(oldPath, charsetName); + tw.writeString(newPath, charsetName); + + sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes()); + + expectStatusOKMessage(req_id); + } + + /** + * Open a file for reading. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRO(String fileName) throws IOException { + return openFile(fileName, 0x00000001, null); // SSH_FXF_READ + } + + /** + * Open a file for reading and writing. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRW(String fileName) throws IOException { + return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE + } + + // Append is broken (already in the specification, because there is no way to + // send a write operation (what offset to use??)) + // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException + // { + // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND + // } + + /** + * Create a file and open it for reading and writing. + * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFile(String fileName) throws IOException { + return createFile(fileName, null); + } + + /** + * Create a file and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException { + return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE + } + + /** + * Create a file (truncate it if it already exists) and open it for reading and writing. + * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}. + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException { + return createFileTruncate(fileName, null); + } + + /** + * reate a file (truncate it if it already exists) and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param fileName See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException { + return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE + } + + private byte[] createAttrs(SFTPv3FileAttributes attr) { + TypesWriter tw = new TypesWriter(); + + int attrFlags = 0; + + if (attr == null) { + tw.writeUINT32(0); + } else { + if (attr.size != null) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; + + if ((attr.uid != null) && (attr.gid != null)) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID; + + if (attr.permissions != null) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; + + if ((attr.atime != null) && (attr.mtime != null)) + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME; + + tw.writeUINT32(attrFlags); + + if (attr.size != null) + tw.writeUINT64(attr.size.longValue()); + + if ((attr.uid != null) && (attr.gid != null)) { + tw.writeUINT32(attr.uid.intValue()); + tw.writeUINT32(attr.gid.intValue()); + } + + if (attr.permissions != null) + tw.writeUINT32(attr.permissions.intValue()); + + if ((attr.atime != null) && (attr.mtime != null)) { + tw.writeUINT32(attr.atime.intValue()); + tw.writeUINT32(attr.mtime.intValue()); + } + } + + return tw.getBytes(); + } + + private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(fileName, charsetName); + tw.writeUINT32(flags); + tw.writeBytes(createAttrs(attr)); + + if (debug != null) { + debug.println("Sending SSH_FXP_OPEN..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_HANDLE) { + if (debug != null) { + debug.println("Got SSH_FXP_HANDLE."); + debug.flush(); + } + + return new SFTPv3FileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + + throw new SFTPException(errorMessage, errorCode); + } + + /** + * Read bytes from a file. No more than 32768 bytes may be read at once. + * Be aware that the semantics of read() are different than for Java streams. + *

    + *

      + *
    • The server will read as many bytes as it can from the file (up to len), + * and return them.
    • + *
    • If EOF is encountered before reading any data, -1 is returned. + *
    • If an error occurs, an exception is thrown
    • . + *
    • For normal disk files, it is guaranteed that the server will return the specified + * number of bytes, or up to end of file. For, e.g., device files this may return + * fewer bytes than requested.
    • + *
    + * + * @param handle a SFTPv3FileHandle handle + * @param fileOffset offset (in bytes) in the file + * @param dst the destination byte array + * @param dstoff offset in the destination byte array + * @param len how many bytes to read, 0 < len <= 32768 bytes + * @return the number of bytes that could be read, may be less than requested if + * the end of the file is reached, -1 is returned in case of EOF + * @throws IOException + */ + public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException { + checkHandleValidAndOpen(handle); + + if ((len > 32768) || (len <= 0)) + throw new IllegalArgumentException("invalid len argument"); + + int req_id = generateNextRequestID(); + + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + tw.writeUINT64(fileOffset); + tw.writeUINT32(len); + + if (debug != null) { + debug.println("Sending SSH_FXP_READ..."); + debug.flush(); + } + + sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes()); + + byte[] resp = receiveMessage(34000); + + TypesReader tr = new TypesReader(resp); + + int t = tr.readByte(); + + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); + + if (t == Packet.SSH_FXP_DATA) { + if (debug != null) { + debug.println("Got SSH_FXP_DATA..."); + debug.flush(); + } + + int readLen = tr.readUINT32(); + + if ((readLen < 0) || (readLen > len)) + throw new IOException("The server sent an invalid length field."); + + tr.readBytes(dst, dstoff, readLen); + + return readLen; + } + + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) { + if (debug != null) { + debug.println("Got SSH_FX_EOF."); + debug.flush(); + } + + return -1; + } + + String errorMessage = tr.readString(); + + throw new SFTPException(errorMessage, errorCode); + } - int t = tr.readByte(); + /** + * Write bytes to a file. If len > 32768, then the write operation will + * be split into multiple writes. + * + * @param handle a SFTPv3FileHandle handle. + * @param fileOffset offset (in bytes) in the file. + * @param src the source byte array. + * @param srcoff offset in the source byte array. + * @param len how many bytes to write. + * @throws IOException + */ + public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException { + checkHandleValidAndOpen(handle); + + if (len < 0) + + while (len > 0) { + int writeRequestLen = len; - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); + if (writeRequestLen > 32768) + writeRequestLen = 32768; - if (t == Packet.SSH_FXP_DATA) - { - if (debug != null) - { - debug.println("Got SSH_FXP_DATA..."); - debug.flush(); - } + int req_id = generateNextRequestID(); - int readLen = tr.readUINT32(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); + tw.writeUINT64(fileOffset); + tw.writeString(src, srcoff, writeRequestLen); - if ((readLen < 0) || (readLen > len)) - throw new IOException("The server sent an invalid length field."); + if (debug != null) { + debug.println("Sending SSH_FXP_WRITE..."); + debug.flush(); + } - tr.readBytes(dst, dstoff, readLen); + sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes()); - return readLen; - } - - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_EOF) - { - if (debug != null) - { - debug.println("Got SSH_FX_EOF."); - debug.flush(); - } - - return -1; - } - - String errorMessage = tr.readString(); - - throw new SFTPException(errorMessage, errorCode); - } - - /** - * Write bytes to a file. If len > 32768, then the write operation will - * be split into multiple writes. - * - * @param handle a SFTPv3FileHandle handle. - * @param fileOffset offset (in bytes) in the file. - * @param src the source byte array. - * @param srcoff offset in the source byte array. - * @param len how many bytes to write. - * @throws IOException - */ - public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException - { - checkHandleValidAndOpen(handle); - - if (len < 0) - - while (len > 0) - { - int writeRequestLen = len; - - if (writeRequestLen > 32768) - writeRequestLen = 32768; - - int req_id = generateNextRequestID(); + fileOffset += writeRequestLen; - TypesWriter tw = new TypesWriter(); - tw.writeString(handle.fileHandle, 0, handle.fileHandle.length); - tw.writeUINT64(fileOffset); - tw.writeString(src, srcoff, writeRequestLen); + srcoff += writeRequestLen; + len -= writeRequestLen; - if (debug != null) - { - debug.println("Sending SSH_FXP_WRITE..."); - debug.flush(); - } + byte[] resp = receiveMessage(34000); - sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes()); + TypesReader tr = new TypesReader(resp); - fileOffset += writeRequestLen; + int t = tr.readByte(); - srcoff += writeRequestLen; - len -= writeRequestLen; + int rep_id = tr.readUINT32(); + if (rep_id != req_id) + throw new IOException("The server sent an invalid id field."); - byte[] resp = receiveMessage(34000); + if (t != Packet.SSH_FXP_STATUS) + throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); - TypesReader tr = new TypesReader(resp); + int errorCode = tr.readUINT32(); - int t = tr.readByte(); + if (errorCode == ErrorCodes.SSH_FX_OK) + continue; - int rep_id = tr.readUINT32(); - if (rep_id != req_id) - throw new IOException("The server sent an invalid id field."); + String errorMessage = tr.readString(); - if (t != Packet.SSH_FXP_STATUS) - throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")"); + throw new SFTPException(errorMessage, errorCode); + } + } - int errorCode = tr.readUINT32(); - - if (errorCode == ErrorCodes.SSH_FX_OK) - continue; - - String errorMessage = tr.readString(); - - throw new SFTPException(errorMessage, errorCode); - } - } + /** + * Close a file. + * + * @param handle a SFTPv3FileHandle handle + * @throws IOException + */ + public void closeFile(SFTPv3FileHandle handle) throws IOException { + if (handle == null) + throw new IllegalArgumentException("the handle argument may not be null"); - /** - * Close a file. - * - * @param handle a SFTPv3FileHandle handle - * @throws IOException - */ - public void closeFile(SFTPv3FileHandle handle) throws IOException - { - if (handle == null) - throw new IllegalArgumentException("the handle argument may not be null"); - - try - { - if (handle.isClosed == false) - { - closeHandle(handle.fileHandle); - } - } - finally - { - handle.isClosed = true; - } - } + try { + if (handle.isClosed == false) { + closeHandle(handle.fileHandle); + } + } finally { + handle.isClosed = true; + } + } } diff --git a/src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java b/src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java index 3180c5a..9209ada 100644 --- a/src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java +++ b/src/ch/ethz/ssh2/SFTPv3DirectoryEntry.java @@ -1,38 +1,36 @@ - package ch.ethz.ssh2; /** * A SFTPv3DirectoryEntry as returned by {@link SFTPv3Client#ls(String)}. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SFTPv3DirectoryEntry.java,v 1.2 2006/08/18 22:26:35 cplattne Exp $ */ -public class SFTPv3DirectoryEntry -{ - /** - * A relative name within the directory, without any path components. - */ - public String filename; +public class SFTPv3DirectoryEntry { + /** + * A relative name within the directory, without any path components. + */ + public String filename; - /** - * An expanded format for the file name, similar to what is returned by - * "ls -l" on Un*x systems. - *

    - * The format of this field is unspecified by the SFTP v3 protocol. - * It MUST be suitable for use in the output of a directory listing - * command (in fact, the recommended operation for a directory listing - * command is to simply display this data). However, clients SHOULD NOT - * attempt to parse the longname field for file attributes; they SHOULD - * use the attrs field instead. - *

    - * The recommended format for the longname field is as follows:
    - * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer - */ - public String longEntry; + /** + * An expanded format for the file name, similar to what is returned by + * "ls -l" on Un*x systems. + *

    + * The format of this field is unspecified by the SFTP v3 protocol. + * It MUST be suitable for use in the output of a directory listing + * command (in fact, the recommended operation for a directory listing + * command is to simply display this data). However, clients SHOULD NOT + * attempt to parse the longname field for file attributes; they SHOULD + * use the attrs field instead. + *

    + * The recommended format for the longname field is as follows:
    + * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer + */ + public String longEntry; - /** - * The attributes of this entry. - */ - public SFTPv3FileAttributes attributes; + /** + * The attributes of this entry. + */ + public SFTPv3FileAttributes attributes; } diff --git a/src/ch/ethz/ssh2/SFTPv3FileAttributes.java b/src/ch/ethz/ssh2/SFTPv3FileAttributes.java index a30a845..42bff51 100644 --- a/src/ch/ethz/ssh2/SFTPv3FileAttributes.java +++ b/src/ch/ethz/ssh2/SFTPv3FileAttributes.java @@ -1,145 +1,138 @@ - package ch.ethz.ssh2; /** * A SFTPv3FileAttributes object represents detail information * about a file on the server. Not all fields may/must be present. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SFTPv3FileAttributes.java,v 1.3 2006/09/04 07:41:46 cplattne Exp $ */ -public class SFTPv3FileAttributes -{ - /** - * The SIZE attribute. NULL if not present. - */ - public Long size = null; - - /** - * The UID attribute. NULL if not present. - */ - public Integer uid = null; - - /** - * The GID attribute. NULL if not present. - */ - public Integer gid = null; - - /** - * The POSIX permissions. NULL if not present. - *

    - * Here is a list: - *

    - *

    Note: these numbers are all OCTAL.
    -	 *  
    -	 *  S_IFMT     0170000   bitmask for the file type bitfields
    -	 *  S_IFSOCK   0140000   socket
    -	 *  S_IFLNK    0120000   symbolic link
    -	 *  S_IFREG    0100000   regular file
    -	 *  S_IFBLK    0060000   block device
    -	 *  S_IFDIR    0040000   directory
    -	 *  S_IFCHR    0020000   character device
    -	 *  S_IFIFO    0010000   fifo 
    -	 *  S_ISUID    0004000   set UID bit
    -	 *  S_ISGID    0002000   set GID bit 
    -	 *  S_ISVTX    0001000   sticky bit
    -	 *  
    -	 *  S_IRWXU    00700     mask for file owner permissions
    -	 *  S_IRUSR    00400     owner has read permission
    -	 *  S_IWUSR    00200     owner has write permission
    -	 *  S_IXUSR    00100     owner has execute permission
    -	 *  S_IRWXG    00070     mask for group permissions
    -	 *  S_IRGRP    00040     group has read permission
    -	 *  S_IWGRP    00020     group has write permission
    -	 *  S_IXGRP    00010     group has execute permission
    -	 *  S_IRWXO    00007     mask for permissions for others (not in group)
    -	 *  S_IROTH    00004     others have read permission
    -	 *  S_IWOTH    00002     others have write permisson
    -	 *  S_IXOTH    00001     others have execute permission
    -	 * 
    - */ - public Integer permissions = null; - - /** - * The ATIME attribute. Represented as seconds from Jan 1, 1970 in UTC. - * NULL if not present. - */ - public Integer atime = null; - - /** - * The MTIME attribute. Represented as seconds from Jan 1, 1970 in UTC. - * NULL if not present. - */ - public Integer mtime = null; - - /** - * Checks if this entry is a directory. - * - * @return Returns true if permissions are available and they indicate - * that this entry represents a directory. - */ - public boolean isDirectory() - { - if (permissions == null) - return false; - - return ((permissions.intValue() & 0040000) != 0); - } - - /** - * Checks if this entry is a regular file. - * - * @return Returns true if permissions are available and they indicate - * that this entry represents a regular file. - */ - public boolean isRegularFile() - { - if (permissions == null) - return false; - - return ((permissions.intValue() & 0100000) != 0); - } - - /** - * Checks if this entry is a a symlink. - * - * @return Returns true if permissions are available and they indicate - * that this entry represents a symlink. - */ - public boolean isSymlink() - { - if (permissions == null) - return false; - - return ((permissions.intValue() & 0120000) != 0); - } - - /** - * Turn the POSIX permissions into a 7 digit octal representation. - * Note: the returned value is first masked with 0177777. - * - * @return NULL if permissions are not available. - */ - public String getOctalPermissions() - { - if (permissions == null) - return null; - - String res = Integer.toString(permissions.intValue() & 0177777, 8); - - StringBuffer sb = new StringBuffer(); - - int leadingZeros = 7 - res.length(); - - while (leadingZeros > 0) - { - sb.append('0'); - leadingZeros--; - } - - sb.append(res); - - return sb.toString(); - } +public class SFTPv3FileAttributes { + /** + * The SIZE attribute. NULL if not present. + */ + public Long size = null; + + /** + * The UID attribute. NULL if not present. + */ + public Integer uid = null; + + /** + * The GID attribute. NULL if not present. + */ + public Integer gid = null; + + /** + * The POSIX permissions. NULL if not present. + *

    + * Here is a list: + *

    + *

    Note: these numbers are all OCTAL.
    +     *
    +     *  S_IFMT     0170000   bitmask for the file type bitfields
    +     *  S_IFSOCK   0140000   socket
    +     *  S_IFLNK    0120000   symbolic link
    +     *  S_IFREG    0100000   regular file
    +     *  S_IFBLK    0060000   block device
    +     *  S_IFDIR    0040000   directory
    +     *  S_IFCHR    0020000   character device
    +     *  S_IFIFO    0010000   fifo
    +     *  S_ISUID    0004000   set UID bit
    +     *  S_ISGID    0002000   set GID bit
    +     *  S_ISVTX    0001000   sticky bit
    +     *
    +     *  S_IRWXU    00700     mask for file owner permissions
    +     *  S_IRUSR    00400     owner has read permission
    +     *  S_IWUSR    00200     owner has write permission
    +     *  S_IXUSR    00100     owner has execute permission
    +     *  S_IRWXG    00070     mask for group permissions
    +     *  S_IRGRP    00040     group has read permission
    +     *  S_IWGRP    00020     group has write permission
    +     *  S_IXGRP    00010     group has execute permission
    +     *  S_IRWXO    00007     mask for permissions for others (not in group)
    +     *  S_IROTH    00004     others have read permission
    +     *  S_IWOTH    00002     others have write permisson
    +     *  S_IXOTH    00001     others have execute permission
    +     * 
    + */ + public Integer permissions = null; + + /** + * The ATIME attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Integer atime = null; + + /** + * The MTIME attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Integer mtime = null; + + /** + * Checks if this entry is a directory. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a directory. + */ + public boolean isDirectory() { + if (permissions == null) + return false; + + return ((permissions.intValue() & 0040000) != 0); + } + + /** + * Checks if this entry is a regular file. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a regular file. + */ + public boolean isRegularFile() { + if (permissions == null) + return false; + + return ((permissions.intValue() & 0100000) != 0); + } + + /** + * Checks if this entry is a a symlink. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a symlink. + */ + public boolean isSymlink() { + if (permissions == null) + return false; + + return ((permissions.intValue() & 0120000) != 0); + } + + /** + * Turn the POSIX permissions into a 7 digit octal representation. + * Note: the returned value is first masked with 0177777. + * + * @return NULL if permissions are not available. + */ + public String getOctalPermissions() { + if (permissions == null) + return null; + + String res = Integer.toString(permissions.intValue() & 0177777, 8); + + StringBuffer sb = new StringBuffer(); + + int leadingZeros = 7 - res.length(); + + while (leadingZeros > 0) { + sb.append('0'); + leadingZeros--; + } + + sb.append(res); + + return sb.toString(); + } } diff --git a/src/ch/ethz/ssh2/SFTPv3FileHandle.java b/src/ch/ethz/ssh2/SFTPv3FileHandle.java index 7fd60f9..a37f2a6 100644 --- a/src/ch/ethz/ssh2/SFTPv3FileHandle.java +++ b/src/ch/ethz/ssh2/SFTPv3FileHandle.java @@ -1,45 +1,40 @@ - package ch.ethz.ssh2; /** * A SFTPv3FileHandle. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SFTPv3FileHandle.java,v 1.2 2006/09/11 09:05:11 cplattne Exp $ */ -public class SFTPv3FileHandle -{ - final SFTPv3Client client; - final byte[] fileHandle; - boolean isClosed = false; +public class SFTPv3FileHandle { + final SFTPv3Client client; + final byte[] fileHandle; + boolean isClosed = false; /* The constructor is NOT public */ - SFTPv3FileHandle(SFTPv3Client client, byte[] h) - { - this.client = client; - this.fileHandle = h; - } + SFTPv3FileHandle(SFTPv3Client client, byte[] h) { + this.client = client; + this.fileHandle = h; + } - /** - * Get the SFTPv3Client instance which created this handle. - * - * @return A SFTPv3Client instance. - */ - public SFTPv3Client getClient() - { - return client; - } + /** + * Get the SFTPv3Client instance which created this handle. + * + * @return A SFTPv3Client instance. + */ + public SFTPv3Client getClient() { + return client; + } - /** - * Check if this handle was closed with the {@link SFTPv3Client#closeFile(SFTPv3FileHandle)} method - * of the SFTPv3Client instance which created the handle. - * - * @return if the handle is closed. - */ - public boolean isClosed() - { - return isClosed; - } + /** + * Check if this handle was closed with the {@link SFTPv3Client#closeFile(SFTPv3FileHandle)} method + * of the SFTPv3Client instance which created the handle. + * + * @return if the handle is closed. + */ + public boolean isClosed() { + return isClosed; + } } diff --git a/src/ch/ethz/ssh2/ServerHostKeyVerifier.java b/src/ch/ethz/ssh2/ServerHostKeyVerifier.java index d2e461d..447983f 100644 --- a/src/ch/ethz/ssh2/ServerHostKeyVerifier.java +++ b/src/ch/ethz/ssh2/ServerHostKeyVerifier.java @@ -1,31 +1,29 @@ - package ch.ethz.ssh2; /** * A callback interface used to implement a client specific method of checking * server host keys. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ServerHostKeyVerifier.java,v 1.4 2006/02/14 19:43:16 cplattne Exp $ */ -public interface ServerHostKeyVerifier -{ - /** - * The actual verifier method, it will be called by the key exchange code - * on EVERY key exchange - this can happen several times during the lifetime - * of a connection. - *

    - * Note: SSH-2 servers are allowed to change their hostkey at ANY time. - * - * @param hostname the hostname used to create the {@link Connection} object - * @param port the remote TCP port - * @param serverHostKeyAlgorithm the public key algorithm (ssh-rsa or ssh-dss) - * @param serverHostKey the server's public key blob - * @return if the client wants to accept the server's host key - if not, the - * connection will be closed. - * @throws Exception Will be wrapped with an IOException, extended version of returning false =) - */ - public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) - throws Exception; +public interface ServerHostKeyVerifier { + /** + * The actual verifier method, it will be called by the key exchange code + * on EVERY key exchange - this can happen several times during the lifetime + * of a connection. + *

    + * Note: SSH-2 servers are allowed to change their hostkey at ANY time. + * + * @param hostname the hostname used to create the {@link Connection} object + * @param port the remote TCP port + * @param serverHostKeyAlgorithm the public key algorithm (ssh-rsa or ssh-dss) + * @param serverHostKey the server's public key blob + * @return if the client wants to accept the server's host key - if not, the + * connection will be closed. + * @throws Exception Will be wrapped with an IOException, extended version of returning false =) + */ + public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) + throws Exception; } diff --git a/src/ch/ethz/ssh2/Session.java b/src/ch/ethz/ssh2/Session.java index 8d87a9f..00712b0 100644 --- a/src/ch/ethz/ssh2/Session.java +++ b/src/ch/ethz/ssh2/Session.java @@ -1,453 +1,405 @@ - package ch.ethz.ssh2; +import ch.ethz.ssh2.channel.Channel; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.X11ServerData; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; -import ch.ethz.ssh2.channel.Channel; -import ch.ethz.ssh2.channel.ChannelManager; -import ch.ethz.ssh2.channel.X11ServerData; - /** * A Session is a remote execution of a program. "Program" means * in this context either a shell, an application or a system command. The * program may or may not have a tty. Only one single program can be started on * a session. However, multiple sessions can be active simultaneously. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Session.java,v 1.9 2006/02/14 19:43:16 cplattne Exp $ */ -public class Session -{ - ChannelManager cm; - Channel cn; - - boolean flag_pty_requested = false; - boolean flag_x11_requested = false; - boolean flag_execution_started = false; - boolean flag_closed = false; - - String x11FakeCookie = null; - - final SecureRandom rnd; - - Session(ChannelManager cm, SecureRandom rnd) throws IOException - { - this.cm = cm; - this.cn = cm.openSessionChannel(); - this.rnd = rnd; - } - - /** - * Basically just a wrapper for lazy people - identical to calling - * requestPTY("dumb", 0, 0, 0, 0, null). - * - * @throws IOException - */ - public void requestDumbPTY() throws IOException - { - requestPTY("dumb", 0, 0, 0, 0, null); - } - - /** - * Basically just another wrapper for lazy people - identical to calling - * requestPTY(term, 0, 0, 0, 0, null). - * - * @throws IOException - */ - public void requestPTY(String term) throws IOException - { - requestPTY(term, 0, 0, 0, 0, null); - } - - /** - * Allocate a pseudo-terminal for this session. - *

    - * This method may only be called before a program or shell is started in - * this session. - *

    - * Different aspects can be specified: - *

    - *

      - *
    • The TERM environment variable value (e.g., vt100)
    • - *
    • The terminal's dimensions.
    • - *
    • The encoded terminal modes.
    • - *
    - * Zero dimension parameters are ignored. The character/row dimensions - * override the pixel dimensions (when nonzero). Pixel dimensions refer to - * the drawable area of the window. The dimension parameters are only - * informational. The encoding of terminal modes (parameter - * terminal_modes) is described, e.g., in - * draft-ietf-secsh-connect-XY.txt. - * - * @param term - * The TERM environment variable value (e.g., vt100) - * @param term_width_characters - * terminal width, characters (e.g., 80) - * @param term_height_characters - * terminal height, rows (e.g., 24) - * @param term_width_pixels - * terminal width, pixels (e.g., 640) - * @param term_height_pixels - * terminal height, pixels (e.g., 480) - * @param terminal_modes - * encoded terminal modes (may be null) - * @throws IOException - */ - public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels, - int term_height_pixels, byte[] terminal_modes) throws IOException - { - if (term == null) - throw new IllegalArgumentException("TERM cannot be null."); - - if ((terminal_modes != null) && (terminal_modes.length > 0)) - { - if (terminal_modes[terminal_modes.length - 1] != 0) - throw new IOException("Illegal terminal modes description, does not end in zero byte"); - } - else - terminal_modes = new byte[] { 0 }; - - synchronized (this) - { +public class Session { + final SecureRandom rnd; + ChannelManager cm; + Channel cn; + boolean flag_pty_requested = false; + boolean flag_x11_requested = false; + boolean flag_execution_started = false; + boolean flag_closed = false; + String x11FakeCookie = null; + + Session(ChannelManager cm, SecureRandom rnd) throws IOException { + this.cm = cm; + this.cn = cm.openSessionChannel(); + this.rnd = rnd; + } + + /** + * Basically just a wrapper for lazy people - identical to calling + * requestPTY("dumb", 0, 0, 0, 0, null). + * + * @throws IOException + */ + public void requestDumbPTY() throws IOException { + requestPTY("dumb", 0, 0, 0, 0, null); + } + + /** + * Basically just another wrapper for lazy people - identical to calling + * requestPTY(term, 0, 0, 0, 0, null). + * + * @throws IOException + */ + public void requestPTY(String term) throws IOException { + requestPTY(term, 0, 0, 0, 0, null); + } + + /** + * Allocate a pseudo-terminal for this session. + *

    + * This method may only be called before a program or shell is started in + * this session. + *

    + * Different aspects can be specified: + *

    + *

      + *
    • The TERM environment variable value (e.g., vt100)
    • + *
    • The terminal's dimensions.
    • + *
    • The encoded terminal modes.
    • + *
    + * Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. The encoding of terminal modes (parameter + * terminal_modes) is described, e.g., in + * draft-ietf-secsh-connect-XY.txt. + * + * @param term The TERM environment variable value (e.g., vt100) + * @param term_width_characters terminal width, characters (e.g., 80) + * @param term_height_characters terminal height, rows (e.g., 24) + * @param term_width_pixels terminal width, pixels (e.g., 640) + * @param term_height_pixels terminal height, pixels (e.g., 480) + * @param terminal_modes encoded terminal modes (may be null) + * @throws IOException + */ + public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels, byte[] terminal_modes) throws IOException { + if (term == null) + throw new IllegalArgumentException("TERM cannot be null."); + + if ((terminal_modes != null) && (terminal_modes.length > 0)) { + if (terminal_modes[terminal_modes.length - 1] != 0) + throw new IOException("Illegal terminal modes description, does not end in zero byte"); + } else + terminal_modes = new byte[]{0}; + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_pty_requested) + throw new IOException("A PTY was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request PTY at this stage anymore, a remote execution has already started."); + + flag_pty_requested = true; + } + + cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, + terminal_modes); + } + + /** + * Request X11 forwarding for the current session. + *

    + * You have to supply the name and port of your X-server. + *

    + * This method may only be called before a program or shell is started in + * this session. + * + * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1) + * @param port the port of the real (target) X11 server (e.g., 6010) + * @param cookie if non-null, then present this cookie to the real X11 server + * @param singleConnection if true, then the server is instructed to only forward one single + * connection, no more connections shall be forwarded after first, or after the session + * channel has been closed + * @throws IOException + */ + public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection) + throws IOException { + if (hostname == null) + throw new IllegalArgumentException("hostname argument may not be null"); + + synchronized (this) { /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_pty_requested) - throw new IOException("A PTY was already requested."); - - if (flag_execution_started) - throw new IOException( - "Cannot request PTY at this stage anymore, a remote execution has already started."); - - flag_pty_requested = true; - } - - cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, - terminal_modes); - } - - /** - * Request X11 forwarding for the current session. - *

    - * You have to supply the name and port of your X-server. - *

    - * This method may only be called before a program or shell is started in - * this session. - * - * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1) - * @param port the port of the real (target) X11 server (e.g., 6010) - * @param cookie if non-null, then present this cookie to the real X11 server - * @param singleConnection if true, then the server is instructed to only forward one single - * connection, no more connections shall be forwarded after first, or after the session - * channel has been closed - * @throws IOException - */ - public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection) - throws IOException - { - if (hostname == null) - throw new IllegalArgumentException("hostname argument may not be null"); - - synchronized (this) - { - /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); + if (flag_closed) + throw new IOException("This session is closed."); - if (flag_x11_requested) - throw new IOException("X11 forwarding was already requested."); + if (flag_x11_requested) + throw new IOException("X11 forwarding was already requested."); - if (flag_execution_started) - throw new IOException( - "Cannot request X11 forwarding at this stage anymore, a remote execution has already started."); + if (flag_execution_started) + throw new IOException( + "Cannot request X11 forwarding at this stage anymore, a remote execution has already started."); - flag_x11_requested = true; - } + flag_x11_requested = true; + } /* X11ServerData - used to store data about the target X11 server */ - X11ServerData x11data = new X11ServerData(); + X11ServerData x11data = new X11ServerData(); - x11data.hostname = hostname; - x11data.port = port; - x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */ + x11data.hostname = hostname; + x11data.port = port; + x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */ /* Generate fake cookie - this one is used between remote clients and the ganymed proxy */ - byte[] fakeCookie = new byte[16]; - String hexEncodedFakeCookie; + byte[] fakeCookie = new byte[16]; + String hexEncodedFakeCookie; /* Make sure that this fake cookie is unique for this connection */ - while (true) - { - rnd.nextBytes(fakeCookie); + while (true) { + rnd.nextBytes(fakeCookie); /* Generate also hex representation of fake cookie */ - StringBuffer tmp = new StringBuffer(32); - for (int i = 0; i < fakeCookie.length; i++) - { - String digit2 = Integer.toHexString(fakeCookie[i] & 0xff); - tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); - } - hexEncodedFakeCookie = tmp.toString(); + StringBuffer tmp = new StringBuffer(32); + for (int i = 0; i < fakeCookie.length; i++) { + String digit2 = Integer.toHexString(fakeCookie[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + hexEncodedFakeCookie = tmp.toString(); /* Well, yes, chances are low, but we want to be on the safe side */ - if (cm.checkX11Cookie(hexEncodedFakeCookie) == null) - break; - } + if (cm.checkX11Cookie(hexEncodedFakeCookie) == null) + break; + } /* Ask for X11 forwarding */ - cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0); + cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0); /* OK, that went fine, get ready to accept X11 connections... */ /* ... but only if the user has not called close() in the meantime =) */ - synchronized (this) - { - if (flag_closed == false) - { - this.x11FakeCookie = hexEncodedFakeCookie; - cm.registerX11Cookie(hexEncodedFakeCookie, x11data); - } - } + synchronized (this) { + if (flag_closed == false) { + this.x11FakeCookie = hexEncodedFakeCookie; + cm.registerX11Cookie(hexEncodedFakeCookie, x11data); + } + } /* Now it is safe to start remote X11 programs */ - } - - /** - * Execute a command on the remote machine. - * - * @param cmd - * The command to execute on the remote host. - * @throws IOException - */ - public void execCommand(String cmd) throws IOException - { - if (cmd == null) - throw new IllegalArgumentException("cmd argument may not be null"); - - synchronized (this) - { + } + + /** + * Execute a command on the remote machine. + * + * @param cmd The command to execute on the remote host. + * @throws IOException + */ + public void execCommand(String cmd) throws IOException { + if (cmd == null) + throw new IllegalArgumentException("cmd argument may not be null"); + + synchronized (this) { /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_execution_started) - throw new IOException("A remote execution has already started."); - - flag_execution_started = true; - } - - cm.requestExecCommand(cn, cmd); - } - - /** - * Start a shell on the remote machine. - * - * @throws IOException - */ - public void startShell() throws IOException - { - synchronized (this) - { + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestExecCommand(cn, cmd); + } + + /** + * Start a shell on the remote machine. + * + * @throws IOException + */ + public void startShell() throws IOException { + synchronized (this) { /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_execution_started) - throw new IOException("A remote execution has already started."); - - flag_execution_started = true; - } - - cm.requestShell(cn); - } - - /** - * Start a subsystem on the remote machine. - * Unless you know what you are doing, you will never need this. - * - * @param name the name of the subsystem. - * @throws IOException - */ - public void startSubSystem(String name) throws IOException - { - if (name == null) - throw new IllegalArgumentException("name argument may not be null"); - - synchronized (this) - { + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestShell(cn); + } + + /** + * Start a subsystem on the remote machine. + * Unless you know what you are doing, you will never need this. + * + * @param name the name of the subsystem. + * @throws IOException + */ + public void startSubSystem(String name) throws IOException { + if (name == null) + throw new IllegalArgumentException("name argument may not be null"); + + synchronized (this) { /* The following is just a nicer error, we would catch it anyway later in the channel code */ - if (flag_closed) - throw new IOException("This session is closed."); - - if (flag_execution_started) - throw new IOException("A remote execution has already started."); - - flag_execution_started = true; - } - - cm.requestSubSystem(cn, name); - } - - public InputStream getStdout() - { - return cn.getStdoutStream(); - } - - public InputStream getStderr() - { - return cn.getStderrStream(); - } - - public OutputStream getStdin() - { - return cn.getStdinStream(); - } - - /** - * This method blocks until there is more data available on either the - * stdout or stderr InputStream of this Session. Very useful - * if you do not want to use two parallel threads for reading from the two - * InputStreams. One can also specify a timeout. NOTE: do NOT call this - * method if you use concurrent threads that operate on either of the two - * InputStreams of this Session (otherwise this method may - * block, even though more data is available). - * - * @param timeout - * The (non-negative) timeout in ms. 0 means no - * timeout, the call may block forever. - * @return - *

      - *
    • 0 if no more data will arrive.
    • - *
    • 1 if more data is available.
    • - *
    • -1 if a timeout occurred.
    • - *
    - * - * @throws IOException - * @deprecated This method has been replaced with a much more powerful wait-for-condition - * interface and therefore acts only as a wrapper. - * - */ - public int waitUntilDataAvailable(long timeout) throws IOException - { - if (timeout < 0) - throw new IllegalArgumentException("timeout must not be negative!"); - - int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA - | ChannelCondition.EOF); - - if ((conditions & ChannelCondition.TIMEOUT) != 0) - return -1; - - if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0) - return 1; + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestSubSystem(cn, name); + } + + public InputStream getStdout() { + return cn.getStdoutStream(); + } + + public InputStream getStderr() { + return cn.getStderrStream(); + } + + public OutputStream getStdin() { + return cn.getStdinStream(); + } + + /** + * This method blocks until there is more data available on either the + * stdout or stderr InputStream of this Session. Very useful + * if you do not want to use two parallel threads for reading from the two + * InputStreams. One can also specify a timeout. NOTE: do NOT call this + * method if you use concurrent threads that operate on either of the two + * InputStreams of this Session (otherwise this method may + * block, even though more data is available). + * + * @param timeout The (non-negative) timeout in ms. 0 means no + * timeout, the call may block forever. + * @return
      + *
    • 0 if no more data will arrive.
    • + *
    • 1 if more data is available.
    • + *
    • -1 if a timeout occurred.
    • + *
    + * @throws IOException + * @deprecated This method has been replaced with a much more powerful wait-for-condition + * interface and therefore acts only as a wrapper. + */ + public int waitUntilDataAvailable(long timeout) throws IOException { + if (timeout < 0) + throw new IllegalArgumentException("timeout must not be negative!"); + + int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA + | ChannelCondition.EOF); + + if ((conditions & ChannelCondition.TIMEOUT) != 0) + return -1; + + if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0) + return 1; /* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */ - if ((conditions & ChannelCondition.EOF) != 0) - return 0; - - throw new IllegalStateException("Unexpected condition result (" + conditions + ")"); - } - - /** - * This method blocks until certain conditions hold true on the underlying SSH-2 channel. - *

    - * This method returns as soon as one of the following happens: - *

    - *

    - * In any case, the result value contains ALL current conditions, which may be more - * than the specified condition set (i.e., never use the "==" operator to test for conditions - * in the bitmask, see also comments in {@link ChannelCondition}). - *

    - * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and - * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two - * InputStreams of this Session (otherwise this method may - * block, even though more data is available in the StreamGobblers). - * - * @param condition_set a bitmask based on {@link ChannelCondition} values - * @param timeout non-negative timeout in ms, 0 means no timeout - * @return all bitmask specifying all current conditions that are true - */ - - public int waitForCondition(int condition_set, long timeout) - { - if (timeout < 0) - throw new IllegalArgumentException("timeout must be non-negative!"); - - return cm.waitForCondition(cn, timeout, condition_set); - } - - /** - * Get the exit code/status from the remote command - if available. Be - * careful - not all server implementations return this value. It is - * generally a good idea to call this method only when all data from the - * remote side has been consumed (see also the method). - * - * @return An Integer holding the exit code, or - * null if no exit code is (yet) available. - */ - public Integer getExitStatus() - { - return cn.getExitStatus(); - } - - /** - * Get the name of the signal by which the process on the remote side was - * stopped - if available and applicable. Be careful - not all server - * implementations return this value. - * - * @return An String holding the name of the signal, or - * null if the process exited normally or is still - * running (or if the server forgot to send this information). - */ - public String getExitSignal() - { - return cn.getExitSignal(); - } - - /** - * Close this session. NEVER forget to call this method to free up resources - - * even if you got an exception from one of the other methods (or when - * getting an Exception on the Input- or OutputStreams). Sometimes these other - * methods may throw an exception, saying that the underlying channel is - * closed (this can happen, e.g., if the other server sent a close message.) - * However, as long as you have not called the close() - * method, you may be wasting (local) resources. - * - */ - public void close() - { - synchronized (this) - { - if (flag_closed) - return; - - flag_closed = true; - - if (x11FakeCookie != null) - cm.unRegisterX11Cookie(x11FakeCookie, true); - - try - { - cm.closeChannel(cn, "Closed due to user request", true); - } - catch (IOException ignored) - { - } - } - } + if ((conditions & ChannelCondition.EOF) != 0) + return 0; + + throw new IllegalStateException("Unexpected condition result (" + conditions + ")"); + } + + /** + * This method blocks until certain conditions hold true on the underlying SSH-2 channel. + *

    + * This method returns as soon as one of the following happens: + *

      + *
    • at least of the specified conditions (see {@link ChannelCondition}) holds true
    • + *
    • timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions) + *
    • the underlying channel was closed (CLOSED will be set in result conditions) + *
    + *

    + * In any case, the result value contains ALL current conditions, which may be more + * than the specified condition set (i.e., never use the "==" operator to test for conditions + * in the bitmask, see also comments in {@link ChannelCondition}). + *

    + * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and + * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two + * InputStreams of this Session (otherwise this method may + * block, even though more data is available in the StreamGobblers). + * + * @param condition_set a bitmask based on {@link ChannelCondition} values + * @param timeout non-negative timeout in ms, 0 means no timeout + * @return all bitmask specifying all current conditions that are true + */ + + public int waitForCondition(int condition_set, long timeout) { + if (timeout < 0) + throw new IllegalArgumentException("timeout must be non-negative!"); + + return cm.waitForCondition(cn, timeout, condition_set); + } + + /** + * Get the exit code/status from the remote command - if available. Be + * careful - not all server implementations return this value. It is + * generally a good idea to call this method only when all data from the + * remote side has been consumed (see also the method). + * + * @return An Integer holding the exit code, or + * null if no exit code is (yet) available. + */ + public Integer getExitStatus() { + return cn.getExitStatus(); + } + + /** + * Get the name of the signal by which the process on the remote side was + * stopped - if available and applicable. Be careful - not all server + * implementations return this value. + * + * @return An String holding the name of the signal, or + * null if the process exited normally or is still + * running (or if the server forgot to send this information). + */ + public String getExitSignal() { + return cn.getExitSignal(); + } + + /** + * Close this session. NEVER forget to call this method to free up resources - + * even if you got an exception from one of the other methods (or when + * getting an Exception on the Input- or OutputStreams). Sometimes these other + * methods may throw an exception, saying that the underlying channel is + * closed (this can happen, e.g., if the other server sent a close message.) + * However, as long as you have not called the close() + * method, you may be wasting (local) resources. + */ + public void close() { + synchronized (this) { + if (flag_closed) + return; + + flag_closed = true; + + if (x11FakeCookie != null) + cm.unRegisterX11Cookie(x11FakeCookie, true); + + try { + cm.closeChannel(cn, "Closed due to user request", true); + } catch (IOException ignored) { + } + } + } } diff --git a/src/ch/ethz/ssh2/StreamGobbler.java b/src/ch/ethz/ssh2/StreamGobbler.java index 9859a14..cb35eb0 100644 --- a/src/ch/ethz/ssh2/StreamGobbler.java +++ b/src/ch/ethz/ssh2/StreamGobbler.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2; import java.io.IOException; @@ -27,203 +26,169 @@ *

    * The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't", * see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: StreamGobbler.java,v 1.4 2006/02/14 19:43:16 cplattne Exp $ */ -public class StreamGobbler extends InputStream -{ - class GobblerThread extends Thread - { - public void run() - { - byte[] buff = new byte[8192]; - - while (true) - { - try - { - int avail = is.read(buff); - - synchronized (synchronizer) - { - if (avail <= 0) - { - isEOF = true; - synchronizer.notifyAll(); - break; - } - - int space_available = buffer.length - write_pos; - - if (space_available < avail) - { - /* compact/resize buffer */ - - int unread_size = write_pos - read_pos; - int need_space = unread_size + avail; - - byte[] new_buffer = buffer; - - if (need_space > buffer.length) - { - int inc = need_space / 3; - inc = (inc < 256) ? 256 : inc; - inc = (inc > 8192) ? 8192 : inc; - new_buffer = new byte[need_space + inc]; - } - - if (unread_size > 0) - System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size); - - buffer = new_buffer; - - read_pos = 0; - write_pos = unread_size; - } - - System.arraycopy(buff, 0, buffer, write_pos, avail); - write_pos += avail; - - synchronizer.notifyAll(); - } - } - catch (IOException e) - { - synchronized (synchronizer) - { - exception = e; - synchronizer.notifyAll(); - break; - } - } - } - } - } - - private InputStream is; - private GobblerThread t; - - private Object synchronizer = new Object(); - - private boolean isEOF = false; - private boolean isClosed = false; - private IOException exception = null; - - private byte[] buffer = new byte[2048]; - private int read_pos = 0; - private int write_pos = 0; - - public StreamGobbler(InputStream is) - { - this.is = is; - t = new GobblerThread(); - t.setDaemon(true); - t.start(); - } - - public int read() throws IOException - { - synchronized (synchronizer) - { - if (isClosed) - throw new IOException("This StreamGobbler is closed."); - - while (read_pos == write_pos) - { - if (exception != null) - throw exception; - - if (isEOF) - return -1; - - try - { - synchronizer.wait(); - } - catch (InterruptedException e) - { - } - } - - int b = buffer[read_pos++] & 0xff; - - return b; - } - } - - public int available() throws IOException - { - synchronized (synchronizer) - { - if (isClosed) - throw new IOException("This StreamGobbler is closed."); - - return write_pos - read_pos; - } - } - - public int read(byte[] b) throws IOException - { - return read(b, 0, b.length); - } - - public void close() throws IOException - { - synchronized (synchronizer) - { - if (isClosed) - return; - isClosed = true; - isEOF = true; - synchronizer.notifyAll(); - is.close(); - } - } - - public int read(byte[] b, int off, int len) throws IOException - { - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return 0; - - synchronized (synchronizer) - { - if (isClosed) - throw new IOException("This StreamGobbler is closed."); - - while (read_pos == write_pos) - { - if (exception != null) - throw exception; - - if (isEOF) - return -1; - - try - { - synchronizer.wait(); - } - catch (InterruptedException e) - { - } - } - - int avail = write_pos - read_pos; - - avail = (avail > len) ? len : avail; - - System.arraycopy(buffer, read_pos, b, off, avail); - - read_pos += avail; - - return avail; - } - } +public class StreamGobbler extends InputStream { + private InputStream is; + private GobblerThread t; + private Object synchronizer = new Object(); + private boolean isEOF = false; + private boolean isClosed = false; + private IOException exception = null; + private byte[] buffer = new byte[2048]; + private int read_pos = 0; + private int write_pos = 0; + public StreamGobbler(InputStream is) { + this.is = is; + t = new GobblerThread(); + t.setDaemon(true); + t.start(); + } + + public int read() throws IOException { + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try { + synchronizer.wait(); + } catch (InterruptedException e) { + } + } + + int b = buffer[read_pos++] & 0xff; + + return b; + } + } + + public int available() throws IOException { + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + return write_pos - read_pos; + } + } + + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + public void close() throws IOException { + synchronized (synchronizer) { + if (isClosed) + return; + isClosed = true; + isEOF = true; + synchronizer.notifyAll(); + is.close(); + } + } + + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try { + synchronizer.wait(); + } catch (InterruptedException e) { + } + } + + int avail = write_pos - read_pos; + + avail = (avail > len) ? len : avail; + + System.arraycopy(buffer, read_pos, b, off, avail); + + read_pos += avail; + + return avail; + } + } + + class GobblerThread extends Thread { + public void run() { + byte[] buff = new byte[8192]; + + while (true) { + try { + int avail = is.read(buff); + + synchronized (synchronizer) { + if (avail <= 0) { + isEOF = true; + synchronizer.notifyAll(); + break; + } + + int space_available = buffer.length - write_pos; + + if (space_available < avail) { + /* compact/resize buffer */ + + int unread_size = write_pos - read_pos; + int need_space = unread_size + avail; + + byte[] new_buffer = buffer; + + if (need_space > buffer.length) { + int inc = need_space / 3; + inc = (inc < 256) ? 256 : inc; + inc = (inc > 8192) ? 8192 : inc; + new_buffer = new byte[need_space + inc]; + } + + if (unread_size > 0) + System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size); + + buffer = new_buffer; + + read_pos = 0; + write_pos = unread_size; + } + + System.arraycopy(buff, 0, buffer, write_pos, avail); + write_pos += avail; + + synchronizer.notifyAll(); + } + } catch (IOException e) { + synchronized (synchronizer) { + exception = e; + synchronizer.notifyAll(); + break; + } + } + } + } + } } diff --git a/src/ch/ethz/ssh2/auth/AuthenticationManager.java b/src/ch/ethz/ssh2/auth/AuthenticationManager.java index 940f126..2bf0090 100644 --- a/src/ch/ethz/ssh2/auth/AuthenticationManager.java +++ b/src/ch/ethz/ssh2/auth/AuthenticationManager.java @@ -1,402 +1,333 @@ - package ch.ethz.ssh2.auth; -import java.io.IOException; -import java.security.SecureRandom; -import java.util.Vector; - import ch.ethz.ssh2.InteractiveCallback; import ch.ethz.ssh2.crypto.PEMDecoder; -import ch.ethz.ssh2.packets.PacketServiceAccept; -import ch.ethz.ssh2.packets.PacketServiceRequest; -import ch.ethz.ssh2.packets.PacketUserauthBanner; -import ch.ethz.ssh2.packets.PacketUserauthFailure; -import ch.ethz.ssh2.packets.PacketUserauthInfoRequest; -import ch.ethz.ssh2.packets.PacketUserauthInfoResponse; -import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive; -import ch.ethz.ssh2.packets.PacketUserauthRequestNone; -import ch.ethz.ssh2.packets.PacketUserauthRequestPassword; -import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey; -import ch.ethz.ssh2.packets.Packets; -import ch.ethz.ssh2.packets.TypesWriter; -import ch.ethz.ssh2.signature.DSAPrivateKey; -import ch.ethz.ssh2.signature.DSASHA1Verify; -import ch.ethz.ssh2.signature.DSASignature; -import ch.ethz.ssh2.signature.RSAPrivateKey; -import ch.ethz.ssh2.signature.RSASHA1Verify; -import ch.ethz.ssh2.signature.RSASignature; +import ch.ethz.ssh2.packets.*; +import ch.ethz.ssh2.signature.*; import ch.ethz.ssh2.transport.MessageHandler; import ch.ethz.ssh2.transport.TransportManager; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Vector; + /** * AuthenticationManager. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: AuthenticationManager.java,v 1.14 2006/07/30 21:59:29 cplattne Exp $ */ -public class AuthenticationManager implements MessageHandler -{ - TransportManager tm; - - Vector packets = new Vector(); - boolean connectionClosed = false; - - String banner; - - String[] remainingMethods = null; - boolean isPartialSuccess = false; - - boolean authenticated = false; - boolean initDone = false; - - public AuthenticationManager(TransportManager tm) - { - this.tm = tm; - } - - boolean methodPossible(String methName) - { - if (remainingMethods == null) - return false; - - for (int i = 0; i < remainingMethods.length; i++) - { - if (remainingMethods[i].compareTo(methName) == 0) - return true; - } - return false; - } - - byte[] deQueue() throws IOException - { - synchronized (packets) - { - while (packets.size() == 0) - { - if (connectionClosed) - throw (IOException) new IOException("The connection is closed.").initCause(tm - .getReasonClosedCause()); - - try - { - packets.wait(); - } - catch (InterruptedException ign) - { - } - } - /* This sequence works with J2ME */ - byte[] res = (byte[]) packets.firstElement(); - packets.removeElementAt(0); - return res; - } - } - - byte[] getNextMessage() throws IOException - { - while (true) - { - byte[] msg = deQueue(); - - if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER) - return msg; - - PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0, msg.length); - - banner = sb.getBanner(); - } - } - - public String[] getRemainingMethods(String user) throws IOException - { - initialize(user); - return remainingMethods; - } - - public boolean getPartialSuccess() - { - return isPartialSuccess; - } - - private boolean initialize(String user) throws IOException - { - if (initDone == false) - { - tm.registerMessageHandler(this, 0, 255); - - PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); - tm.sendMessage(sr.getPayload()); - - PacketUserauthRequestNone urn = new PacketUserauthRequestNone("ssh-connection", user); - tm.sendMessage(urn.getPayload()); - - byte[] msg = getNextMessage(); - new PacketServiceAccept(msg, 0, msg.length); - msg = getNextMessage(); - - initDone = true; - - if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) - { - authenticated = true; - return true; - } - - if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE) - { - PacketUserauthFailure puf = new PacketUserauthFailure(msg, 0, msg.length); - - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - return false; - } - - throw new IOException("Unexpected SSH message (type " + msg[0] + ")"); - } - return authenticated; - } - - public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) throws IOException - { - try - { - initialize(user); - - if (methodPossible("publickey") == false) - throw new IOException("Authentication method publickey not supported by the server at this stage."); - - Object key = PEMDecoder.decode(PEMPrivateKey, password); - - if (key instanceof DSAPrivateKey) - { - DSAPrivateKey pk = (DSAPrivateKey) key; - - byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey(pk.getPublicKey()); - - TypesWriter tw = new TypesWriter(); - - byte[] H = tm.getSessionIdentifier(); - - tw.writeString(H, 0, H.length); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(user); - tw.writeString("ssh-connection"); - tw.writeString("publickey"); - tw.writeBoolean(true); - tw.writeString("ssh-dss"); - tw.writeString(pk_enc, 0, pk_enc.length); - - byte[] msg = tw.getBytes(); - - DSASignature ds = DSASHA1Verify.generateSignature(msg, pk, rnd); - - byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); - - PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, - "ssh-dss", pk_enc, ds_enc); - tm.sendMessage(ua.getPayload()); - } - else if (key instanceof RSAPrivateKey) - { - RSAPrivateKey pk = (RSAPrivateKey) key; - - byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey(pk.getPublicKey()); - - TypesWriter tw = new TypesWriter(); - { - byte[] H = tm.getSessionIdentifier(); - - tw.writeString(H, 0, H.length); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(user); - tw.writeString("ssh-connection"); - tw.writeString("publickey"); - tw.writeBoolean(true); - tw.writeString("ssh-rsa"); - tw.writeString(pk_enc, 0, pk_enc.length); - } - - byte[] msg = tw.getBytes(); - - RSASignature ds = RSASHA1Verify.generateSignature(msg, pk); - - byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds); - - PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, - "ssh-rsa", pk_enc, rsa_sig_enc); - tm.sendMessage(ua.getPayload()); - } - else - { - throw new IOException("Unknown private key type returned by the PEM decoder."); - } - - byte[] ar = getNextMessage(); - - if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) - { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) - { - PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); - - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - - return false; - } - - throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); - - } - catch (IOException e) - { - tm.close(e, false); - throw (IOException) new IOException("Publickey authentication failed.").initCause(e); - } - } - - public boolean authenticatePassword(String user, String pass) throws IOException - { - try - { - initialize(user); - - if (methodPossible("password") == false) - throw new IOException("Authentication method password not supported by the server at this stage."); - - PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); - tm.sendMessage(ua.getPayload()); - - byte[] ar = getNextMessage(); - - if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) - { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) - { - PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); - - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - - return false; - } - - throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); - - } - catch (IOException e) - { - tm.close(e, false); - throw (IOException) new IOException("Password authentication failed.").initCause(e); - } - } - - public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException - { - try - { - initialize(user); - - if (methodPossible("keyboard-interactive") == false) - throw new IOException( - "Authentication method keyboard-interactive not supported by the server at this stage."); - - if (submethods == null) - submethods = new String[0]; - - PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, - submethods); - - tm.sendMessage(ua.getPayload()); - - while (true) - { - byte[] ar = getNextMessage(); - - if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) - { - authenticated = true; - tm.removeMessageHandler(this, 0, 255); - return true; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) - { - PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); - - remainingMethods = puf.getAuthThatCanContinue(); - isPartialSuccess = puf.isPartialSuccess(); - - return false; - } - - if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST) - { - PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(ar, 0, ar.length); - - String[] responses; - - try - { - responses = cb.replyToChallenge(pui.getName(), pui.getInstruction(), pui.getNumPrompts(), pui - .getPrompt(), pui.getEcho()); - } - catch (Exception e) - { - throw (IOException) new IOException("Exception in callback.").initCause(e); - } - - if (responses == null) - throw new IOException("Your callback may not return NULL!"); - - PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); - tm.sendMessage(puir.getPayload()); - - continue; - } - - throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); - } - } - catch (IOException e) - { - tm.close(e, false); - throw (IOException) new IOException("Keyboard-interactive authentication failed.").initCause(e); - } - } - - public void handleMessage(byte[] msg, int msglen) throws IOException - { - synchronized (packets) - { - if (msg == null) - { - connectionClosed = true; - } - else - { - byte[] tmp = new byte[msglen]; - System.arraycopy(msg, 0, tmp, 0, msglen); - packets.addElement(tmp); - } - - packets.notifyAll(); - - if (packets.size() > 5) - { - connectionClosed = true; - throw new IOException("Error, peer is flooding us with authentication packets."); - } - } - } +public class AuthenticationManager implements MessageHandler { + TransportManager tm; + + Vector packets = new Vector(); + boolean connectionClosed = false; + + String banner; + + String[] remainingMethods = null; + boolean isPartialSuccess = false; + + boolean authenticated = false; + boolean initDone = false; + + public AuthenticationManager(TransportManager tm) { + this.tm = tm; + } + + boolean methodPossible(String methName) { + if (remainingMethods == null) + return false; + + for (int i = 0; i < remainingMethods.length; i++) { + if (remainingMethods[i].compareTo(methName) == 0) + return true; + } + return false; + } + + byte[] deQueue() throws IOException { + synchronized (packets) { + while (packets.size() == 0) { + if (connectionClosed) + throw (IOException) new IOException("The connection is closed.").initCause(tm + .getReasonClosedCause()); + + try { + packets.wait(); + } catch (InterruptedException ign) { + } + } + /* This sequence works with J2ME */ + byte[] res = (byte[]) packets.firstElement(); + packets.removeElementAt(0); + return res; + } + } + + byte[] getNextMessage() throws IOException { + while (true) { + byte[] msg = deQueue(); + + if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER) + return msg; + + PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0, msg.length); + + banner = sb.getBanner(); + } + } + + public String[] getRemainingMethods(String user) throws IOException { + initialize(user); + return remainingMethods; + } + + public boolean getPartialSuccess() { + return isPartialSuccess; + } + + private boolean initialize(String user) throws IOException { + if (initDone == false) { + tm.registerMessageHandler(this, 0, 255); + + PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); + tm.sendMessage(sr.getPayload()); + + PacketUserauthRequestNone urn = new PacketUserauthRequestNone("ssh-connection", user); + tm.sendMessage(urn.getPayload()); + + byte[] msg = getNextMessage(); + new PacketServiceAccept(msg, 0, msg.length); + msg = getNextMessage(); + + initDone = true; + + if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { + authenticated = true; + return true; + } + + if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { + PacketUserauthFailure puf = new PacketUserauthFailure(msg, 0, msg.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new IOException("Unexpected SSH message (type " + msg[0] + ")"); + } + return authenticated; + } + + public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) throws IOException { + try { + initialize(user); + + if (methodPossible("publickey") == false) + throw new IOException("Authentication method publickey not supported by the server at this stage."); + + Object key = PEMDecoder.decode(PEMPrivateKey, password); + + if (key instanceof DSAPrivateKey) { + DSAPrivateKey pk = (DSAPrivateKey) key; + + byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey(pk.getPublicKey()); + + TypesWriter tw = new TypesWriter(); + + byte[] H = tm.getSessionIdentifier(); + + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-dss"); + tw.writeString(pk_enc, 0, pk_enc.length); + + byte[] msg = tw.getBytes(); + + DSASignature ds = DSASHA1Verify.generateSignature(msg, pk, rnd); + + byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); + + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-dss", pk_enc, ds_enc); + tm.sendMessage(ua.getPayload()); + } else if (key instanceof RSAPrivateKey) { + RSAPrivateKey pk = (RSAPrivateKey) key; + + byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey(pk.getPublicKey()); + + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-rsa"); + tw.writeString(pk_enc, 0, pk_enc.length); + } + + byte[] msg = tw.getBytes(); + + RSASignature ds = RSASHA1Verify.generateSignature(msg, pk); + + byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds); + + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-rsa", pk_enc, rsa_sig_enc); + tm.sendMessage(ua.getPayload()); + } else { + throw new IOException("Unknown private key type returned by the PEM decoder."); + } + + byte[] ar = getNextMessage(); + + if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { + PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + + return false; + } + + throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); + + } catch (IOException e) { + tm.close(e, false); + throw (IOException) new IOException("Publickey authentication failed.").initCause(e); + } + } + + public boolean authenticatePassword(String user, String pass) throws IOException { + try { + initialize(user); + + if (methodPossible("password") == false) + throw new IOException("Authentication method password not supported by the server at this stage."); + + PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); + tm.sendMessage(ua.getPayload()); + + byte[] ar = getNextMessage(); + + if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { + PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + + return false; + } + + throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); + + } catch (IOException e) { + tm.close(e, false); + throw (IOException) new IOException("Password authentication failed.").initCause(e); + } + } + + public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException { + try { + initialize(user); + + if (methodPossible("keyboard-interactive") == false) + throw new IOException( + "Authentication method keyboard-interactive not supported by the server at this stage."); + + if (submethods == null) + submethods = new String[0]; + + PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, + submethods); + + tm.sendMessage(ua.getPayload()); + + while (true) { + byte[] ar = getNextMessage(); + + if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) { + authenticated = true; + tm.removeMessageHandler(this, 0, 255); + return true; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) { + PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length); + + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + + return false; + } + + if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST) { + PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(ar, 0, ar.length); + + String[] responses; + + try { + responses = cb.replyToChallenge(pui.getName(), pui.getInstruction(), pui.getNumPrompts(), pui + .getPrompt(), pui.getEcho()); + } catch (Exception e) { + throw (IOException) new IOException("Exception in callback.").initCause(e); + } + + if (responses == null) + throw new IOException("Your callback may not return NULL!"); + + PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); + tm.sendMessage(puir.getPayload()); + + continue; + } + + throw new IOException("Unexpected SSH message (type " + ar[0] + ")"); + } + } catch (IOException e) { + tm.close(e, false); + throw (IOException) new IOException("Keyboard-interactive authentication failed.").initCause(e); + } + } + + public void handleMessage(byte[] msg, int msglen) throws IOException { + synchronized (packets) { + if (msg == null) { + connectionClosed = true; + } else { + byte[] tmp = new byte[msglen]; + System.arraycopy(msg, 0, tmp, 0, msglen); + packets.addElement(tmp); + } + + packets.notifyAll(); + + if (packets.size() > 5) { + connectionClosed = true; + throw new IOException("Error, peer is flooding us with authentication packets."); + } + } + } } diff --git a/src/ch/ethz/ssh2/channel/Channel.java b/src/ch/ethz/ssh2/channel/Channel.java index fd4530c..d96f71d 100644 --- a/src/ch/ethz/ssh2/channel/Channel.java +++ b/src/ch/ethz/ssh2/channel/Channel.java @@ -1,15 +1,13 @@ - package ch.ethz.ssh2.channel; /** * Channel. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Channel.java,v 1.7 2005/12/07 10:25:48 cplattne Exp $ */ -public class Channel -{ - /* +public class Channel { + /* * OK. Here is an important part of the JVM Specification: * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) * @@ -36,34 +34,33 @@ public class Channel * */ - static final int STATE_OPENING = 1; - static final int STATE_OPEN = 2; - static final int STATE_CLOSED = 4; + static final int STATE_OPENING = 1; + static final int STATE_OPEN = 2; + static final int STATE_CLOSED = 4; - static final int CHANNEL_BUFFER_SIZE = 30000; + static final int CHANNEL_BUFFER_SIZE = 30000; /* * To achieve correctness, the following rules have to be respected when * accessing this object: */ - // These fields can always be read - final ChannelManager cm; - final ChannelOutputStream stdinStream; - final ChannelInputStream stdoutStream; - final ChannelInputStream stderrStream; - - // These two fields will only be written while the Channel is in state - // STATE_OPENING. - // The code makes sure that the two fields are written out when the state is - // changing to STATE_OPEN. - // Therefore, if you know that the Channel is in state STATE_OPEN, then you - // can read these two fields without synchronizing on the Channel. However, make - // sure that you get the latest values (e.g., flush caches by synchronizing on any - // object). However, to be on the safe side, you can lock the channel. - - int localID = -1; - int remoteID = -1; + // These fields can always be read + final ChannelManager cm; + final ChannelOutputStream stdinStream; + final ChannelInputStream stdoutStream; + final ChannelInputStream stderrStream; + + // These two fields will only be written while the Channel is in state + // STATE_OPENING. + // The code makes sure that the two fields are written out when the state is + // changing to STATE_OPEN. + // Therefore, if you know that the Channel is in state STATE_OPEN, then you + // can read these two fields without synchronizing on the Channel. However, make + // sure that you get the latest values (e.g., flush caches by synchronizing on any + // object). However, to be on the safe side, you can lock the channel. + final Object channelSendLock = new Object(); + final byte[] msgWindowAdjust = new byte[9]; /* * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE @@ -87,121 +84,95 @@ public class Channel * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation * above. */ - - final Object channelSendLock = new Object(); - boolean closeMessageSent = false; + final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; + final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; /* * Stop memory fragmentation by allocating this often used buffer. * May only be used while holding the channelSendLock */ - - final byte[] msgWindowAdjust = new byte[9]; - - // If you access (read or write) any of the following fields, then you have - // to synchronize on the channel. - - int state = STATE_OPENING; - - boolean closeMessageRecv = false; - - /* This is a stupid implementation. At the moment we can only wait - * for one pending request per channel. - */ - int successCounter = 0; - int failedCounter = 0; - - int localWindow = 0; /* locally, we use a small window, < 2^31 */ - long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ - - int localMaxPacketSize = -1; - int remoteMaxPacketSize = -1; - - final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; - final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; - - int stdoutReadpos = 0; - int stdoutWritepos = 0; - int stderrReadpos = 0; - int stderrWritepos = 0; - - boolean EOF = false; - - Integer exit_status; - - String exit_signal; - - // we keep the x11 cookie so that this channel can be closed when this - // specific x11 forwarding gets stopped - - String hexX11FakeCookie; - - // reasonClosed is special, since we sometimes need to access it - // while holding the channelSendLock. - // We protect it with a private short term lock. - - private final Object reasonClosedLock = new Object(); - private String reasonClosed = null; - - public Channel(ChannelManager cm) - { - this.cm = cm; - - this.localWindow = CHANNEL_BUFFER_SIZE; - this.localMaxPacketSize = 35000 - 1024; // leave enough slack - - this.stdinStream = new ChannelOutputStream(this); - this.stdoutStream = new ChannelInputStream(this, false); - this.stderrStream = new ChannelInputStream(this, true); - } + private final Object reasonClosedLock = new Object(); + + // If you access (read or write) any of the following fields, then you have + // to synchronize on the channel. + int localID = -1; + int remoteID = -1; + boolean closeMessageSent = false; + int state = STATE_OPENING; + boolean closeMessageRecv = false; + /* This is a stupid implementation. At the moment we can only wait + * for one pending request per channel. + */ + int successCounter = 0; + int failedCounter = 0; + int localWindow = 0; /* locally, we use a small window, < 2^31 */ + long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ + int localMaxPacketSize = -1; + int remoteMaxPacketSize = -1; + int stdoutReadpos = 0; + int stdoutWritepos = 0; + int stderrReadpos = 0; + int stderrWritepos = 0; + boolean EOF = false; + Integer exit_status; + + // we keep the x11 cookie so that this channel can be closed when this + // specific x11 forwarding gets stopped + String exit_signal; + + // reasonClosed is special, since we sometimes need to access it + // while holding the channelSendLock. + // We protect it with a private short term lock. + String hexX11FakeCookie; + private String reasonClosed = null; + + public Channel(ChannelManager cm) { + this.cm = cm; + + this.localWindow = CHANNEL_BUFFER_SIZE; + this.localMaxPacketSize = 35000 - 1024; // leave enough slack + + this.stdinStream = new ChannelOutputStream(this); + this.stdoutStream = new ChannelInputStream(this, false); + this.stderrStream = new ChannelInputStream(this, true); + } /* Methods to allow access from classes outside of this package */ - public ChannelInputStream getStderrStream() - { - return stderrStream; - } - - public ChannelOutputStream getStdinStream() - { - return stdinStream; - } - - public ChannelInputStream getStdoutStream() - { - return stdoutStream; - } - - public String getExitSignal() - { - synchronized (this) - { - return exit_signal; - } - } - - public Integer getExitStatus() - { - synchronized (this) - { - return exit_status; - } - } - - public String getReasonClosed() - { - synchronized (reasonClosedLock) - { - return reasonClosed; - } - } - - public void setReasonClosed(String reasonClosed) - { - synchronized (reasonClosedLock) - { - if (this.reasonClosed == null) - this.reasonClosed = reasonClosed; - } - } + public ChannelInputStream getStderrStream() { + return stderrStream; + } + + public ChannelOutputStream getStdinStream() { + return stdinStream; + } + + public ChannelInputStream getStdoutStream() { + return stdoutStream; + } + + public String getExitSignal() { + synchronized (this) { + return exit_signal; + } + } + + public Integer getExitStatus() { + synchronized (this) { + return exit_status; + } + } + + public String getReasonClosed() { + synchronized (reasonClosedLock) { + return reasonClosed; + } + } + + public void setReasonClosed(String reasonClosed) { + synchronized (reasonClosedLock) { + if (this.reasonClosed == null) + this.reasonClosed = reasonClosed; + } + } } diff --git a/src/ch/ethz/ssh2/channel/ChannelInputStream.java b/src/ch/ethz/ssh2/channel/ChannelInputStream.java index 5a331b6..211652a 100644 --- a/src/ch/ethz/ssh2/channel/ChannelInputStream.java +++ b/src/ch/ethz/ssh2/channel/ChannelInputStream.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.channel; import java.io.IOException; @@ -6,81 +5,73 @@ /** * ChannelInputStream. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ChannelInputStream.java,v 1.5 2005/12/05 17:13:26 cplattne Exp $ */ -public final class ChannelInputStream extends InputStream -{ - Channel c; +public final class ChannelInputStream extends InputStream { + Channel c; - boolean isClosed = false; - boolean isEOF = false; - boolean extendedFlag = false; + boolean isClosed = false; + boolean isEOF = false; + boolean extendedFlag = false; - ChannelInputStream(Channel c, boolean isExtended) - { - this.c = c; - this.extendedFlag = isExtended; - } + ChannelInputStream(Channel c, boolean isExtended) { + this.c = c; + this.extendedFlag = isExtended; + } - public int available() throws IOException - { - if (isEOF) - return 0; + public int available() throws IOException { + if (isEOF) + return 0; - int avail = c.cm.getAvailable(c, extendedFlag); + int avail = c.cm.getAvailable(c, extendedFlag); /* We must not return -1 on EOF */ - return (avail > 0) ? avail : 0; - } + return (avail > 0) ? avail : 0; + } - public void close() throws IOException - { - isClosed = true; - } + public void close() throws IOException { + isClosed = true; + } - public int read(byte[] b, int off, int len) throws IOException - { - if (b == null) - throw new NullPointerException(); + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) + throw new NullPointerException(); - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); - if (len == 0) - return 0; + if (len == 0) + return 0; - if (isEOF) - return -1; + if (isEOF) + return -1; - int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); + int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); - if (ret == -1) - { - isEOF = true; - } + if (ret == -1) { + isEOF = true; + } - return ret; - } + return ret; + } - public int read(byte[] b) throws IOException - { - return read(b, 0, b.length); - } + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } - public int read() throws IOException - { - /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ + public int read() throws IOException { + /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ - final byte b[] = new byte[1]; + final byte b[] = new byte[1]; - int ret = read(b, 0, 1); + int ret = read(b, 0, 1); - if (ret != 1) - return -1; + if (ret != 1) + return -1; - return b[0] & 0xff; - } + return b[0] & 0xff; + } } diff --git a/src/ch/ethz/ssh2/channel/ChannelManager.java b/src/ch/ethz/ssh2/channel/ChannelManager.java index 67ada25..325ba30 100644 --- a/src/ch/ethz/ssh2/channel/ChannelManager.java +++ b/src/ch/ethz/ssh2/channel/ChannelManager.java @@ -1,965 +1,804 @@ - package ch.ethz.ssh2.channel; -import java.io.IOException; -import java.util.HashMap; -import java.util.Vector; - import ch.ethz.ssh2.ChannelCondition; import ch.ethz.ssh2.log.Logger; -import ch.ethz.ssh2.packets.PacketChannelOpenConfirmation; -import ch.ethz.ssh2.packets.PacketChannelOpenFailure; -import ch.ethz.ssh2.packets.PacketGlobalCancelForwardRequest; -import ch.ethz.ssh2.packets.PacketGlobalForwardRequest; -import ch.ethz.ssh2.packets.PacketOpenDirectTCPIPChannel; -import ch.ethz.ssh2.packets.PacketOpenSessionChannel; -import ch.ethz.ssh2.packets.PacketSessionExecCommand; -import ch.ethz.ssh2.packets.PacketSessionPtyRequest; -import ch.ethz.ssh2.packets.PacketSessionStartShell; -import ch.ethz.ssh2.packets.PacketSessionSubsystemRequest; -import ch.ethz.ssh2.packets.PacketSessionX11Request; -import ch.ethz.ssh2.packets.Packets; -import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.*; import ch.ethz.ssh2.transport.MessageHandler; import ch.ethz.ssh2.transport.TransportManager; +import java.io.IOException; +import java.util.HashMap; +import java.util.Vector; + /** * ChannelManager. Please read the comments in Channel.java. *

    * Besides the crypto part, this is the core of the library. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ChannelManager.java,v 1.15 2006/08/11 12:24:01 cplattne Exp $ */ -public class ChannelManager implements MessageHandler -{ - private static final Logger log = Logger.getLogger(ChannelManager.class); - - private HashMap x11_magic_cookies = new HashMap(); - - private TransportManager tm; - - private Vector channels = new Vector(); - private int nextLocalChannel = 100; - private boolean shutdown = false; - private int globalSuccessCounter = 0; - private int globalFailedCounter = 0; - - private HashMap remoteForwardings = new HashMap(); - - private Vector listenerThreads = new Vector(); - - private boolean listenerThreadsAllowed = true; - - public ChannelManager(TransportManager tm) - { - this.tm = tm; - tm.registerMessageHandler(this, 80, 100); - } - - private Channel getChannel(int id) - { - synchronized (channels) - { - for (int i = 0; i < channels.size(); i++) - { - Channel c = (Channel) channels.elementAt(i); - if (c.localID == id) - return c; - } - } - return null; - } - - private void removeChannel(int id) - { - synchronized (channels) - { - for (int i = 0; i < channels.size(); i++) - { - Channel c = (Channel) channels.elementAt(i); - if (c.localID == id) - { - channels.removeElementAt(i); - break; - } - } - } - } - - private int addChannel(Channel c) - { - synchronized (channels) - { - channels.addElement(c); - return nextLocalChannel++; - } - } - - private void waitUntilChannelOpen(Channel c) throws IOException - { - synchronized (c) - { - while (c.state == Channel.STATE_OPENING) - { - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } - - 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 + ")"); - } - } - } - - private final void waitForGlobalSuccessOrFailure() throws IOException - { - synchronized (channels) - { - while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) - { - if (shutdown) - { - throw new IOException("The connection is being shutdown"); - } - - try - { - channels.wait(); - } - catch (InterruptedException ignore) - { - } - } - - if (globalFailedCounter != 0) - { - throw new IOException("The server denied the request (did you enable port forwarding?)"); - } - - if (globalSuccessCounter == 0) - { - throw new IOException("Illegal state."); - } - - } - } - - private final void waitForChannelSuccessOrFailure(Channel c) throws IOException - { - 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) - { - } - } - - if (c.failedCounter != 0) - { - throw new IOException("The server denied the request."); - } - } - } - - 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; - - if (log.isEnabled()) - log.log(50, "Closing all X11 channels for the given fake cookie"); - - Vector channel_copy; - - synchronized (channels) - { - channel_copy = (Vector) channels.clone(); - } - - for (int i = 0; i < channel_copy.size(); i++) - { - Channel c = (Channel) channel_copy.elementAt(i); - - synchronized (c) - { - if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) - continue; - } - - try - { - closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); - } - catch (IOException e) - { - } - } - } - - public X11ServerData checkX11Cookie(String hexFakeCookie) - { - synchronized (x11_magic_cookies) - { - if (hexFakeCookie != null) - return (X11ServerData) x11_magic_cookies.get(hexFakeCookie); - } - return null; - } - - public void closeAllChannels() - { - if (log.isEnabled()) - log.log(50, "Closing all channels"); - - Vector channel_copy; - - synchronized (channels) - { - channel_copy = (Vector) channels.clone(); - } - - for (int i = 0; i < channel_copy.size(); i++) - { - Channel c = (Channel) channel_copy.elementAt(i); - try - { - closeChannel(c, "Closing all channels", true); - } - catch (IOException e) - { - } - } - } - - 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; - } - - if (log.isEnabled()) - log.log(50, "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); - } - - if (log.isEnabled()) - log.log(50, "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 - { - while (len > 0) - { - int thislen = 0; - byte[] msg; - - synchronized (c) - { - while (true) - { - if (c.state == Channel.STATE_CLOSED) - throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - if (c.state != Channel.STATE_OPEN) - throw new IOException("SSH channel in strange state. (" + c.state + ")"); - - if (c.remoteWindow != 0) - break; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } +public class ChannelManager implements MessageHandler { + private static final Logger log = Logger.getLogger(ChannelManager.class); + + private HashMap x11_magic_cookies = new HashMap(); + + private TransportManager tm; + + private Vector channels = new Vector(); + private int nextLocalChannel = 100; + private boolean shutdown = false; + private int globalSuccessCounter = 0; + private int globalFailedCounter = 0; + + private HashMap remoteForwardings = new HashMap(); + + private Vector listenerThreads = new Vector(); + + private boolean listenerThreadsAllowed = true; + + public ChannelManager(TransportManager tm) { + this.tm = tm; + tm.registerMessageHandler(this, 80, 100); + } + + private Channel getChannel(int id) { + synchronized (channels) { + for (int i = 0; i < channels.size(); i++) { + Channel c = (Channel) channels.elementAt(i); + if (c.localID == id) + return c; + } + } + return null; + } + + private void removeChannel(int id) { + synchronized (channels) { + for (int i = 0; i < channels.size(); i++) { + Channel c = (Channel) channels.elementAt(i); + if (c.localID == id) { + channels.removeElementAt(i); + break; + } + } + } + } + + private int addChannel(Channel c) { + synchronized (channels) { + channels.addElement(c); + return nextLocalChannel++; + } + } + + private void waitUntilChannelOpen(Channel c) throws IOException { + synchronized (c) { + while (c.state == Channel.STATE_OPENING) { + try { + c.wait(); + } catch (InterruptedException ignore) { + } + } + + 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 + ")"); + } + } + } + + private final void waitForGlobalSuccessOrFailure() throws IOException { + synchronized (channels) { + while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) { + if (shutdown) { + throw new IOException("The connection is being shutdown"); + } + + try { + channels.wait(); + } catch (InterruptedException ignore) { + } + } + + if (globalFailedCounter != 0) { + throw new IOException("The server denied the request (did you enable port forwarding?)"); + } + + if (globalSuccessCounter == 0) { + throw new IOException("Illegal state."); + } + + } + } + + private final void waitForChannelSuccessOrFailure(Channel c) throws IOException { + 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) { + } + } + + if (c.failedCounter != 0) { + throw new IOException("The server denied the request."); + } + } + } + + 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; + + if (log.isEnabled()) + log.log(50, "Closing all X11 channels for the given fake cookie"); + + Vector channel_copy; + + synchronized (channels) { + channel_copy = (Vector) channels.clone(); + } + + for (int i = 0; i < channel_copy.size(); i++) { + Channel c = (Channel) channel_copy.elementAt(i); + + synchronized (c) { + if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) + continue; + } + + try { + closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); + } catch (IOException e) { + } + } + } + + public X11ServerData checkX11Cookie(String hexFakeCookie) { + synchronized (x11_magic_cookies) { + if (hexFakeCookie != null) + return (X11ServerData) x11_magic_cookies.get(hexFakeCookie); + } + return null; + } + + public void closeAllChannels() { + if (log.isEnabled()) + log.log(50, "Closing all channels"); + + Vector channel_copy; + + synchronized (channels) { + channel_copy = (Vector) channels.clone(); + } + + for (int i = 0; i < channel_copy.size(); i++) { + Channel c = (Channel) channel_copy.elementAt(i); + try { + closeChannel(c, "Closing all channels", true); + } catch (IOException e) { + } + } + } + + 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; + } + + if (log.isEnabled()) + log.log(50, "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); + } + + if (log.isEnabled()) + log.log(50, "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 { + while (len > 0) { + int thislen = 0; + byte[] msg; + + synchronized (c) { + while (true) { + if (c.state == Channel.STATE_CLOSED) + throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + if (c.state != Channel.STATE_OPEN) + throw new IOException("SSH channel in strange state. (" + c.state + ")"); + + if (c.remoteWindow != 0) + break; + + try { + c.wait(); + } catch (InterruptedException ignore) { + } + } /* len > 0, no sign extension can happen when comparing */ - thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; + thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; - int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); + 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 IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - tm.sendMessage(msg); - } - - pos += thislen; - len -= thislen; - } - } - - 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()); - - if (log.isEnabled()) - log.log(50, "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 = (RemoteForwardingData) 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()); - - if (log.isEnabled()) - log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); - - waitForGlobalSuccessOrFailure(); + 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 IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + tm.sendMessage(msg); + } + + pos += thislen; + len -= thislen; + } + } + + 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()); + + if (log.isEnabled()) + log.log(50, "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 = (RemoteForwardingData) 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()); + + if (log.isEnabled()) + log.log(50, "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.addElement(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 - } - - if (log.isEnabled()) - log.log(50, "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()); - } - - if (log.isEnabled()) - log.log(50, "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 - { - 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()); - } - - if (log.isEnabled()) - log.log(50, "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 + ")"); - - if (log.isEnabled()) - log.log(80, "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 - * @return all current events - * - */ - public int waitForCondition(Channel c, long timeout, int condition_mask) - { - 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) - { - } - } - } - } - - 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 - { - int copylen = 0; - int increment = 0; - int remoteID = 0; - int localID = 0; - - synchronized (c) - { - int stdoutAvail = 0; - int stderrAvail = 0; - - while (true) - { - /* + 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.addElement(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 + } + + if (log.isEnabled()) + log.log(50, "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()); + } + + if (log.isEnabled()) + log.log(50, "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 { + 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()); + } + + if (log.isEnabled()) + log.log(50, "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 + ")"); + + if (log.isEnabled()) + log.log(80, "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 + * @return all current events + */ + public int waitForCondition(Channel c, long timeout, int condition_mask) { + 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) { + } + } + } + } + + 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 { + 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; + stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + stderrAvail = c.stderrWritepos - c.stderrReadpos; - if ((!extended) && (stdoutAvail != 0)) - break; + if ((!extended) && (stdoutAvail != 0)) + break; - if ((extended) && (stderrAvail != 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; + if ((c.EOF) || (c.state != Channel.STATE_OPEN)) + return -1; - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } + try { + c.wait(); + } catch (InterruptedException ignore) { + } + } /* 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 (!extended) { + copylen = (stdoutAvail > len) ? len : stdoutAvail; + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); + c.stdoutReadpos += copylen; - if (c.stdoutReadpos != c.stdoutWritepos) + if (c.stdoutReadpos != c.stdoutWritepos) - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - - c.stdoutReadpos); + 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; + 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) + if (c.stderrReadpos != c.stderrWritepos) - System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - - c.stderrReadpos); + System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos + - c.stderrReadpos); - c.stderrWritepos -= c.stderrReadpos; - c.stderrReadpos = 0; - } + c.stderrWritepos -= c.stderrReadpos; + c.stderrReadpos = 0; + } - if (c.state != Channel.STATE_OPEN) - return copylen; + 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); + 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; - } + increment = minFreeSpace - c.localWindow; + c.localWindow = minFreeSpace; + } - remoteID = c.remoteID; /* read while holding the lock */ - localID = c.localID; /* read while holding the lock */ - } + 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 @@ -967,633 +806,584 @@ public int getChannelData(Channel c, boolean extended, byte[] target, int off, i * does not matter in which order they arrive at the server. */ - if (increment > 0) - { - if (log.isEnabled()) - log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); + if (increment > 0) { + if (log.isEnabled()) + log.log(80, "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; - } + return copylen; + } - 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 + ")"); - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); + if (log.isEnabled()) + log.log(80, "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? */ - if ((c.remoteWindow > huge)) - c.remoteWindow = huge; + if ((c.remoteWindow > huge)) + c.remoteWindow = huge; - c.notifyAll(); - } + c.notifyAll(); + } - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); - } + if (log.isEnabled()) + log.log(80, "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 ("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()); - if (log.isEnabled()) - log.log(20, "Unexpected X11 request, denying it!"); + if (log.isEnabled()) + log.log(20, "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 */ - 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 = (RemoteForwardingData) remoteForwardings.get(new Integer(remoteConnectedPort)); - } + synchronized (remoteForwardings) { + rfd = (RemoteForwardingData) 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. */ - tm.sendAsynchronousMessage(pcof.getPayload()); + tm.sendAsynchronousMessage(pcof.getPayload()); - if (log.isEnabled()) - log.log(20, "Unexpected forwarded-tcpip request, denying it!"); + if (log.isEnabled()) + log.log(20, "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. */ - RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, - remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); + RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, + remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); - rat.setDaemon(true); - rat.start(); + rat.setDaemon(true); + rat.start(); - return; - } + 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", ""); + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + "Unknown channel type", ""); - tm.sendAsynchronousMessage(pcof.getPayload()); + tm.sendAsynchronousMessage(pcof.getPayload()); - if (log.isEnabled()) - log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")"); - } + if (log.isEnabled()) + log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")"); + } - public void msgChannelRequest(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); + 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(); + tr.readByte(); // skip packet type + int id = tr.readUINT32(); - Channel c = getChannel(id); + Channel c = getChannel(id); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); - String type = tr.readString("US-ASCII"); - boolean wantReply = tr.readBoolean(); + String type = tr.readString("US-ASCII"); + boolean wantReply = tr.readBoolean(); - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); + if (log.isEnabled()) + log.log(80, "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 message, 'want reply' is true"); + if (type.equals("exit-status")) { + if (wantReply != false) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); - int exit_status = tr.readUINT32(); + int exit_status = tr.readUINT32(); - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + 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(); - } + synchronized (c) { + c.exit_status = new Integer(exit_status); + c.notifyAll(); + } - if (log.isEnabled()) - log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); + if (log.isEnabled()) + log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); - return; - } + return; + } - if (type.equals("exit-signal")) - { - if (wantReply != false) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); + if (type.equals("exit-signal")) { + if (wantReply != false) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); - String signame = tr.readString("US-ASCII"); - tr.readBoolean(); - tr.readString(); - tr.readString(); + 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"); + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - synchronized (c) - { - c.exit_signal = signame; - c.notifyAll(); - } + synchronized (c) { + c.exit_signal = signame; + c.notifyAll(); + } - if (log.isEnabled()) - log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); + if (log.isEnabled()) + log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); - return; - } + 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) - { - byte[] reply = new byte[5]; + if (wantReply) { + byte[] reply = new byte[5]; + + reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; + reply[1] = (byte) (c.remoteID >> 24); + reply[2] = (byte) (c.remoteID >> 16); + reply[3] = (byte) (c.remoteID >> 8); + reply[4] = (byte) (c.remoteID); + + tm.sendAsynchronousMessage(reply); + } + + if (log.isEnabled()) + log.log(50, "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(); + } + + if (log.isEnabled()) + log.log(50, "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(); + } + + if (log.isEnabled()) + log.log(50, "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); - reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; - reply[1] = (byte) (c.remoteID >> 24); - reply[2] = (byte) (c.remoteID >> 16); - reply[3] = (byte) (c.remoteID >> 8); - reply[4] = (byte) (c.remoteID); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); - tm.sendAsynchronousMessage(reply); - } + synchronized (c) { + c.successCounter++; + c.notifyAll(); + } - if (log.isEnabled()) - log.log(50, "Channel request '" + type + "' is not known, ignoring it"); - } + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); + } - public void msgChannelEOF(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); + 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); + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - Channel c = getChannel(id); + Channel c = getChannel(id); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); - synchronized (c) - { - c.EOF = true; - c.notifyAll(); - } + synchronized (c) { + c.failedCounter++; + c.notifyAll(); + } - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); - } + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (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 + ")"); + public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException { + PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(sm.recipientChannelID); - Channel c = getChannel(id); + 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_CLOSE message for non-existent channel " + id); + synchronized (c) { + if (c.state != Channel.STATE_OPENING) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + + sm.recipientChannelID); - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("Close requested by remote"); - c.closeMessageRecv = true; + c.remoteID = sm.senderChannelID; + c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = sm.maxPacketSize; + c.state = Channel.STATE_OPEN; + c.notifyAll(); + } - removeChannel(c.localID); + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + + sm.senderChannelID + ")"); + } - c.notifyAll(); - } + 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 + ")"); - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); - } + TypesReader tr = new TypesReader(msg, 0, msglen); - public void msgChannelSuccess(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); + tr.readByte(); // skip packet type + int id = tr.readUINT32(); /* sender channel */ - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); - Channel c = getChannel(id); + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); + int reasonCode = tr.readUINT32(); + String description = tr.readString("UTF-8"); - synchronized (c) - { - c.successCounter++; - c.notifyAll(); - } + String reasonCodeSymbolicName = null; - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); - } + 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 + ")"; + } - public void msgChannelFailure(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); + StringBuffer descriptionBuffer = new StringBuffer(); + descriptionBuffer.append(description); - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); + for (int i = 0; i < descriptionBuffer.length(); i++) { + char cc = descriptionBuffer.charAt(i); - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); - - synchronized (c) - { - c.failedCounter++; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); - } - - public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException - { - PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); - - Channel c = getChannel(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); - - c.remoteID = sm.senderChannelID; - c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = sm.maxPacketSize; - c.state = Channel.STATE_OPEN; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "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); - - 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 + ")"; - } - - StringBuffer descriptionBuffer = new StringBuffer(); - 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(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); - } - - public void msgGlobalRequest(byte[] msg, int msglen) throws IOException - { + 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(); + } + + if (log.isEnabled()) + log.log(50, "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); + TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); // skip packet type - String requestName = tr.readString(); - boolean wantReply = tr.readBoolean(); + 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; + if (wantReply) { + byte[] reply_failure = new byte[1]; + reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; - tm.sendAsynchronousMessage(reply_failure); - } + tm.sendAsynchronousMessage(reply_failure); + } /* We do not clean up the requestName String - that is OK for debug */ - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); - } - - public void msgGlobalSuccess() throws IOException - { - synchronized (channels) - { - globalSuccessCounter++; - channels.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_REQUEST_SUCCESS"); - } - - public void msgGlobalFailure() throws IOException - { - synchronized (channels) - { - globalFailedCounter++; - channels.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_REQUEST_FAILURE"); - } - - public void handleMessage(byte[] msg, int msglen) throws IOException - { - if (msg == null) - { - if (log.isEnabled()) - log.log(50, "HandleMessage: got shutdown"); - - synchronized (listenerThreads) - { - for (int i = 0; i < listenerThreads.size(); i++) - { - IChannelWorkerThread lat = (IChannelWorkerThread) listenerThreads.elementAt(i); - lat.stopWorking(); - } - listenerThreadsAllowed = false; - } - - synchronized (channels) - { - shutdown = true; - - for (int i = 0; i < channels.size(); i++) - { - Channel c = (Channel) channels.elementAt(i); - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The connection is being shutdown"); - c.closeMessageRecv = true; /* + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); + } + + public void msgGlobalSuccess() throws IOException { + synchronized (channels) { + globalSuccessCounter++; + channels.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_REQUEST_SUCCESS"); + } + + public void msgGlobalFailure() throws IOException { + synchronized (channels) { + globalFailedCounter++; + channels.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_REQUEST_FAILURE"); + } + + public void handleMessage(byte[] msg, int msglen) throws IOException { + if (msg == null) { + if (log.isEnabled()) + log.log(50, "HandleMessage: got shutdown"); + + synchronized (listenerThreads) { + for (int i = 0; i < listenerThreads.size(); i++) { + IChannelWorkerThread lat = (IChannelWorkerThread) listenerThreads.elementAt(i); + lat.stopWorking(); + } + listenerThreadsAllowed = false; + } + + synchronized (channels) { + shutdown = true; + + for (int i = 0; i < channels.size(); i++) { + Channel c = (Channel) channels.elementAt(i); + 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(); - } - } + c.notifyAll(); + } + } /* Works with J2ME */ - channels.setSize(0); - channels.trimToSize(); - 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)); - } - } + channels.setSize(0); + channels.trimToSize(); + 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)); + } + } } diff --git a/src/ch/ethz/ssh2/channel/ChannelOutputStream.java b/src/ch/ethz/ssh2/channel/ChannelOutputStream.java index 2c2bdfa..25b1621 100644 --- a/src/ch/ethz/ssh2/channel/ChannelOutputStream.java +++ b/src/ch/ethz/ssh2/channel/ChannelOutputStream.java @@ -5,66 +5,58 @@ /** * ChannelOutputStream. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ChannelOutputStream.java,v 1.3 2005/08/11 12:47:30 cplattne Exp $ */ -public final class ChannelOutputStream extends OutputStream -{ - Channel c; - - boolean isClosed = false; - - ChannelOutputStream(Channel c) - { - this.c = c; - } - - public void write(int b) throws IOException - { - byte[] buff = new byte[1]; - - buff[0] = (byte) b; - - write(buff, 0, 1); - } - - public void close() throws IOException - { - if (isClosed == false) - { - isClosed = true; - c.cm.sendEOF(c); - } - } - - public void flush() throws IOException - { - if (isClosed) - throw new IOException("This OutputStream is closed."); +public final class ChannelOutputStream extends OutputStream { + Channel c; + + boolean isClosed = false; + + ChannelOutputStream(Channel c) { + this.c = c; + } + + public void write(int b) throws IOException { + byte[] buff = new byte[1]; + + buff[0] = (byte) b; + + write(buff, 0, 1); + } + + public void close() throws IOException { + if (isClosed == false) { + isClosed = true; + c.cm.sendEOF(c); + } + } + + public void flush() throws IOException { + if (isClosed) + throw new IOException("This OutputStream is closed."); /* This is a no-op, since this stream is unbuffered */ - } - - public void write(byte[] b, int off, int len) throws IOException - { - if (isClosed) - throw new IOException("This OutputStream is closed."); - - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return; - - c.cm.sendData(c, b, off, len); - } - - public void write(byte[] b) throws IOException - { - write(b, 0, b.length); - } + } + + public void write(byte[] b, int off, int len) throws IOException { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return; + + c.cm.sendData(c, b, off, len); + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } } diff --git a/src/ch/ethz/ssh2/channel/IChannelWorkerThread.java b/src/ch/ethz/ssh2/channel/IChannelWorkerThread.java index aa72e9d..1b9e906 100644 --- a/src/ch/ethz/ssh2/channel/IChannelWorkerThread.java +++ b/src/ch/ethz/ssh2/channel/IChannelWorkerThread.java @@ -1,13 +1,11 @@ - package ch.ethz.ssh2.channel; /** * IChannelWorkerThread. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: IChannelWorkerThread.java,v 1.2 2005/12/05 17:13:26 cplattne Exp $ */ -interface IChannelWorkerThread -{ - public void stopWorking(); +interface IChannelWorkerThread { + public void stopWorking(); } diff --git a/src/ch/ethz/ssh2/channel/LocalAcceptThread.java b/src/ch/ethz/ssh2/channel/LocalAcceptThread.java index f631081..0d85415 100644 --- a/src/ch/ethz/ssh2/channel/LocalAcceptThread.java +++ b/src/ch/ethz/ssh2/channel/LocalAcceptThread.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.channel; import java.io.IOException; @@ -7,119 +6,92 @@ /** * LocalAcceptThread. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: LocalAcceptThread.java,v 1.7 2006/07/30 21:59:29 cplattne Exp $ */ -public class LocalAcceptThread extends Thread implements IChannelWorkerThread -{ - ChannelManager cm; - int local_port; - String host_to_connect; - int port_to_connect; - - final ServerSocket ss; - - public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) - throws IOException - { - this.cm = cm; - this.local_port = local_port; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - - ss = new ServerSocket(local_port); - } - - public void run() - { - try - { - cm.registerThread(this); - } - catch (IOException e) - { - stopWorking(); - return; - } - - while (true) - { - Socket s = null; - - try - { - s = ss.accept(); - } - catch (IOException e) - { - stopWorking(); - return; - } - - Channel cn = null; - StreamForwarder r2l = null; - StreamForwarder l2r = null; - - try - { - /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ - - cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s - .getPort()); - - } - catch (IOException e) - { +public class LocalAcceptThread extends Thread implements IChannelWorkerThread { + final ServerSocket ss; + ChannelManager cm; + int local_port; + String host_to_connect; + int port_to_connect; + + public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.local_port = local_port; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + ss = new ServerSocket(local_port); + } + + public void run() { + try { + cm.registerThread(this); + } catch (IOException e) { + stopWorking(); + return; + } + + while (true) { + Socket s = null; + + try { + s = ss.accept(); + } catch (IOException e) { + stopWorking(); + return; + } + + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + try { + /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ + + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s + .getPort()); + + } catch (IOException e) { /* Simply close the local socket and wait for the next incoming connection */ - try - { - s.close(); - } - catch (IOException ignore) - { - } - - continue; - } - - try - { - r2l = new StreamForwarder(cn, null, null, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); - l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); - } - catch (IOException e) - { - try - { + try { + s.close(); + } catch (IOException ignore) { + } + + continue; + } + + try { + r2l = new StreamForwarder(cn, null, null, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); + } catch (IOException e) { + try { /* This message is only visible during debugging, since we discard the channel immediatelly */ - cn.cm.closeChannel(cn, "Weird error during creation of StreamForwarder (" + e.getMessage() + ")", - true); - } - catch (IOException ignore) - { - } - - continue; - } - - r2l.setDaemon(true); - l2r.setDaemon(true); - r2l.start(); - l2r.start(); - } - } - - public void stopWorking() - { - try - { + cn.cm.closeChannel(cn, "Weird error during creation of StreamForwarder (" + e.getMessage() + ")", + true); + } catch (IOException ignore) { + } + + continue; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public void stopWorking() { + try { /* This will lead to an IOException in the ss.accept() call */ - ss.close(); - } - catch (IOException e) - { - } - } + ss.close(); + } catch (IOException e) { + } + } } diff --git a/src/ch/ethz/ssh2/channel/RemoteAcceptThread.java b/src/ch/ethz/ssh2/channel/RemoteAcceptThread.java index 6a681b4..72e2a8c 100644 --- a/src/ch/ethz/ssh2/channel/RemoteAcceptThread.java +++ b/src/ch/ethz/ssh2/channel/RemoteAcceptThread.java @@ -1,102 +1,85 @@ - package ch.ethz.ssh2.channel; +import ch.ethz.ssh2.log.Logger; + import java.io.IOException; import java.net.Socket; -import ch.ethz.ssh2.log.Logger; - /** * RemoteAcceptThread. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RemoteAcceptThread.java,v 1.4 2006/02/13 21:19:25 cplattne Exp $ */ -public class RemoteAcceptThread extends Thread -{ - private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); - - Channel c; - - String remoteConnectedAddress; - int remoteConnectedPort; - String remoteOriginatorAddress; - int remoteOriginatorPort; - String targetAddress; - int targetPort; - - Socket s; - - public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, - String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) - { - this.c = c; - this.remoteConnectedAddress = remoteConnectedAddress; - this.remoteConnectedPort = remoteConnectedPort; - this.remoteOriginatorAddress = remoteOriginatorAddress; - this.remoteOriginatorPort = remoteOriginatorPort; - this.targetAddress = targetAddress; - this.targetPort = targetPort; - - if (log.isEnabled()) - log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " - + remoteOriginatorAddress + "/" + remoteOriginatorPort); - } - - public void run() - { - try - { - c.cm.sendOpenConfirmation(c); - - s = new Socket(targetAddress, targetPort); - - StreamForwarder r2l = new StreamForwarder(c, null, null, c.getStdoutStream(), s.getOutputStream(), - "RemoteToLocal"); - StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), - "LocalToRemote"); +public class RemoteAcceptThread extends Thread { + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + Channel c; + + String remoteConnectedAddress; + int remoteConnectedPort; + String remoteOriginatorAddress; + int remoteOriginatorPort; + String targetAddress; + int targetPort; + + Socket s; + + public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, + String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) { + this.c = c; + this.remoteConnectedAddress = remoteConnectedAddress; + this.remoteConnectedPort = remoteConnectedPort; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + this.targetAddress = targetAddress; + this.targetPort = targetPort; + + if (log.isEnabled()) + log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " + + remoteOriginatorAddress + "/" + remoteOriginatorPort); + } + + public void run() { + try { + c.cm.sendOpenConfirmation(c); + + s = new Socket(targetAddress, targetPort); + + StreamForwarder r2l = new StreamForwarder(c, null, null, c.getStdoutStream(), s.getOutputStream(), + "RemoteToLocal"); + StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), + "LocalToRemote"); /* No need to start two threads, one can be executed in the current thread */ - - r2l.setDaemon(true); - r2l.start(); - l2r.run(); - - while (r2l.isAlive()) - { - try - { - r2l.join(); - } - catch (InterruptedException e) - { - } - } + + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) { + try { + r2l.join(); + } catch (InterruptedException e) { + } + } /* If the channel is already closed, then this is a no-op */ - c.cm.closeChannel(c, "EOF on both streams reached.", true); - s.close(); - } - catch (IOException e) - { - log.log(50, "IOException in proxy code: " + e.getMessage()); - - try - { - c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true); - } - catch (IOException e1) - { - } - try - { - if (s != null) - s.close(); - } - catch (IOException e1) - { - } - } - } + c.cm.closeChannel(c, "EOF on both streams reached.", true); + s.close(); + } catch (IOException e) { + log.log(50, "IOException in proxy code: " + e.getMessage()); + + try { + c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true); + } catch (IOException e1) { + } + try { + if (s != null) + s.close(); + } catch (IOException e1) { + } + } + } } diff --git a/src/ch/ethz/ssh2/channel/RemoteForwardingData.java b/src/ch/ethz/ssh2/channel/RemoteForwardingData.java index 69724fe..2f75b6f 100644 --- a/src/ch/ethz/ssh2/channel/RemoteForwardingData.java +++ b/src/ch/ethz/ssh2/channel/RemoteForwardingData.java @@ -1,17 +1,15 @@ - package ch.ethz.ssh2.channel; /** * RemoteForwardingData. Data about a requested remote forwarding. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RemoteForwardingData.java,v 1.1 2005/12/07 10:25:48 cplattne Exp $ */ -public class RemoteForwardingData -{ - public String bindAddress; - public int bindPort; +public class RemoteForwardingData { + public String bindAddress; + public int bindPort; - String targetAddress; - int targetPort; + String targetAddress; + int targetPort; } diff --git a/src/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java b/src/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java index e16eb32..5ca8407 100644 --- a/src/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java +++ b/src/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java @@ -1,49 +1,44 @@ - package ch.ethz.ssh2.channel; +import ch.ethz.ssh2.log.Logger; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; -import ch.ethz.ssh2.log.Logger; - /** * RemoteX11AcceptThread. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RemoteX11AcceptThread.java,v 1.5 2006/02/14 15:17:37 cplattne Exp $ */ -public class RemoteX11AcceptThread extends Thread -{ - private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); +public class RemoteX11AcceptThread extends Thread { + private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); - Channel c; + Channel c; - String remoteOriginatorAddress; - int remoteOriginatorPort; + String remoteOriginatorAddress; + int remoteOriginatorPort; - Socket s; + Socket s; - public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) - { - this.c = c; - this.remoteOriginatorAddress = remoteOriginatorAddress; - this.remoteOriginatorPort = remoteOriginatorPort; - } + public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) { + this.c = c; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + } - public void run() - { - try - { - /* Send Open Confirmation */ + public void run() { + try { + /* Send Open Confirmation */ - c.cm.sendOpenConfirmation(c); + c.cm.sendOpenConfirmation(c); /* Read startup packet from client */ - OutputStream remote_os = c.getStdinStream(); - InputStream remote_is = c.getStdoutStream(); + OutputStream remote_os = c.getStdinStream(); + InputStream remote_is = c.getStdoutStream(); /* The following code is based on the protocol description given in: * Scheifler/Gettys, @@ -79,163 +74,146 @@ public void run() /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ - byte[] header = new byte[6]; + byte[] header = new byte[6]; - if (remote_is.read(header) != 6) - throw new IOException("Unexpected EOF on X11 startup!"); + if (remote_is.read(header) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); - if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first - throw new IOException("Unknown endian format in X11 message!"); + if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first + throw new IOException("Unknown endian format in X11 message!"); /* Yes, I came up with this myself - shall I file an application for a patent? =) */ - - int idxMSB = (header[0] == 0x42) ? 0 : 1; + + int idxMSB = (header[0] == 0x42) ? 0 : 1; /* Read authorization data header */ - byte[] auth_buff = new byte[6]; + byte[] auth_buff = new byte[6]; - if (remote_is.read(auth_buff) != 6) - throw new IOException("Unexpected EOF on X11 startup!"); + if (remote_is.read(auth_buff) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); - int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); - int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); + int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); + int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); - if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) - throw new IOException("Buggy X11 authorization data"); + if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) + throw new IOException("Buggy X11 authorization data"); - int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); - int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); + int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); + int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); - byte[] authProtocolName = new byte[authProtocolNameLength]; - byte[] authProtocolData = new byte[authProtocolDataLength]; + byte[] authProtocolName = new byte[authProtocolNameLength]; + byte[] authProtocolData = new byte[authProtocolDataLength]; - byte[] paddingBuffer = new byte[4]; + byte[] paddingBuffer = new byte[4]; - if (remote_is.read(authProtocolName) != authProtocolNameLength) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); + if (remote_is.read(authProtocolName) != authProtocolNameLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); - if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); + if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); - if (remote_is.read(authProtocolData) != authProtocolDataLength) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); + if (remote_is.read(authProtocolData) != authProtocolDataLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); - if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); + if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); - if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName)) == false) - throw new IOException("Unknown X11 authorization protocol!"); + if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName)) == false) + throw new IOException("Unknown X11 authorization protocol!"); - if (authProtocolDataLength != 16) - throw new IOException("Wrong data length for X11 authorization data!"); + if (authProtocolDataLength != 16) + throw new IOException("Wrong data length for X11 authorization data!"); - StringBuffer tmp = new StringBuffer(32); - for (int i = 0; i < authProtocolData.length; i++) - { - String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); - tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); - } - String hexEncodedFakeCookie = tmp.toString(); + StringBuffer tmp = new StringBuffer(32); + for (int i = 0; i < authProtocolData.length; i++) { + String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + String hexEncodedFakeCookie = tmp.toString(); /* Order is very important here - it may be that a certain x11 forwarding * gets disabled right in the moment when we check and register our connection * */ - synchronized (c) - { + synchronized (c) { /* Please read the comment in Channel.java */ - c.hexX11FakeCookie = hexEncodedFakeCookie; - } + c.hexX11FakeCookie = hexEncodedFakeCookie; + } /* Now check our fake cookie directory to see if we produced this cookie */ - X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); + X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); - if (sd == null) - throw new IOException("Invalid X11 cookie received."); + if (sd == null) + throw new IOException("Invalid X11 cookie received."); /* If the session which corresponds to this cookie is closed then we will * detect this: the session's close code will close all channels * with the session's assigned x11 fake cookie. */ - s = new Socket(sd.hostname, sd.port); + s = new Socket(sd.hostname, sd.port); - OutputStream x11_os = s.getOutputStream(); - InputStream x11_is = s.getInputStream(); + OutputStream x11_os = s.getOutputStream(); + InputStream x11_is = s.getInputStream(); /* Now we are sending the startup packet to the real X11 server */ - x11_os.write(header); + x11_os.write(header); - if (sd.x11_magic_cookie == null) - { - byte[] emptyAuthData = new byte[6]; + if (sd.x11_magic_cookie == null) { + byte[] emptyAuthData = new byte[6]; /* empty auth data, hopefully you are connecting to localhost =) */ - x11_os.write(emptyAuthData); - } - else - { - if (sd.x11_magic_cookie.length != 16) - throw new IOException("The real X11 cookie has an invalid length!"); + x11_os.write(emptyAuthData); + } else { + if (sd.x11_magic_cookie.length != 16) + throw new IOException("The real X11 cookie has an invalid length!"); /* send X11 cookie specified by client */ - x11_os.write(auth_buff); - x11_os.write(authProtocolName); /* re-use */ - x11_os.write(paddingBuffer, 0, authProtocolNamePadding); - x11_os.write(sd.x11_magic_cookie); - x11_os.write(paddingBuffer, 0, authProtocolDataPadding); - } + x11_os.write(auth_buff); + x11_os.write(authProtocolName); /* re-use */ + x11_os.write(paddingBuffer, 0, authProtocolNamePadding); + x11_os.write(sd.x11_magic_cookie); + x11_os.write(paddingBuffer, 0, authProtocolDataPadding); + } - x11_os.flush(); + x11_os.flush(); /* Start forwarding traffic */ - StreamForwarder r2l = new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11"); - StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); + StreamForwarder r2l = new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11"); + StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); /* No need to start two threads, one can be executed in the current thread */ - r2l.setDaemon(true); - r2l.start(); - l2r.run(); - - while (r2l.isAlive()) - { - try - { - r2l.join(); - } - catch (InterruptedException e) - { - } - } + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) { + try { + r2l.join(); + } catch (InterruptedException e) { + } + } /* If the channel is already closed, then this is a no-op */ - c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); - s.close(); - } - catch (IOException e) - { - log.log(50, "IOException in X11 proxy code: " + e.getMessage()); - - try - { - c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true); - } - catch (IOException e1) - { - } - try - { - if (s != null) - s.close(); - } - catch (IOException e1) - { - } - } - } + c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); + s.close(); + } catch (IOException e) { + log.log(50, "IOException in X11 proxy code: " + e.getMessage()); + + try { + c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true); + } catch (IOException e1) { + } + try { + if (s != null) + s.close(); + } catch (IOException e1) { + } + } + } } diff --git a/src/ch/ethz/ssh2/channel/StreamForwarder.java b/src/ch/ethz/ssh2/channel/StreamForwarder.java index 9ec94fa..df3f012 100644 --- a/src/ch/ethz/ssh2/channel/StreamForwarder.java +++ b/src/ch/ethz/ssh2/channel/StreamForwarder.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.channel; import java.io.IOException; @@ -7,106 +6,77 @@ import java.net.Socket; /** - * A StreamForwarder forwards data between two given streams. + * A StreamForwarder forwards data between two given streams. * If two StreamForwarder threads are used (one for each direction) * then one can be configured to shutdown the underlying channel/socket * if both threads have finished forwarding (EOF). - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: StreamForwarder.java,v 1.2 2006/02/13 21:19:25 cplattne Exp $ */ -public class StreamForwarder extends Thread -{ - OutputStream os; - InputStream is; - byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; - Channel c; - StreamForwarder sibling; - Socket s; - String mode; +public class StreamForwarder extends Thread { + OutputStream os; + InputStream is; + byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + Channel c; + StreamForwarder sibling; + Socket s; + String mode; - StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) - throws IOException - { - this.is = is; - this.os = os; - this.mode = mode; - this.c = c; - this.sibling = sibling; - this.s = s; - } + StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) + throws IOException { + this.is = is; + this.os = os; + this.mode = mode; + this.c = c; + this.sibling = sibling; + this.s = s; + } - public void run() - { - try - { - while (true) - { - int len = is.read(buffer); - if (len <= 0) - break; - os.write(buffer, 0, len); - os.flush(); - } - } - catch (IOException ignore) - { - try - { - c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): " - + ignore.getMessage(), true); - } - catch (IOException e) - { - } - } - finally - { - try - { - os.close(); - } - catch (IOException e1) - { - } - try - { - is.close(); - } - catch (IOException e2) - { - } + public void run() { + try { + while (true) { + int len = is.read(buffer); + if (len <= 0) + break; + os.write(buffer, 0, len); + os.flush(); + } + } catch (IOException ignore) { + try { + c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): " + + ignore.getMessage(), true); + } catch (IOException e) { + } + } finally { + try { + os.close(); + } catch (IOException e1) { + } + try { + is.close(); + } catch (IOException e2) { + } - if (sibling != null) - { - while (sibling.isAlive()) - { - try - { - sibling.join(); - } - catch (InterruptedException e) - { - } - } + if (sibling != null) { + while (sibling.isAlive()) { + try { + sibling.join(); + } catch (InterruptedException e) { + } + } - try - { - c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); - } - catch (IOException e3) - { - } + try { + c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); + } catch (IOException e3) { + } - try - { - if (s != null) - s.close(); - } - catch (IOException e1) - { - } - } - } - } + try { + if (s != null) + s.close(); + } catch (IOException e1) { + } + } + } + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/channel/X11ServerData.java b/src/ch/ethz/ssh2/channel/X11ServerData.java index f7d8511..157c5c3 100644 --- a/src/ch/ethz/ssh2/channel/X11ServerData.java +++ b/src/ch/ethz/ssh2/channel/X11ServerData.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.channel; /** @@ -6,11 +5,9 @@ * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: X11ServerData.java,v 1.2 2005/08/29 14:26:21 cplattne Exp $ - * */ -public class X11ServerData -{ - public String hostname; - public int port; - public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ +public class X11ServerData { + public String hostname; + public int port; + public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ } diff --git a/src/ch/ethz/ssh2/crypto/Base64.java b/src/ch/ethz/ssh2/crypto/Base64.java index fb245a9..d26522a 100644 --- a/src/ch/ethz/ssh2/crypto/Base64.java +++ b/src/ch/ethz/ssh2/crypto/Base64.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto; import java.io.CharArrayWriter; @@ -6,143 +5,116 @@ /** * Basic Base64 Support. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Base64.java,v 1.4 2005/08/11 12:47:31 cplattne Exp $ */ -public class Base64 -{ - static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); - - public static char[] encode(byte[] content) - { - CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3); - - int idx = 0; - - int x = 0; - - for (int i = 0; i < content.length; i++) - { - if (idx == 0) - x = (content[i] & 0xff) << 16; - else if (idx == 1) - x = x | ((content[i] & 0xff) << 8); - else - x = x | (content[i] & 0xff); - - idx++; - - if (idx == 3) - { - cw.write(alphabet[x >> 18]); - cw.write(alphabet[(x >> 12) & 0x3f]); - cw.write(alphabet[(x >> 6) & 0x3f]); - cw.write(alphabet[x & 0x3f]); - - idx = 0; - } - } - - if (idx == 1) - { - cw.write(alphabet[x >> 18]); - cw.write(alphabet[(x >> 12) & 0x3f]); - cw.write('='); - cw.write('='); - } - - if (idx == 2) - { - cw.write(alphabet[x >> 18]); - cw.write(alphabet[(x >> 12) & 0x3f]); - cw.write(alphabet[(x >> 6) & 0x3f]); - cw.write('='); - } - - return cw.toCharArray(); - } - - public static byte[] decode(char[] message) throws IOException - { - byte buff[] = new byte[4]; - byte dest[] = new byte[message.length]; - - int bpos = 0; - int destpos = 0; - - for (int i = 0; i < message.length; i++) - { - int c = message[i]; - - if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t')) - continue; - - if ((c >= 'A') && (c <= 'Z')) - { - buff[bpos++] = (byte) (c - 'A'); - } - else if ((c >= 'a') && (c <= 'z')) - { - buff[bpos++] = (byte) ((c - 'a') + 26); - } - else if ((c >= '0') && (c <= '9')) - { - buff[bpos++] = (byte) ((c - '0') + 52); - } - else if (c == '+') - { - buff[bpos++] = 62; - } - else if (c == '/') - { - buff[bpos++] = 63; - } - else if (c == '=') - { - buff[bpos++] = 64; - } - else - { - throw new IOException("Illegal char in base64 code."); - } - - if (bpos == 4) - { - bpos = 0; - - if (buff[0] == 64) - break; - - if (buff[1] == 64) - throw new IOException("Unexpected '=' in base64 code."); - - if (buff[2] == 64) - { - int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f))); - dest[destpos++] = (byte) (v >> 4); - break; - } - else if (buff[3] == 64) - { - int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f))); - dest[destpos++] = (byte) (v >> 10); - dest[destpos++] = (byte) (v >> 2); - break; - } - else - { - int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f))); - dest[destpos++] = (byte) (v >> 16); - dest[destpos++] = (byte) (v >> 8); - dest[destpos++] = (byte) (v); - } - } - } - - byte[] res = new byte[destpos]; - System.arraycopy(dest, 0, res, 0, destpos); - - return res; - } +public class Base64 { + static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + + public static char[] encode(byte[] content) { + CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3); + + int idx = 0; + + int x = 0; + + for (int i = 0; i < content.length; i++) { + if (idx == 0) + x = (content[i] & 0xff) << 16; + else if (idx == 1) + x = x | ((content[i] & 0xff) << 8); + else + x = x | (content[i] & 0xff); + + idx++; + + if (idx == 3) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write(alphabet[x & 0x3f]); + + idx = 0; + } + } + + if (idx == 1) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write('='); + cw.write('='); + } + + if (idx == 2) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write('='); + } + + return cw.toCharArray(); + } + + public static byte[] decode(char[] message) throws IOException { + byte buff[] = new byte[4]; + byte dest[] = new byte[message.length]; + + int bpos = 0; + int destpos = 0; + + for (int i = 0; i < message.length; i++) { + int c = message[i]; + + if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t')) + continue; + + if ((c >= 'A') && (c <= 'Z')) { + buff[bpos++] = (byte) (c - 'A'); + } else if ((c >= 'a') && (c <= 'z')) { + buff[bpos++] = (byte) ((c - 'a') + 26); + } else if ((c >= '0') && (c <= '9')) { + buff[bpos++] = (byte) ((c - '0') + 52); + } else if (c == '+') { + buff[bpos++] = 62; + } else if (c == '/') { + buff[bpos++] = 63; + } else if (c == '=') { + buff[bpos++] = 64; + } else { + throw new IOException("Illegal char in base64 code."); + } + + if (bpos == 4) { + bpos = 0; + + if (buff[0] == 64) + break; + + if (buff[1] == 64) + throw new IOException("Unexpected '=' in base64 code."); + + if (buff[2] == 64) { + int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f))); + dest[destpos++] = (byte) (v >> 4); + break; + } else if (buff[3] == 64) { + int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f))); + dest[destpos++] = (byte) (v >> 10); + dest[destpos++] = (byte) (v >> 2); + break; + } else { + int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f))); + dest[destpos++] = (byte) (v >> 16); + dest[destpos++] = (byte) (v >> 8); + dest[destpos++] = (byte) (v); + } + } + } + + byte[] res = new byte[destpos]; + System.arraycopy(dest, 0, res, 0, destpos); + + return res; + } } diff --git a/src/ch/ethz/ssh2/crypto/CryptoWishList.java b/src/ch/ethz/ssh2/crypto/CryptoWishList.java index 950c439..1335748 100644 --- a/src/ch/ethz/ssh2/crypto/CryptoWishList.java +++ b/src/ch/ethz/ssh2/crypto/CryptoWishList.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto; import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; @@ -7,16 +6,15 @@ /** * CryptoWishList. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: CryptoWishList.java,v 1.3 2005/08/24 17:54:10 cplattne Exp $ */ -public class CryptoWishList -{ - public String[] kexAlgorithms = KexManager.getDefaultKexAlgorithmList(); - public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList(); - public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList(); - public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); - public String[] c2s_mac_algos = MAC.getMacList(); - public String[] s2c_mac_algos = MAC.getMacList(); +public class CryptoWishList { + public String[] kexAlgorithms = KexManager.getDefaultKexAlgorithmList(); + public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList(); + public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] c2s_mac_algos = MAC.getMacList(); + public String[] s2c_mac_algos = MAC.getMacList(); } diff --git a/src/ch/ethz/ssh2/crypto/KeyMaterial.java b/src/ch/ethz/ssh2/crypto/KeyMaterial.java index 209a8eb..c647dd2 100644 --- a/src/ch/ethz/ssh2/crypto/KeyMaterial.java +++ b/src/ch/ethz/ssh2/crypto/KeyMaterial.java @@ -1,91 +1,86 @@ - package ch.ethz.ssh2.crypto; -import java.math.BigInteger; - import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; +import java.math.BigInteger; + /** * Establishes key material for iv/key/mac (both directions). - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: KeyMaterial.java,v 1.2 2005/12/05 17:13:27 cplattne Exp $ */ -public class KeyMaterial -{ - public byte[] initial_iv_client_to_server; - public byte[] initial_iv_server_to_client; - public byte[] enc_key_client_to_server; - public byte[] enc_key_server_to_client; - public byte[] integrity_key_client_to_server; - public byte[] integrity_key_server_to_client; +public class KeyMaterial { + public byte[] initial_iv_client_to_server; + public byte[] initial_iv_server_to_client; + public byte[] enc_key_client_to_server; + public byte[] enc_key_server_to_client; + public byte[] integrity_key_client_to_server; + public byte[] integrity_key_server_to_client; - private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID, - int keyLength) - { - byte[] res = new byte[keyLength]; + private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID, + int keyLength) { + byte[] res = new byte[keyLength]; - int dglen = sh.getDigestLength(); - int numRounds = (keyLength + dglen - 1) / dglen; + int dglen = sh.getDigestLength(); + int numRounds = (keyLength + dglen - 1) / dglen; - byte[][] tmp = new byte[numRounds][]; + byte[][] tmp = new byte[numRounds][]; - sh.reset(); - sh.updateBigInt(K); - sh.updateBytes(H); - sh.updateByte(type); - sh.updateBytes(SessionID); + sh.reset(); + sh.updateBigInt(K); + sh.updateBytes(H); + sh.updateByte(type); + sh.updateBytes(SessionID); - tmp[0] = sh.getDigest(); + tmp[0] = sh.getDigest(); - int off = 0; - int produced = Math.min(dglen, keyLength); + int off = 0; + int produced = Math.min(dglen, keyLength); - System.arraycopy(tmp[0], 0, res, off, produced); + System.arraycopy(tmp[0], 0, res, off, produced); - keyLength -= produced; - off += produced; + keyLength -= produced; + off += produced; - for (int i = 1; i < numRounds; i++) - { - sh.updateBigInt(K); - sh.updateBytes(H); + for (int i = 1; i < numRounds; i++) { + sh.updateBigInt(K); + sh.updateBytes(H); - for (int j = 0; j < i; j++) - sh.updateBytes(tmp[j]); + for (int j = 0; j < i; j++) + sh.updateBytes(tmp[j]); - tmp[i] = sh.getDigest(); + tmp[i] = sh.getDigest(); - produced = Math.min(dglen, keyLength); - System.arraycopy(tmp[i], 0, res, off, produced); - keyLength -= produced; - off += produced; - } + produced = Math.min(dglen, keyLength); + System.arraycopy(tmp[i], 0, res, off, produced); + keyLength -= produced; + off += produced; + } - return res; - } + return res; + } - public static KeyMaterial create(String hashType, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS, - int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC) - throws IllegalArgumentException - { - KeyMaterial km = new KeyMaterial(); + public static KeyMaterial create(String hashType, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS, + int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC) + throws IllegalArgumentException { + KeyMaterial km = new KeyMaterial(); - HashForSSH2Types sh = new HashForSSH2Types(hashType); + HashForSSH2Types sh = new HashForSSH2Types(hashType); - km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS); + km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS); - km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC); + km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC); - km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS); + km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS); - km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC); + km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC); - km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS); + km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS); - km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC); + km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC); - return km; - } + return km; + } } diff --git a/src/ch/ethz/ssh2/crypto/PEMDecoder.java b/src/ch/ethz/ssh2/crypto/PEMDecoder.java index 1cb1d69..3838e1b 100644 --- a/src/ch/ethz/ssh2/crypto/PEMDecoder.java +++ b/src/ch/ethz/ssh2/crypto/PEMDecoder.java @@ -1,376 +1,334 @@ - package ch.ethz.ssh2.crypto; +import ch.ethz.ssh2.crypto.cipher.*; +import ch.ethz.ssh2.crypto.digest.MD5; +import ch.ethz.ssh2.signature.DSAPrivateKey; +import ch.ethz.ssh2.signature.RSAPrivateKey; + import java.io.BufferedReader; import java.io.CharArrayReader; import java.io.IOException; import java.math.BigInteger; -import ch.ethz.ssh2.crypto.cipher.AES; -import ch.ethz.ssh2.crypto.cipher.BlockCipher; -import ch.ethz.ssh2.crypto.cipher.CBCMode; -import ch.ethz.ssh2.crypto.cipher.DES; -import ch.ethz.ssh2.crypto.cipher.DESede; -import ch.ethz.ssh2.crypto.digest.MD5; -import ch.ethz.ssh2.signature.DSAPrivateKey; -import ch.ethz.ssh2.signature.RSAPrivateKey; - /** * PEM Support. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PEMDecoder.java,v 1.7 2006/02/02 09:11:03 cplattne Exp $ */ -public class PEMDecoder -{ - private static final int PEM_RSA_PRIVATE_KEY = 1; - private static final int PEM_DSA_PRIVATE_KEY = 2; +public class PEMDecoder { + private static final int PEM_RSA_PRIVATE_KEY = 1; + private static final int PEM_DSA_PRIVATE_KEY = 2; - private static final int hexToInt(char c) - { - if ((c >= 'a') && (c <= 'f')) - { - return (c - 'a') + 10; - } + private static final int hexToInt(char c) { + if ((c >= 'a') && (c <= 'f')) { + return (c - 'a') + 10; + } - if ((c >= 'A') && (c <= 'F')) - { - return (c - 'A') + 10; - } + if ((c >= 'A') && (c <= 'F')) { + return (c - 'A') + 10; + } - if ((c >= '0') && (c <= '9')) - { - return (c - '0'); - } + if ((c >= '0') && (c <= '9')) { + return (c - '0'); + } - throw new IllegalArgumentException("Need hex char"); - } + throw new IllegalArgumentException("Need hex char"); + } - private static byte[] hexToByteArray(String hex) - { - if (hex == null) - throw new IllegalArgumentException("null argument"); + private static byte[] hexToByteArray(String hex) { + if (hex == null) + throw new IllegalArgumentException("null argument"); - if ((hex.length() % 2) != 0) - throw new IllegalArgumentException("Uneven string length in hex encoding."); + if ((hex.length() % 2) != 0) + throw new IllegalArgumentException("Uneven string length in hex encoding."); - byte decoded[] = new byte[hex.length() / 2]; + byte decoded[] = new byte[hex.length() / 2]; - for (int i = 0; i < decoded.length; i++) - { - int hi = hexToInt(hex.charAt(i * 2)); - int lo = hexToInt(hex.charAt((i * 2) + 1)); + for (int i = 0; i < decoded.length; i++) { + int hi = hexToInt(hex.charAt(i * 2)); + int lo = hexToInt(hex.charAt((i * 2) + 1)); - decoded[i] = (byte) (hi * 16 + lo); - } + decoded[i] = (byte) (hi * 16 + lo); + } - return decoded; - } + return decoded; + } - private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) - throws IOException - { - if (salt.length < 8) - throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); + private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) + throws IOException { + if (salt.length < 8) + throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); - MD5 md5 = new MD5(); + MD5 md5 = new MD5(); - byte[] key = new byte[keyLen]; - byte[] tmp = new byte[md5.getDigestLength()]; + byte[] key = new byte[keyLen]; + byte[] tmp = new byte[md5.getDigestLength()]; - while (true) - { - md5.update(password, 0, password.length); - md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the salt in this step. - // This took me two hours until I got AES-xxx running. + while (true) { + md5.update(password, 0, password.length); + md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the salt in this step. + // This took me two hours until I got AES-xxx running. - int copy = (keyLen < tmp.length) ? keyLen : tmp.length; + int copy = (keyLen < tmp.length) ? keyLen : tmp.length; - md5.digest(tmp, 0); + md5.digest(tmp, 0); - System.arraycopy(tmp, 0, key, key.length - keyLen, copy); + System.arraycopy(tmp, 0, key, key.length - keyLen, copy); - keyLen -= copy; + keyLen -= copy; - if (keyLen == 0) - return key; + if (keyLen == 0) + return key; - md5.update(tmp, 0, tmp.length); - } - } + md5.update(tmp, 0, tmp.length); + } + } - private static byte[] removePadding(byte[] buff, int blockSize) throws IOException - { - /* Removes RFC 1423/PKCS #7 padding */ + private static byte[] removePadding(byte[] buff, int blockSize) throws IOException { + /* Removes RFC 1423/PKCS #7 padding */ - int rfc_1423_padding = buff[buff.length - 1] & 0xff; + int rfc_1423_padding = buff[buff.length - 1] & 0xff; - if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) - throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); - for (int i = 2; i <= rfc_1423_padding; i++) - { - if (buff[buff.length - i] != rfc_1423_padding) - throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); - } + for (int i = 2; i <= rfc_1423_padding; i++) { + if (buff[buff.length - i] != rfc_1423_padding) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + } - byte[] tmp = new byte[buff.length - rfc_1423_padding]; - System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); - return tmp; - } + byte[] tmp = new byte[buff.length - rfc_1423_padding]; + System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); + return tmp; + } - private static final PEMStructure parsePEM(char[] pem) throws IOException - { - PEMStructure ps = new PEMStructure(); + private static final PEMStructure parsePEM(char[] pem) throws IOException { + PEMStructure ps = new PEMStructure(); - String line = null; + String line = null; - BufferedReader br = new BufferedReader(new CharArrayReader(pem)); + BufferedReader br = new BufferedReader(new CharArrayReader(pem)); - String endLine = null; + String endLine = null; - while (true) - { - line = br.readLine(); + while (true) { + line = br.readLine(); - if (line == null) - throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); + if (line == null) + throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); - line = line.trim(); + line = line.trim(); - if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) - { - endLine = "-----END DSA PRIVATE KEY-----"; - ps.pemType = PEM_DSA_PRIVATE_KEY; - break; - } + if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) { + endLine = "-----END DSA PRIVATE KEY-----"; + ps.pemType = PEM_DSA_PRIVATE_KEY; + break; + } - if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) - { - endLine = "-----END RSA PRIVATE KEY-----"; - ps.pemType = PEM_RSA_PRIVATE_KEY; - break; - } - } + if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) { + endLine = "-----END RSA PRIVATE KEY-----"; + ps.pemType = PEM_RSA_PRIVATE_KEY; + break; + } + } - while (true) - { - line = br.readLine(); + while (true) { + line = br.readLine(); - if (line == null) - throw new IOException("Invalid PEM structure, " + endLine + " missing"); + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); - line = line.trim(); + line = line.trim(); - int sem_idx = line.indexOf(':'); + int sem_idx = line.indexOf(':'); - if (sem_idx == -1) - break; + if (sem_idx == -1) + break; - String name = line.substring(0, sem_idx + 1); - String value = line.substring(sem_idx + 1); + String name = line.substring(0, sem_idx + 1); + String value = line.substring(sem_idx + 1); - String values[] = value.split(","); + String values[] = value.split(","); - for (int i = 0; i < values.length; i++) - values[i] = values[i].trim(); + for (int i = 0; i < values.length; i++) + values[i] = values[i].trim(); - // Proc-Type: 4,ENCRYPTED - // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 + // Proc-Type: 4,ENCRYPTED + // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 - if ("Proc-Type:".equals(name)) - { - ps.procType = values; - continue; - } + if ("Proc-Type:".equals(name)) { + ps.procType = values; + continue; + } - if ("DEK-Info:".equals(name)) - { - ps.dekInfo = values; - continue; - } + if ("DEK-Info:".equals(name)) { + ps.dekInfo = values; + continue; + } /* Ignore line */ - } - - StringBuffer keyData = new StringBuffer(); - - while (true) - { - if (line == null) - throw new IOException("Invalid PEM structure, " + endLine + " missing"); - - line = line.trim(); - - if (line.startsWith(endLine)) - break; - - keyData.append(line); - - line = br.readLine(); - } - - char[] pem_chars = new char[keyData.length()]; - keyData.getChars(0, pem_chars.length, pem_chars, 0); - - ps.data = Base64.decode(pem_chars); - - if (ps.data.length == 0) - throw new IOException("Invalid PEM structure, no data available"); - - return ps; - } - - private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException - { - if (ps.dekInfo == null) - throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); - - if (ps.dekInfo.length != 2) - throw new IOException("Broken PEM, DEK-Info is incomplete!"); - - String algo = ps.dekInfo[0]; - byte[] salt = hexToByteArray(ps.dekInfo[1]); - - BlockCipher bc = null; - - if (algo.equals("DES-EDE3-CBC")) - { - DESede des3 = new DESede(); - des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); - bc = new CBCMode(des3, salt, false); - } - else if (algo.equals("DES-CBC")) - { - DES des = new DES(); - des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); - bc = new CBCMode(des, salt, false); - } - else if (algo.equals("AES-128-CBC")) - { - AES aes = new AES(); - aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); - bc = new CBCMode(aes, salt, false); - } - else if (algo.equals("AES-192-CBC")) - { - AES aes = new AES(); - aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); - bc = new CBCMode(aes, salt, false); - } - else if (algo.equals("AES-256-CBC")) - { - AES aes = new AES(); - aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); - bc = new CBCMode(aes, salt, false); - } - else - { - throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); - } - - if ((ps.data.length % bc.getBlockSize()) != 0) - throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " - + bc.getBlockSize()); + } + + StringBuffer keyData = new StringBuffer(); + + while (true) { + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + + if (line.startsWith(endLine)) + break; + + keyData.append(line); + + line = br.readLine(); + } + + char[] pem_chars = new char[keyData.length()]; + keyData.getChars(0, pem_chars.length, pem_chars, 0); + + ps.data = Base64.decode(pem_chars); + + if (ps.data.length == 0) + throw new IOException("Invalid PEM structure, no data available"); + + return ps; + } + + private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException { + if (ps.dekInfo == null) + throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); + + if (ps.dekInfo.length != 2) + throw new IOException("Broken PEM, DEK-Info is incomplete!"); + + String algo = ps.dekInfo[0]; + byte[] salt = hexToByteArray(ps.dekInfo[1]); + + BlockCipher bc = null; + + if (algo.equals("DES-EDE3-CBC")) { + DESede des3 = new DESede(); + des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(des3, salt, false); + } else if (algo.equals("DES-CBC")) { + DES des = new DES(); + des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); + bc = new CBCMode(des, salt, false); + } else if (algo.equals("AES-128-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); + bc = new CBCMode(aes, salt, false); + } else if (algo.equals("AES-192-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(aes, salt, false); + } else if (algo.equals("AES-256-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); + bc = new CBCMode(aes, salt, false); + } else { + throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); + } + + if ((ps.data.length % bc.getBlockSize()) != 0) + throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " + + bc.getBlockSize()); /* Now decrypt the content */ - byte[] dz = new byte[ps.data.length]; + byte[] dz = new byte[ps.data.length]; - for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) - { - bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); - } + for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) { + bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); + } /* Now check and remove RFC 1423/PKCS #7 padding */ - dz = removePadding(dz, bc.getBlockSize()); + dz = removePadding(dz, bc.getBlockSize()); - ps.data = dz; - ps.dekInfo = null; - ps.procType = null; - } + ps.data = dz; + ps.dekInfo = null; + ps.procType = null; + } - public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException - { - if (ps.procType == null) - return false; + public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException { + if (ps.procType == null) + return false; - if (ps.procType.length != 2) - throw new IOException("Unknown Proc-Type field."); + if (ps.procType.length != 2) + throw new IOException("Unknown Proc-Type field."); - if ("4".equals(ps.procType[0]) == false) - throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); + if ("4".equals(ps.procType[0]) == false) + throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); - if ("ENCRYPTED".equals(ps.procType[1])) - return true; + if ("ENCRYPTED".equals(ps.procType[1])) + return true; - return false; - } + return false; + } - public static Object decode(char[] pem, String password) throws IOException - { - PEMStructure ps = parsePEM(pem); + public static Object decode(char[] pem, String password) throws IOException { + PEMStructure ps = parsePEM(pem); - if (isPEMEncrypted(ps)) - { - if (password == null) - throw new IOException("PEM is encrypted, but no password was specified"); + if (isPEMEncrypted(ps)) { + if (password == null) + throw new IOException("PEM is encrypted, but no password was specified"); - decryptPEM(ps, password.getBytes()); - } + decryptPEM(ps, password.getBytes()); + } - if (ps.pemType == PEM_DSA_PRIVATE_KEY) - { - SimpleDERReader dr = new SimpleDERReader(ps.data); + if (ps.pemType == PEM_DSA_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); - byte[] seq = dr.readSequenceAsByteArray(); + byte[] seq = dr.readSequenceAsByteArray(); - if (dr.available() != 0) - throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); - dr.resetInput(seq); + dr.resetInput(seq); - BigInteger version = dr.readInt(); + BigInteger version = dr.readInt(); - if (version.compareTo(BigInteger.ZERO) != 0) - throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); + if (version.compareTo(BigInteger.ZERO) != 0) + throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); - BigInteger p = dr.readInt(); - BigInteger q = dr.readInt(); - BigInteger g = dr.readInt(); - BigInteger y = dr.readInt(); - BigInteger x = dr.readInt(); + BigInteger p = dr.readInt(); + BigInteger q = dr.readInt(); + BigInteger g = dr.readInt(); + BigInteger y = dr.readInt(); + BigInteger x = dr.readInt(); - if (dr.available() != 0) - throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); - return new DSAPrivateKey(p, q, g, y, x); - } + return new DSAPrivateKey(p, q, g, y, x); + } - if (ps.pemType == PEM_RSA_PRIVATE_KEY) - { - SimpleDERReader dr = new SimpleDERReader(ps.data); + if (ps.pemType == PEM_RSA_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); - byte[] seq = dr.readSequenceAsByteArray(); + byte[] seq = dr.readSequenceAsByteArray(); - if (dr.available() != 0) - throw new IOException("Padding in RSA PRIVATE KEY DER stream."); + if (dr.available() != 0) + throw new IOException("Padding in RSA PRIVATE KEY DER stream."); - dr.resetInput(seq); + dr.resetInput(seq); - BigInteger version = dr.readInt(); + BigInteger version = dr.readInt(); - if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) - throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); + if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); - BigInteger n = dr.readInt(); - BigInteger e = dr.readInt(); - BigInteger d = dr.readInt(); + BigInteger n = dr.readInt(); + BigInteger e = dr.readInt(); + BigInteger d = dr.readInt(); - return new RSAPrivateKey(d, e, n); - } + return new RSAPrivateKey(d, e, n); + } - throw new IOException("PEM problem: it is of unknown type"); - } + throw new IOException("PEM problem: it is of unknown type"); + } } diff --git a/src/ch/ethz/ssh2/crypto/PEMStructure.java b/src/ch/ethz/ssh2/crypto/PEMStructure.java index 68a6372..d05ce54 100644 --- a/src/ch/ethz/ssh2/crypto/PEMStructure.java +++ b/src/ch/ethz/ssh2/crypto/PEMStructure.java @@ -1,17 +1,15 @@ - package ch.ethz.ssh2.crypto; /** * Parsed PEM structure. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PEMStructure.java,v 1.1 2005/08/11 12:47:31 cplattne Exp $ */ -public class PEMStructure -{ - int pemType; - String dekInfo[]; - String procType[]; - byte[] data; +public class PEMStructure { + int pemType; + String dekInfo[]; + String procType[]; + byte[] data; } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/crypto/SimpleDERReader.java b/src/ch/ethz/ssh2/crypto/SimpleDERReader.java index 5d2d276..9d5aef8 100644 --- a/src/ch/ethz/ssh2/crypto/SimpleDERReader.java +++ b/src/ch/ethz/ssh2/crypto/SimpleDERReader.java @@ -5,155 +5,141 @@ /** * SimpleDERReader. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SimpleDERReader.java,v 1.3 2006/08/11 12:24:00 cplattne Exp $ */ -public class SimpleDERReader -{ - byte[] buffer; - int pos; - int count; - - public SimpleDERReader(byte[] b) - { - resetInput(b); - } - - public SimpleDERReader(byte[] b, int off, int len) - { - resetInput(b, off, len); - } - - public void resetInput(byte[] b) - { - resetInput(b, 0, b.length); - } - - public void resetInput(byte[] b, int off, int len) - { - buffer = b; - pos = off; - count = len; - } - - private byte readByte() throws IOException - { - if (count <= 0) - throw new IOException("DER byte array: out of data"); - count--; - return buffer[pos++]; - } - - private byte[] readBytes(int len) throws IOException - { - if (len > count) - throw new IOException("DER byte array: out of data"); - - byte[] b = new byte[len]; - - System.arraycopy(buffer, pos, b, 0, len); - - pos += len; - count -= len; - - return b; - } - - public int available() - { - return count; - } - - private int readLength() throws IOException - { - int len = readByte() & 0xff; - - if ((len & 0x80) == 0) - return len; - - int remain = len & 0x7F; - - if (remain == 0) - return -1; - - len = 0; - - while (remain > 0) - { - len = len << 8; - len = len | (readByte() & 0xff); - remain--; - } - - return len; - } - - public int ignoreNextObject() throws IOException - { - int type = readByte() & 0xff; - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - readBytes(len); - - return type; - } - - public BigInteger readInt() throws IOException - { - int type = readByte() & 0xff; - - if (type != 0x02) - throw new IOException("Expected DER Integer, but found type " + type); - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - - BigInteger bi = new BigInteger(b); - - return bi; - } - - public byte[] readSequenceAsByteArray() throws IOException - { - int type = readByte() & 0xff; - - if (type != 0x30) - throw new IOException("Expected DER Sequence, but found type " + type); - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - - return b; - } - - public byte[] readOctetString() throws IOException - { - int type = readByte() & 0xff; - - if (type != 0x04) - throw new IOException("Expected DER Octetstring, but found type " + type); - - int len = readLength(); - - if ((len < 0) || len > available()) - throw new IOException("Illegal len in DER object (" + len + ")"); - - byte[] b = readBytes(len); - - return b; - } +public class SimpleDERReader { + byte[] buffer; + int pos; + int count; + + public SimpleDERReader(byte[] b) { + resetInput(b); + } + + public SimpleDERReader(byte[] b, int off, int len) { + resetInput(b, off, len); + } + + public void resetInput(byte[] b) { + resetInput(b, 0, b.length); + } + + public void resetInput(byte[] b, int off, int len) { + buffer = b; + pos = off; + count = len; + } + + private byte readByte() throws IOException { + if (count <= 0) + throw new IOException("DER byte array: out of data"); + count--; + return buffer[pos++]; + } + + private byte[] readBytes(int len) throws IOException { + if (len > count) + throw new IOException("DER byte array: out of data"); + + byte[] b = new byte[len]; + + System.arraycopy(buffer, pos, b, 0, len); + + pos += len; + count -= len; + + return b; + } + + public int available() { + return count; + } + + private int readLength() throws IOException { + int len = readByte() & 0xff; + + if ((len & 0x80) == 0) + return len; + + int remain = len & 0x7F; + + if (remain == 0) + return -1; + + len = 0; + + while (remain > 0) { + len = len << 8; + len = len | (readByte() & 0xff); + remain--; + } + + return len; + } + + public int ignoreNextObject() throws IOException { + int type = readByte() & 0xff; + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + readBytes(len); + + return type; + } + + public BigInteger readInt() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x02) + throw new IOException("Expected DER Integer, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + BigInteger bi = new BigInteger(b); + + return bi; + } + + public byte[] readSequenceAsByteArray() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x30) + throw new IOException("Expected DER Sequence, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + return b; + } + + public byte[] readOctetString() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x04) + throw new IOException("Expected DER Octetstring, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + + return b; + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/AES.java b/src/ch/ethz/ssh2/crypto/cipher/AES.java index ad09d23..9f042e1 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/AES.java +++ b/src/ch/ethz/ssh2/crypto/cipher/AES.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto.cipher; /* @@ -33,358 +32,348 @@ of this software and associated documentation files (the "Software"), to deal * For further details see: http://csrc.nist.gov/encryption/aes/ * . - * + *

    * This implementation is based on optimizations from Dr. Brian Gladman's paper * and C code at http://fp.gladman.plus.com/cryptography_technology/rijndael/ * - * + *

    * There are three levels of tradeoff of speed vs memory Because java has no * preprocessor, they are written as three separate classes from which to choose - * + *

    * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 * 256 word tables for encryption and 4 for decryption. - * + *

    * The middle performance version uses only one 256 word table for each, for a * total of 2Kbytes, adding 12 rotate operations per round to compute the values * contained in the other tables from the contents of the first - * + *

    * The slowest version uses no static tables at all and computes the values in * each round *

    * This file contains the fast version with 8Kbytes of static tables for round * precomputation - * + * * @author See comments in the source file * @version $Id: AES.java,v 1.4 2006/02/02 09:11:03 cplattne Exp $ */ -public class AES implements BlockCipher -{ - // The S box - private static final byte[] S = { (byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, - (byte) 111, (byte) 197, (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, - (byte) 118, (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, - (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, (byte) 183, - (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, (byte) 52, (byte) 165, - (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, (byte) 4, (byte) 199, (byte) 35, - (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, (byte) 7, (byte) 18, (byte) 128, (byte) 226, - (byte) 235, (byte) 39, (byte) 178, (byte) 117, (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, - (byte) 110, (byte) 90, (byte) 160, (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, - (byte) 47, (byte) 132, (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, - (byte) 91, (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, - (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, (byte) 69, - (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, (byte) 81, (byte) 163, - (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, (byte) 188, (byte) 182, (byte) 218, - (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, (byte) 205, (byte) 12, (byte) 19, (byte) 236, - (byte) 95, (byte) 151, (byte) 68, (byte) 23, (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, - (byte) 93, (byte) 25, (byte) 115, (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, - (byte) 144, (byte) 136, (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, - (byte) 219, (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, - (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, (byte) 231, - (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, (byte) 108, (byte) 86, - (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, (byte) 186, (byte) 120, (byte) 37, - (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, (byte) 232, (byte) 221, (byte) 116, (byte) 31, - (byte) 75, (byte) 189, (byte) 139, (byte) 138, (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, - (byte) 3, (byte) 246, (byte) 14, (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, - (byte) 29, (byte) 158, (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, - (byte) 148, (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, - (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, (byte) 65, - (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22, }; - - // The inverse S-box - private static final byte[] Si = { (byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, - (byte) 56, (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, - (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, (byte) 52, - (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, (byte) 84, (byte) 123, - (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, (byte) 238, (byte) 76, (byte) 149, - (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, (byte) 8, (byte) 46, (byte) 161, (byte) 102, - (byte) 40, (byte) 217, (byte) 36, (byte) 178, (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, - (byte) 139, (byte) 209, (byte) 37, (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, - (byte) 152, (byte) 22, (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, - (byte) 146, (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, - (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, (byte) 144, - (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, (byte) 247, (byte) 228, - (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, (byte) 208, (byte) 44, (byte) 30, - (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, (byte) 193, (byte) 175, (byte) 189, (byte) 3, - (byte) 1, (byte) 19, (byte) 138, (byte) 107, (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, - (byte) 103, (byte) 220, (byte) 234, (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, - (byte) 230, (byte) 115, (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, - (byte) 133, (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, - (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, (byte) 111, - (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, (byte) 252, (byte) 86, - (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, (byte) 154, (byte) 219, (byte) 192, - (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, (byte) 31, (byte) 221, (byte) 168, (byte) 51, - (byte) 136, (byte) 7, (byte) 199, (byte) 49, (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, - (byte) 128, (byte) 236, (byte) 95, (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, - (byte) 74, (byte) 13, (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, - (byte) 239, (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, - (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, (byte) 23, - (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, (byte) 225, (byte) 105, - (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125, }; - - // vector used in calculating key schedule (powers of x in GF(256)) - private static final int[] rcon = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, - 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; - - // precomputation tables of calculations for rounds - private static final int[] T0 = { 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, - 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, - 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, - 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, - 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, - 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 0x65232346, - 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, - 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, - 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, - 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, - 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, - 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, - 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, - 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, - 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, - 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, - 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, - 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, - 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, - 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, - 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, - 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, - 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, - 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, - 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, - 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, - 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, - 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, - 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c }; - - private static final int[] T1 = { 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, - 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, - 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, - 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, - 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, - 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, 0x23234665, - 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, - 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, - 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, - 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, - 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, - 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, - 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, - 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, - 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, - 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, - 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, - 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, - 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, - 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, - 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, - 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, - 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, - 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, - 0x03030605, 0xf6f6f701, 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, - 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, - 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, - 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, - 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a }; - - private static final int[] T2 = { 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, - 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, - 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, - 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, - 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, - 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, 0x23466523, - 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, - 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, - 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, - 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, - 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, - 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, - 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, - 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, - 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, - 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, - 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, - 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, - 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, - 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, - 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, - 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, - 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, - 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, - 0x03060503, 0xf6f701f6, 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, - 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, - 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, - 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, - 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16 }; - - private static final int[] T3 = { 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, - 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, - 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, - 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, - 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, - 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, 0x46652323, - 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, - 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, - 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, - 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, - 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, - 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, - 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, - 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, - 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, - 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, - 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, - 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, - 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, - 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, - 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, - 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, - 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, - 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, - 0x06050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, - 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, - 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, - 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, - 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616 }; - - private static final int[] Tinv0 = { 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, - 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, - 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, - 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, - 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, - 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, - 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, - 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, - 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, - 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604, - 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, - 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, - 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, - 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, - 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, - 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, - 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, - 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, - 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, - 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, - 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, - 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, - 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, - 0x4a9804f1, 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, - 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, - 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, - 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, - 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, - 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0 }; - - private static final int[] Tinv1 = { 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, - 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, - 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, - 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, - 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, - 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, - 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, - 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, - 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, - 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, - 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, - 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, - 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, - 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, - 0x171b121d, 0x0d090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, 0xdd99eebb, 0x607fa3fd, - 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, - 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, - 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, - 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, - 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, - 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, - 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, - 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, - 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x0496e4df, 0xb5d19ee3, - 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, - 0x5610e933, 0x47d66d13, 0x61d79a8c, 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, - 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, - 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, - 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042 }; - - private static final int[] Tinv2 = { 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, - 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, - 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, - 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, - 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, - 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, 0xde94876c, - 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, - 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, - 0x0506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, - 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x06046fd4, - 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, - 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, - 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, - 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, - 0x1b121d17, 0x090e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, - 0x01f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, - 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, - 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, - 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, - 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, - 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, - 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, - 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, - 0x04f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, - 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, 0x67b35a1d, 0xdb9252d2, - 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, - 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, - 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, - 0x0c08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257 }; - - private static final int[] Tinv3 = { 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, - 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, - 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, - 0x03e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, - 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, - 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, - 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, - 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, - 0x06d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, 0xa475ebf6, 0x0b39ec83, 0x40aaef60, - 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x046fd406, - 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, 0x79dbeec8, - 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, - 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, - 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, - 0x121d171b, 0x0e0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, - 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, - 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, - 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, - 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, - 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, - 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, - 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, - 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, - 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, - 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, - 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, - 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, - 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, - 0x08deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8 }; - - private final int shift(int r, int shift) - { - return (((r >>> shift) | (r << (32 - shift)))); - } +public class AES implements BlockCipher { + // The S box + private static final byte[] S = {(byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, + (byte) 111, (byte) 197, (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, + (byte) 118, (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, + (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, (byte) 183, + (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, (byte) 52, (byte) 165, + (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, (byte) 4, (byte) 199, (byte) 35, + (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, (byte) 7, (byte) 18, (byte) 128, (byte) 226, + (byte) 235, (byte) 39, (byte) 178, (byte) 117, (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, + (byte) 110, (byte) 90, (byte) 160, (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, + (byte) 47, (byte) 132, (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, + (byte) 91, (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, + (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, (byte) 69, + (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, (byte) 81, (byte) 163, + (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, (byte) 188, (byte) 182, (byte) 218, + (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, (byte) 205, (byte) 12, (byte) 19, (byte) 236, + (byte) 95, (byte) 151, (byte) 68, (byte) 23, (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, + (byte) 93, (byte) 25, (byte) 115, (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, + (byte) 144, (byte) 136, (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, + (byte) 219, (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, + (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, (byte) 231, + (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, (byte) 108, (byte) 86, + (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, (byte) 186, (byte) 120, (byte) 37, + (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, (byte) 232, (byte) 221, (byte) 116, (byte) 31, + (byte) 75, (byte) 189, (byte) 139, (byte) 138, (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, + (byte) 3, (byte) 246, (byte) 14, (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, + (byte) 29, (byte) 158, (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, + (byte) 148, (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, + (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, (byte) 65, + (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22,}; + + // The inverse S-box + private static final byte[] Si = {(byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, + (byte) 56, (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, + (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, (byte) 52, + (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, (byte) 84, (byte) 123, + (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, (byte) 238, (byte) 76, (byte) 149, + (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, (byte) 8, (byte) 46, (byte) 161, (byte) 102, + (byte) 40, (byte) 217, (byte) 36, (byte) 178, (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, + (byte) 139, (byte) 209, (byte) 37, (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, + (byte) 152, (byte) 22, (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, + (byte) 146, (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, + (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, (byte) 144, + (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, (byte) 247, (byte) 228, + (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, (byte) 208, (byte) 44, (byte) 30, + (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, (byte) 193, (byte) 175, (byte) 189, (byte) 3, + (byte) 1, (byte) 19, (byte) 138, (byte) 107, (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, + (byte) 103, (byte) 220, (byte) 234, (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, + (byte) 230, (byte) 115, (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, + (byte) 133, (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, + (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, (byte) 111, + (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, (byte) 252, (byte) 86, + (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, (byte) 154, (byte) 219, (byte) 192, + (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, (byte) 31, (byte) 221, (byte) 168, (byte) 51, + (byte) 136, (byte) 7, (byte) 199, (byte) 49, (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, + (byte) 128, (byte) 236, (byte) 95, (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, + (byte) 74, (byte) 13, (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, + (byte) 239, (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, + (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, (byte) 23, + (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, (byte) 225, (byte) 105, + (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125,}; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, + 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91}; + + // precomputation tables of calculations for rounds + private static final int[] T0 = {0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, + 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, + 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, + 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, + 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 0x65232346, + 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, + 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, + 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, + 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, + 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, + 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, + 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, + 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, + 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, + 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, + 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, + 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, + 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, + 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, + 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, + 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, + 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c}; + + private static final int[] T1 = {0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, + 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, + 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, + 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, + 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, 0x23234665, + 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, + 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, + 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, + 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, + 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, + 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, + 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, + 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, + 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, + 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, + 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, + 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, + 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, + 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, + 0x03030605, 0xf6f6f701, 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, + 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, + 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, + 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a}; + + private static final int[] T2 = {0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, + 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, + 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, + 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, + 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, 0x23466523, + 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, + 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, + 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, + 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, + 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, + 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, + 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, + 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, + 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, + 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, + 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, + 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, + 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, + 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, + 0x03060503, 0xf6f701f6, 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, + 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, + 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, + 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16}; + + private static final int[] T3 = {0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, + 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, + 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, + 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, + 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, 0x46652323, + 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, + 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, + 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, + 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, + 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, + 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, + 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, + 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, + 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, + 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, + 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, + 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, + 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, + 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, + 0x06050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, + 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, + 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, + 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616}; + + private static final int[] Tinv0 = {0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, + 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, + 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, + 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, + 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, + 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, + 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, + 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604, + 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, + 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, + 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, + 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, + 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, + 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, + 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, + 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, + 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, + 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, + 0x4a9804f1, 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, + 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, + 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, + 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, + 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0}; + + private static final int[] Tinv1 = {0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, + 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, + 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, + 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, + 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, + 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, + 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, + 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, + 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, + 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, + 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, + 0x171b121d, 0x0d090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, 0xdd99eebb, 0x607fa3fd, + 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, + 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, + 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, + 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, + 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, + 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, + 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x0496e4df, 0xb5d19ee3, + 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, + 0x5610e933, 0x47d66d13, 0x61d79a8c, 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, + 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, + 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042}; + + private static final int[] Tinv2 = {0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, + 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, + 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, + 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, + 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, 0xde94876c, + 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, + 0x0506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, + 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x06046fd4, + 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, + 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, + 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, + 0x1b121d17, 0x090e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, + 0x01f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, + 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, + 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, + 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, + 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, + 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, + 0x04f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, + 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, 0x67b35a1d, 0xdb9252d2, + 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, + 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, + 0x0c08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257}; + + private static final int[] Tinv3 = {0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, + 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, + 0x03e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, + 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, + 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, + 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, + 0x06d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, 0xa475ebf6, 0x0b39ec83, 0x40aaef60, + 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x046fd406, + 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, 0x79dbeec8, + 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, + 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, + 0x121d171b, 0x0e0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, + 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, + 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, + 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, + 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, + 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, + 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, + 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, + 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, + 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, + 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, + 0x08deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8}; + private static final int m1 = 0x80808080; /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ - - private static final int m1 = 0x80808080; - private static final int m2 = 0x7f7f7f7f; - private static final int m3 = 0x0000001b; - - private final int FFmulX(int x) - { - return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); - } + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + private static final int BLOCK_SIZE = 16; + private int ROUNDS; /* - * The following defines provide alternative definitions of FFmulX that + * The following defines provide alternative definitions of FFmulX that * might give improved performance if a fast 32-bit multiply is not * available. * @@ -394,305 +383,275 @@ private final int FFmulX(int x) * 1) ^ ((u - (u >>> 7)) & m4); } * */ - - private final int inv_mcol(int x) - { - int f2 = FFmulX(x); - int f4 = FFmulX(f2); - int f8 = FFmulX(f4); - int f9 = x ^ f8; - - return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); - } - - private final int subWord(int x) - { - return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); - } - - /** - * Calculate the necessary round keys The number of calculations depends on - * key size and block size AES specified a fixed block size of 128 bits and - * key sizes 128/192/256 bits This code is written assuming those are the - * only possible values - */ - private final int[][] generateWorkingKey(byte[] key, boolean forEncryption) - { - int KC = key.length / 4; // key length in words - int t; - - if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) - { - throw new IllegalArgumentException("Key length not 128/192/256 bits."); - } - - ROUNDS = KC + 6; // This is not always true for the generalized - // Rijndael that allows larger block sizes - int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block - - // - // copy the key into the round key array - // - - t = 0; - for (int i = 0; i < key.length; t++) - { - W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) - | (key[i + 3] << 24); - i += 4; - } - - // - // while not enough round key material calculated - // calculate new values - // - int k = (ROUNDS + 1) << 2; - for (int i = KC; (i < k); i++) - { - int temp = W[(i - 1) >> 2][(i - 1) & 3]; - if ((i % KC) == 0) - { - temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; - } - else if ((KC > 6) && ((i % KC) == 4)) - { - temp = subWord(temp); - } - - W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; - } - - if (!forEncryption) - { - for (int j = 1; j < ROUNDS; j++) - { - for (int i = 0; i < 4; i++) - { - W[j][i] = inv_mcol(W[j][i]); - } - } - } - - return W; - } - - private int ROUNDS; - private int[][] WorkingKey = null; - private int C0, C1, C2, C3; - private boolean doEncrypt; - - private static final int BLOCK_SIZE = 16; - - /** - * default constructor - 128 bit block size. - */ - public AES() - { - } - - /** - * initialise an AES cipher. - * - * @param forEncryption - * whether or not we are for encryption. - * @param key - * the key required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - - public final void init(boolean forEncryption, byte[] key) - { - WorkingKey = generateWorkingKey(key, forEncryption); - this.doEncrypt = forEncryption; - } - - public final String getAlgorithmName() - { - return "AES"; - } - - public final int getBlockSize() - { - return BLOCK_SIZE; - } - - public final int processBlock(byte[] in, int inOff, byte[] out, int outOff) - { - if (WorkingKey == null) - { - throw new IllegalStateException("AES engine not initialised"); - } - - if ((inOff + (32 / 2)) > in.length) - { - throw new IllegalArgumentException("input buffer too short"); - } - - if ((outOff + (32 / 2)) > out.length) - { - throw new IllegalArgumentException("output buffer too short"); - } - - if (doEncrypt) - { - unpackBlock(in, inOff); - encryptBlock(WorkingKey); - packBlock(out, outOff); - } - else - { - unpackBlock(in, inOff); - decryptBlock(WorkingKey); - packBlock(out, outOff); - } - - return BLOCK_SIZE; - } - - public final void reset() - { - } - - private final void unpackBlock(byte[] bytes, int off) - { - int index = off; - - C0 = (bytes[index++] & 0xff); - C0 |= (bytes[index++] & 0xff) << 8; - C0 |= (bytes[index++] & 0xff) << 16; - C0 |= bytes[index++] << 24; - - C1 = (bytes[index++] & 0xff); - C1 |= (bytes[index++] & 0xff) << 8; - C1 |= (bytes[index++] & 0xff) << 16; - C1 |= bytes[index++] << 24; - - C2 = (bytes[index++] & 0xff); - C2 |= (bytes[index++] & 0xff) << 8; - C2 |= (bytes[index++] & 0xff) << 16; - C2 |= bytes[index++] << 24; - - C3 = (bytes[index++] & 0xff); - C3 |= (bytes[index++] & 0xff) << 8; - C3 |= (bytes[index++] & 0xff) << 16; - C3 |= bytes[index++] << 24; - } - - private final void packBlock(byte[] bytes, int off) - { - int index = off; - - bytes[index++] = (byte) C0; - bytes[index++] = (byte) (C0 >> 8); - bytes[index++] = (byte) (C0 >> 16); - bytes[index++] = (byte) (C0 >> 24); - - bytes[index++] = (byte) C1; - bytes[index++] = (byte) (C1 >> 8); - bytes[index++] = (byte) (C1 >> 16); - bytes[index++] = (byte) (C1 >> 24); - - bytes[index++] = (byte) C2; - bytes[index++] = (byte) (C2 >> 8); - bytes[index++] = (byte) (C2 >> 16); - bytes[index++] = (byte) (C2 >> 24); - - bytes[index++] = (byte) C3; - bytes[index++] = (byte) (C3 >> 8); - bytes[index++] = (byte) (C3 >> 16); - bytes[index++] = (byte) (C3 >> 24); - } - - private final void encryptBlock(int[][] KW) - { - int r, r0, r1, r2, r3; - - C0 ^= KW[0][0]; - C1 ^= KW[0][1]; - C2 ^= KW[0][2]; - C3 ^= KW[0][3]; - - for (r = 1; r < ROUNDS - 1;) - { - r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; - r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; - r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; - r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; - C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; - C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; - C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; - C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; - } - - r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; - r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; - r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; - r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; - - // the final round's table is a simple function of S so we don't use a - // whole other four tables for it - - C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) - ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; - C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) - ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; - C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) - ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; - C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) - ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; - - } - - private final void decryptBlock(int[][] KW) - { - int r, r0, r1, r2, r3; - - C0 ^= KW[ROUNDS][0]; - C1 ^= KW[ROUNDS][1]; - C2 ^= KW[ROUNDS][2]; - C3 ^= KW[ROUNDS][3]; - - for (r = ROUNDS - 1; r > 1;) - { - r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] - ^ KW[r][0]; - r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] - ^ KW[r][1]; - r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] - ^ KW[r][2]; - r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] - ^ KW[r--][3]; - C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] - ^ KW[r][0]; - C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] - ^ KW[r][1]; - C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] - ^ KW[r][2]; - C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] - ^ KW[r--][3]; - } - - r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; - r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; - r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; - r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; - - // the final round's table is a simple function of Si so we don't use a - // whole other four tables for it - - C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) - ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; - C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) - ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; - C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) - ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; - C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) - ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; - } - - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) - { - processBlock(src, srcoff, dst, dstoff); - } + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean doEncrypt; + + /** + * default constructor - 128 bit block size. + */ + public AES() { + } + + private final int shift(int r, int shift) { + return (((r >>> shift) | (r << (32 - shift)))); + } + + private final int FFmulX(int x) { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + private final int inv_mcol(int x) { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + private final int subWord(int x) { + return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); + } + + /** + * Calculate the necessary round keys The number of calculations depends on + * key size and block size AES specified a fixed block size of 128 bits and + * key sizes 128/192/256 bits This code is written assuming those are the + * only possible values + */ + private final int[][] generateWorkingKey(byte[] key, boolean forEncryption) { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized + // Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + for (int i = 0; i < key.length; t++) { + W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) + | (key[i + 3] << 24); + i += 4; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (int i = KC; (i < k); i++) { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + if ((i % KC) == 0) { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } else if ((KC > 6) && ((i % KC) == 4)) { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) { + for (int j = 1; j < ROUNDS; j++) { + for (int i = 0; i < 4; i++) { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param key the key required to set up the cipher. + * @throws IllegalArgumentException if the params argument is inappropriate. + */ + + public final void init(boolean forEncryption, byte[] key) { + WorkingKey = generateWorkingKey(key, forEncryption); + this.doEncrypt = forEncryption; + } + + public final String getAlgorithmName() { + return "AES"; + } + + public final int getBlockSize() { + return BLOCK_SIZE; + } + + public final int processBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (WorkingKey == null) { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) { + throw new IllegalArgumentException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) { + throw new IllegalArgumentException("output buffer too short"); + } + + if (doEncrypt) { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } else { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public final void reset() { + } + + private final void unpackBlock(byte[] bytes, int off) { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private final void packBlock(byte[] bytes, int off) { + int index = off; + + bytes[index++] = (byte) C0; + bytes[index++] = (byte) (C0 >> 8); + bytes[index++] = (byte) (C0 >> 16); + bytes[index++] = (byte) (C0 >> 24); + + bytes[index++] = (byte) C1; + bytes[index++] = (byte) (C1 >> 8); + bytes[index++] = (byte) (C1 >> 16); + bytes[index++] = (byte) (C1 >> 24); + + bytes[index++] = (byte) C2; + bytes[index++] = (byte) (C2 >> 8); + bytes[index++] = (byte) (C2 >> 16); + bytes[index++] = (byte) (C2 >> 24); + + bytes[index++] = (byte) C3; + bytes[index++] = (byte) (C3 >> 8); + bytes[index++] = (byte) (C3 >> 16); + bytes[index++] = (byte) (C3 >> 24); + } + + private final void encryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + for (r = 1; r < ROUNDS - 1; ) { + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; + C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; + C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; + C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; + } + + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a + // whole other four tables for it + + C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) + ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; + C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) + ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; + C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) + ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; + C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) + ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; + + } + + private final void decryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + for (r = ROUNDS - 1; r > 1; ) { + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] + ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] + ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] + ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] + ^ KW[r--][3]; + C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] + ^ KW[r][0]; + C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] + ^ KW[r][1]; + C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] + ^ KW[r][2]; + C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] + ^ KW[r--][3]; + } + + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; + + // the final round's table is a simple function of Si so we don't use a + // whole other four tables for it + + C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) + ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; + C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) + ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; + C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) + ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; + C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) + ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + processBlock(src, srcoff, dst, dstoff); + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/BlockCipher.java b/src/ch/ethz/ssh2/crypto/cipher/BlockCipher.java index 1576169..83116ec 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/BlockCipher.java +++ b/src/ch/ethz/ssh2/crypto/cipher/BlockCipher.java @@ -2,15 +2,14 @@ /** * BlockCipher. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: BlockCipher.java,v 1.1 2005/05/26 14:53:27 cplattne Exp $ */ -public interface BlockCipher -{ - public void init(boolean forEncryption, byte[] key); +public interface BlockCipher { + public void init(boolean forEncryption, byte[] key); - public int getBlockSize(); + public int getBlockSize(); - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff); + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff); } diff --git a/src/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java b/src/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java index b8b8460..2efa9c5 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java +++ b/src/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java @@ -1,115 +1,96 @@ - package ch.ethz.ssh2.crypto.cipher; import java.util.Vector; /** * BlockCipherFactory. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: BlockCipherFactory.java,v 1.4 2005/12/05 17:13:27 cplattne Exp $ */ -public class BlockCipherFactory -{ - static class CipherEntry - { - String type; - int blocksize; - int keysize; - String cipherClass; +public class BlockCipherFactory { + static Vector ciphers = new Vector(); + + static { + /* Higher Priority First */ - public CipherEntry(String type, int blockSize, int keySize, String cipherClass) - { - this.type = type; - this.blocksize = blockSize; - this.keysize = keySize; - this.cipherClass = cipherClass; - } - } + ciphers.addElement(new CipherEntry("aes256-ctr", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes192-ctr", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes128-ctr", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("blowfish-ctr", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); - static Vector ciphers = new Vector(); + ciphers.addElement(new CipherEntry("aes256-cbc", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes192-cbc", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("aes128-cbc", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.addElement(new CipherEntry("blowfish-cbc", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); - static - { - /* Higher Priority First */ + ciphers.addElement(new CipherEntry("3des-ctr", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); + ciphers.addElement(new CipherEntry("3des-cbc", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); + } - ciphers.addElement(new CipherEntry("aes256-ctr", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes192-ctr", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes128-ctr", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("blowfish-ctr", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); + public static String[] getDefaultCipherList() { + String list[] = new String[ciphers.size()]; + for (int i = 0; i < ciphers.size(); i++) { + CipherEntry ce = (CipherEntry) ciphers.elementAt(i); + list[i] = new String(ce.type); + } + return list; + } - ciphers.addElement(new CipherEntry("aes256-cbc", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes192-cbc", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("aes128-cbc", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); - ciphers.addElement(new CipherEntry("blowfish-cbc", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); - - ciphers.addElement(new CipherEntry("3des-ctr", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); - ciphers.addElement(new CipherEntry("3des-cbc", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); - } + public static void checkCipherList(String[] cipherCandidates) { + for (int i = 0; i < cipherCandidates.length; i++) + getEntry(cipherCandidates[i]); + } - public static String[] getDefaultCipherList() - { - String list[] = new String[ciphers.size()]; - for (int i = 0; i < ciphers.size(); i++) - { - CipherEntry ce = (CipherEntry) ciphers.elementAt(i); - list[i] = new String(ce.type); - } - return list; - } + public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv) { + try { + CipherEntry ce = getEntry(type); + Class cc = Class.forName(ce.cipherClass); + BlockCipher bc = (BlockCipher) cc.newInstance(); - public static void checkCipherList(String[] cipherCandidates) - { - for (int i = 0; i < cipherCandidates.length; i++) - getEntry(cipherCandidates[i]); - } + if (type.endsWith("-cbc")) { + bc.init(encrypt, key); + return new CBCMode(bc, iv, encrypt); + } else if (type.endsWith("-ctr")) { + bc.init(true, key); + return new CTRMode(bc, iv, encrypt); + } + throw new IllegalArgumentException("Cannot instantiate " + type); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot instantiate " + type); + } + } - public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv) - { - try - { - CipherEntry ce = getEntry(type); - Class cc = Class.forName(ce.cipherClass); - BlockCipher bc = (BlockCipher) cc.newInstance(); + private static CipherEntry getEntry(String type) { + for (int i = 0; i < ciphers.size(); i++) { + CipherEntry ce = (CipherEntry) ciphers.elementAt(i); + if (ce.type.equals(type)) + return ce; + } + throw new IllegalArgumentException("Unkown algorithm " + type); + } - if (type.endsWith("-cbc")) - { - bc.init(encrypt, key); - return new CBCMode(bc, iv, encrypt); - } - else if (type.endsWith("-ctr")) - { - bc.init(true, key); - return new CTRMode(bc, iv, encrypt); - } - throw new IllegalArgumentException("Cannot instantiate " + type); - } - catch (Exception e) - { - throw new IllegalArgumentException("Cannot instantiate " + type); - } - } + public static int getBlockSize(String type) { + CipherEntry ce = getEntry(type); + return ce.blocksize; + } - private static CipherEntry getEntry(String type) - { - for (int i = 0; i < ciphers.size(); i++) - { - CipherEntry ce = (CipherEntry) ciphers.elementAt(i); - if (ce.type.equals(type)) - return ce; - } - throw new IllegalArgumentException("Unkown algorithm " + type); - } + public static int getKeySize(String type) { + CipherEntry ce = getEntry(type); + return ce.keysize; + } - public static int getBlockSize(String type) - { - CipherEntry ce = getEntry(type); - return ce.blocksize; - } + static class CipherEntry { + String type; + int blocksize; + int keysize; + String cipherClass; - public static int getKeySize(String type) - { - CipherEntry ce = getEntry(type); - return ce.keysize; - } + public CipherEntry(String type, int blockSize, int keySize, String cipherClass) { + this.type = type; + this.blocksize = blockSize; + this.keysize = keySize; + this.cipherClass = cipherClass; + } + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/BlowFish.java b/src/ch/ethz/ssh2/crypto/cipher/BlowFish.java index 697aad4..0eaebeb 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/BlowFish.java +++ b/src/ch/ethz/ssh2/crypto/cipher/BlowFish.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto.cipher; /* @@ -31,249 +30,230 @@ of this software and associated documentation files (the "Software"), to deal * A class that provides Blowfish key encryption operations, such as encoding * data and generating keys. All the algorithms herein are from Applied * Cryptography and implement a simplified cryptography interface. - * + * * @author See comments in the source file * @version $Id: BlowFish.java,v 1.3 2005/12/05 17:13:27 cplattne Exp $ */ -public class BlowFish implements BlockCipher -{ - - private final static int[] KP = { 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, - 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, - 0xB5470917, 0x9216D5D9, 0x8979FB1B }, - - KS0 = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, - 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, - 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, - 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, - 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, - 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, - 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, - 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, - 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, - 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, - 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, - 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, - 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, - 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, - 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, - 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, - 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, - 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, - 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, - 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, - 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, - 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, - 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, - 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, - 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, - 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, - 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, - 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, - 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A }, - - KS1 = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, - 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, - 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, - 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, - 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, - 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, - 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, - 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, - 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, - 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, - 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, - 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, - 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, - 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, - 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, - 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, - 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, - 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, - 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, - 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, - 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, - 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, - 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, - 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, - 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, - 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, - 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, - 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, - 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 }, - - KS2 = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, - 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, - 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, - 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, - 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, - 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, - 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, - 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, - 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, - 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, - 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, - 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, - 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, - 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, - 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, - 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, - 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, - 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, - 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, - 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, - 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, - 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, - 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, - 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, - 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, - 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, - 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, - 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, - 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 }, - - KS3 = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, - 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, - 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, - 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, - 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, - 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, - 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, - 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, - 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, - 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, - 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, - 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, - 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, - 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, - 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, - 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, - 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, - 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, - 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, - 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, - 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, - 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, - 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, - 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, - 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, - 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, - 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, - 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, - 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 }; - - // ==================================== - // Useful constants - // ==================================== - - private static final int ROUNDS = 16; - private static final int BLOCK_SIZE = 8; // bytes = 64 bits - private static final int SBOX_SK = 256; - private static final int P_SZ = ROUNDS + 2; - - private final int[] S0, S1, S2, S3; // the s-boxes - private final int[] P; // the p-array - - private boolean doEncrypt = false; - - private byte[] workingKey = null; - - public BlowFish() - { - S0 = new int[SBOX_SK]; - S1 = new int[SBOX_SK]; - S2 = new int[SBOX_SK]; - S3 = new int[SBOX_SK]; - P = new int[P_SZ]; - } - - /** - * initialise a Blowfish cipher. - * - * @param encrypting - * whether or not we are for encryption. - * @param key - * the key required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - public void init(boolean encrypting, byte[] key) - { - this.doEncrypt = encrypting; - this.workingKey = key; - setKey(this.workingKey); - } - - public String getAlgorithmName() - { - return "Blowfish"; - } - - public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff) - { - if (workingKey == null) - { - throw new IllegalStateException("Blowfish not initialised"); - } - - if (doEncrypt) - { - encryptBlock(in, inOff, out, outOff); - } - else - { - decryptBlock(in, inOff, out, outOff); - } - } - - public void reset() - { - } - - public int getBlockSize() - { - return BLOCK_SIZE; - } - - // ================================== - // Private Implementation - // ================================== - - private int F(int x) - { - return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); - } - - /** - * apply the encryption cycle to each value pair in the table. - */ - private void processTable(int xl, int xr, int[] table) - { - int size = table.length; - - for (int s = 0; s < size; s += 2) - { - xl ^= P[0]; - - for (int i = 1; i < ROUNDS; i += 2) - { - xr ^= F(xl) ^ P[i]; - xl ^= F(xr) ^ P[i + 1]; - } - - xr ^= P[ROUNDS + 1]; - - table[s] = xr; - table[s + 1] = xl; - - xr = xl; // end of cycle swap - xl = table[s]; - } - } - - private void setKey(byte[] key) - { - /* +public class BlowFish implements BlockCipher { + + private final static int[] KP = {0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, + 0xB5470917, 0x9216D5D9, 0x8979FB1B}, + + KS0 = {0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, + 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, + 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, + 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, + 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, + 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, + 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, + 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, + 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, + 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, + 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, + 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, + 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, + 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, + 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, + 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, + 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, + 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, + 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, + 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, + 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, + 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A}, + + KS1 = {0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, + 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, + 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, + 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, + 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, + 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, + 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, + 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, + 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, + 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, + 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, + 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, + 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, + 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, + 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7}, + + KS2 = {0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, + 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, + 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, + 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, + 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, + 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, + 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, + 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, + 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, + 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, + 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, + 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, + 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, + 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, + 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0}, + + KS3 = {0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, + 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, + 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, + 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, + 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, + 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, + 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, + 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, + 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, + 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, + 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, + 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, + 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, + 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, + 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6}; + + // ==================================== + // Useful constants + // ==================================== + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS + 2; + + private final int[] S0, S1, S2, S3; // the s-boxes + private final int[] P; // the p-array + + private boolean doEncrypt = false; + + private byte[] workingKey = null; + + public BlowFish() { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param encrypting whether or not we are for encryption. + * @param key the key required to set up the cipher. + * @throws IllegalArgumentException if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + this.doEncrypt = encrypting; + this.workingKey = key; + setKey(this.workingKey); + } + + public String getAlgorithmName() { + return "Blowfish"; + } + + public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (workingKey == null) { + throw new IllegalStateException("Blowfish not initialised"); + } + + if (doEncrypt) { + encryptBlock(in, inOff, out, outOff); + } else { + decryptBlock(in, inOff, out, outOff); + } + } + + public void reset() { + } + + public int getBlockSize() { + return BLOCK_SIZE; + } + + // ================================== + // Private Implementation + // ================================== + + private int F(int x) { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + */ + private void processTable(int xl, int xr, int[] table) { + int size = table.length; + + for (int s = 0; s < size; s += 2) { + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + table[s] = xr; + table[s + 1] = xl; + + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) { + /* * - comments are from _Applied Crypto_, Schneier, p338 please be * careful comparing the two, AC numbers the arrays from 1, the enclosed * code from 0. @@ -281,12 +261,12 @@ private void setKey(byte[] key) * (1) Initialise the S-boxes and the P-array, with a fixed string This * string contains the hexadecimal digits of pi (3.141...) */ - System.arraycopy(KS0, 0, S0, 0, SBOX_SK); - System.arraycopy(KS1, 0, S1, 0, SBOX_SK); - System.arraycopy(KS2, 0, S2, 0, SBOX_SK); - System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); - System.arraycopy(KP, 0, P, 0, P_SZ); + System.arraycopy(KP, 0, P, 0, P_SZ); /* * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with @@ -294,27 +274,24 @@ private void setKey(byte[] key) * to P[17]). Repeatedly cycle through the key bits until the entire * P-array has been XOR-ed with the key bits */ - int keyLength = key.length; - int keyIndex = 0; - - for (int i = 0; i < P_SZ; i++) - { - // get the 32 bits of the key, in 4 * 8 bit chunks - int data = 0x0000000; - for (int j = 0; j < 4; j++) - { - // create a 32 bit block - data = (data << 8) | (key[keyIndex++] & 0xff); - - // wrap when we get to the end of the key - if (keyIndex >= keyLength) - { - keyIndex = 0; - } - } - // XOR the newly created 32 bit chunk onto the P-array - P[i] ^= data; - } + int keyLength = key.length; + int keyIndex = 0; + + for (int i = 0; i < P_SZ; i++) { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + for (int j = 0; j < 4; j++) { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) { + keyIndex = 0; + } + } + // XOR the newly created 32 bit chunk onto the P-array + P[i] ^= data; + } /* * (3) Encrypt the all-zero string with the Blowfish algorithm, using @@ -332,72 +309,66 @@ private void setKey(byte[] key) * changing Blowfish algorithm */ - processTable(0, 0, P); - processTable(P[P_SZ - 2], P[P_SZ - 1], S0); - processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); - processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); - processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); - } - - /** - * Encrypt the given input starting at the given offset and place the result - * in the provided buffer starting at the given offset. The input will be an - * exact multiple of our blocksize. - */ - private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) - { - int xl = BytesTo32bits(src, srcIndex); - int xr = BytesTo32bits(src, srcIndex + 4); - - xl ^= P[0]; - - for (int i = 1; i < ROUNDS; i += 2) - { - xr ^= F(xl) ^ P[i]; - xl ^= F(xr) ^ P[i + 1]; - } - - xr ^= P[ROUNDS + 1]; - - Bits32ToBytes(xr, dst, dstIndex); - Bits32ToBytes(xl, dst, dstIndex + 4); - } - - /** - * Decrypt the given input starting at the given offset and place the result - * in the provided buffer starting at the given offset. The input will be an - * exact multiple of our blocksize. - */ - private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) - { - int xl = BytesTo32bits(src, srcIndex); - int xr = BytesTo32bits(src, srcIndex + 4); - - xl ^= P[ROUNDS + 1]; - - for (int i = ROUNDS; i > 0; i -= 2) - { - xr ^= F(xl) ^ P[i]; - xl ^= F(xr) ^ P[i - 1]; - } - - xr ^= P[0]; - - Bits32ToBytes(xr, dst, dstIndex); - Bits32ToBytes(xl, dst, dstIndex + 4); - } - - private int BytesTo32bits(byte[] b, int i) - { - return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff)); - } - - private void Bits32ToBytes(int in, byte[] b, int offset) - { - b[offset + 3] = (byte) in; - b[offset + 2] = (byte) (in >> 8); - b[offset + 1] = (byte) (in >> 16); - b[offset] = (byte) (in >> 24); - } + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Encrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Decrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + + xl ^= P[ROUNDS + 1]; + + for (int i = ROUNDS; i > 0; i -= 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i - 1]; + } + + xr ^= P[0]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + private int BytesTo32bits(byte[] b, int i) { + return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff)); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) { + b[offset + 3] = (byte) in; + b[offset + 2] = (byte) (in >> 8); + b[offset + 1] = (byte) (in >> 16); + b[offset] = (byte) (in >> 24); + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/CBCMode.java b/src/ch/ethz/ssh2/crypto/cipher/CBCMode.java index 175e376..7a4f8de 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/CBCMode.java +++ b/src/ch/ethz/ssh2/crypto/cipher/CBCMode.java @@ -2,77 +2,70 @@ /** * CBCMode. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: CBCMode.java,v 1.2 2005/12/05 17:13:27 cplattne Exp $ */ -public class CBCMode implements BlockCipher -{ - BlockCipher tc; - int blockSize; - boolean doEncrypt; - - byte[] cbc_vector; - byte[] tmp_vector; - - public void init(boolean forEncryption, byte[] key) - { - } - - public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt) - throws IllegalArgumentException - { - this.tc = tc; - this.blockSize = tc.getBlockSize(); - this.doEncrypt = doEncrypt; - - if (this.blockSize != iv.length) - throw new IllegalArgumentException("IV must be " + blockSize - + " bytes long! (currently " + iv.length + ")"); - - this.cbc_vector = new byte[blockSize]; - this.tmp_vector = new byte[blockSize]; - System.arraycopy(iv, 0, cbc_vector, 0, blockSize); - } - - public int getBlockSize() - { - return blockSize; - } - - private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) - { - for (int i = 0; i < blockSize; i++) - cbc_vector[i] ^= src[srcoff + i]; - - tc.transformBlock(cbc_vector, 0, dst, dstoff); - - System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize); - } - - private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) - { - /* Assume the worst, src and dst are overlapping... */ - - System.arraycopy(src, srcoff, tmp_vector, 0, blockSize); - - tc.transformBlock(src, srcoff, dst, dstoff); - - for (int i = 0; i < blockSize; i++) - dst[dstoff + i] ^= cbc_vector[i]; +public class CBCMode implements BlockCipher { + BlockCipher tc; + int blockSize; + boolean doEncrypt; + + byte[] cbc_vector; + byte[] tmp_vector; + + public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt) + throws IllegalArgumentException { + this.tc = tc; + this.blockSize = tc.getBlockSize(); + this.doEncrypt = doEncrypt; + + if (this.blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + + " bytes long! (currently " + iv.length + ")"); + + this.cbc_vector = new byte[blockSize]; + this.tmp_vector = new byte[blockSize]; + System.arraycopy(iv, 0, cbc_vector, 0, blockSize); + } + + public void init(boolean forEncryption, byte[] key) { + } + + public int getBlockSize() { + return blockSize; + } + + private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + for (int i = 0; i < blockSize; i++) + cbc_vector[i] ^= src[srcoff + i]; + + tc.transformBlock(cbc_vector, 0, dst, dstoff); + + System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize); + } + + private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + /* Assume the worst, src and dst are overlapping... */ + + System.arraycopy(src, srcoff, tmp_vector, 0, blockSize); + + tc.transformBlock(src, srcoff, dst, dstoff); + + for (int i = 0; i < blockSize; i++) + dst[dstoff + i] ^= cbc_vector[i]; /* ...that is why we need a tmp buffer. */ - - byte[] swap = cbc_vector; - cbc_vector = tmp_vector; - tmp_vector = swap; - } - - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) - { - if (doEncrypt) - encryptBlock(src, srcoff, dst, dstoff); - else - decryptBlock(src, srcoff, dst, dstoff); - } + + byte[] swap = cbc_vector; + cbc_vector = tmp_vector; + tmp_vector = swap; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + if (doEncrypt) + encryptBlock(src, srcoff, dst, dstoff); + else + decryptBlock(src, srcoff, dst, dstoff); + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/CTRMode.java b/src/ch/ethz/ssh2/crypto/cipher/CTRMode.java index 4fbc0e6..dad665a 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/CTRMode.java +++ b/src/ch/ethz/ssh2/crypto/cipher/CTRMode.java @@ -1,62 +1,54 @@ - package ch.ethz.ssh2.crypto.cipher; /** * This is CTR mode as described in draft-ietf-secsh-newmodes-XY.txt - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: CTRMode.java,v 1.1 2005/06/06 12:44:25 cplattne Exp $ */ -public class CTRMode implements BlockCipher -{ - byte[] X; - byte[] Xenc; - - BlockCipher bc; - int blockSize; - boolean doEncrypt; - - int count = 0; - - public void init(boolean forEncryption, byte[] key) - { - } - - public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException - { - bc = tc; - blockSize = bc.getBlockSize(); - doEncrypt = doEnc; - - if (blockSize != iv.length) - throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")"); - - X = new byte[blockSize]; - Xenc = new byte[blockSize]; - - System.arraycopy(iv, 0, X, 0, blockSize); - } - - public final int getBlockSize() - { - return blockSize; - } - - public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) - { - bc.transformBlock(X, 0, Xenc, 0); - - for (int i = 0; i < blockSize; i++) - { - dst[dstoff + i] = (byte) (src[srcoff + i] ^ Xenc[i]); - } - - for (int i = (blockSize - 1); i >= 0; i--) - { - X[i]++; - if (X[i] != 0) - break; - - } - } +public class CTRMode implements BlockCipher { + byte[] X; + byte[] Xenc; + + BlockCipher bc; + int blockSize; + boolean doEncrypt; + + int count = 0; + + public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException { + bc = tc; + blockSize = bc.getBlockSize(); + doEncrypt = doEnc; + + if (blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")"); + + X = new byte[blockSize]; + Xenc = new byte[blockSize]; + + System.arraycopy(iv, 0, X, 0, blockSize); + } + + public void init(boolean forEncryption, byte[] key) { + } + + public final int getBlockSize() { + return blockSize; + } + + public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + bc.transformBlock(X, 0, Xenc, 0); + + for (int i = 0; i < blockSize; i++) { + dst[dstoff + i] = (byte) (src[srcoff + i] ^ Xenc[i]); + } + + for (int i = (blockSize - 1); i >= 0; i--) { + X[i]++; + if (X[i] != 0) + break; + + } + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java b/src/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java index ddd698e..e141748 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java +++ b/src/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto.cipher; import java.io.IOException; @@ -6,139 +5,120 @@ /** * CipherInputStream. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: CipherInputStream.java,v 1.3 2006/02/14 15:17:37 cplattne Exp $ */ -public class CipherInputStream -{ - BlockCipher currentCipher; - InputStream bi; - byte[] buffer; - byte[] enc; - int blockSize; - int pos; +public class CipherInputStream { + final int BUFF_SIZE = 2048; + BlockCipher currentCipher; + InputStream bi; + byte[] buffer; + byte[] enc; + int blockSize; /* - * We cannot use java.io.BufferedInputStream, since that is not available in + * We cannot use java.io.BufferedInputStream, since that is not available in * J2ME. Everything could be improved alot here. */ - - final int BUFF_SIZE = 2048; - byte[] input_buffer = new byte[BUFF_SIZE]; - int input_buffer_pos = 0; - int input_buffer_size = 0; - - public CipherInputStream(BlockCipher tc, InputStream bi) - { - this.bi = bi; - changeCipher(tc); - } - - private int fill_buffer() throws IOException - { - input_buffer_pos = 0; - input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE); - return input_buffer_size; - } - - private int internal_read(byte[] b, int off, int len) throws IOException - { - if (input_buffer_size < 0) - return -1; - - if (input_buffer_pos >= input_buffer_size) - { - if (fill_buffer() <= 0) - return -1; - } - - int avail = input_buffer_size - input_buffer_pos; - int thiscopy = (len > avail) ? avail : len; - - System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy); - input_buffer_pos += thiscopy; - - return thiscopy; - } - - public void changeCipher(BlockCipher bc) - { - this.currentCipher = bc; - blockSize = bc.getBlockSize(); - buffer = new byte[blockSize]; - enc = new byte[blockSize]; - pos = blockSize; - } - - private void getBlock() throws IOException - { - int n = 0; - while (n < blockSize) - { - int len = internal_read(enc, n, blockSize - n); - if (len < 0) - throw new IOException("Cannot read full block, EOF reached."); - n += len; - } - - try - { - currentCipher.transformBlock(enc, 0, buffer, 0); - } - catch (Exception e) - { - throw new IOException("Error while decrypting block."); - } - pos = 0; - } - - public int read(byte[] dst) throws IOException - { - return read(dst, 0, dst.length); - } - - public int read(byte[] dst, int off, int len) throws IOException - { - int count = 0; - - while (len > 0) - { - if (pos >= blockSize) - getBlock(); - - int avail = blockSize - pos; - int copy = Math.min(avail, len); - System.arraycopy(buffer, pos, dst, off, copy); - pos += copy; - off += copy; - len -= copy; - count += copy; - } - return count; - } - - public int read() throws IOException - { - if (pos >= blockSize) - { - getBlock(); - } - return buffer[pos++] & 0xff; - } - - public int readPlain(byte[] b, int off, int len) throws IOException - { - if (pos != blockSize) - throw new IOException("Cannot read plain since crypto buffer is not aligned."); - int n = 0; - while (n < len) - { - int cnt = internal_read(b, off + n, len - n); - if (cnt < 0) - throw new IOException("Cannot fill buffer, EOF reached."); - n += cnt; - } - return n; - } + int pos; + byte[] input_buffer = new byte[BUFF_SIZE]; + int input_buffer_pos = 0; + int input_buffer_size = 0; + + public CipherInputStream(BlockCipher tc, InputStream bi) { + this.bi = bi; + changeCipher(tc); + } + + private int fill_buffer() throws IOException { + input_buffer_pos = 0; + input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE); + return input_buffer_size; + } + + private int internal_read(byte[] b, int off, int len) throws IOException { + if (input_buffer_size < 0) + return -1; + + if (input_buffer_pos >= input_buffer_size) { + if (fill_buffer() <= 0) + return -1; + } + + int avail = input_buffer_size - input_buffer_pos; + int thiscopy = (len > avail) ? avail : len; + + System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy); + input_buffer_pos += thiscopy; + + return thiscopy; + } + + public void changeCipher(BlockCipher bc) { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = blockSize; + } + + private void getBlock() throws IOException { + int n = 0; + while (n < blockSize) { + int len = internal_read(enc, n, blockSize - n); + if (len < 0) + throw new IOException("Cannot read full block, EOF reached."); + n += len; + } + + try { + currentCipher.transformBlock(enc, 0, buffer, 0); + } catch (Exception e) { + throw new IOException("Error while decrypting block."); + } + pos = 0; + } + + public int read(byte[] dst) throws IOException { + return read(dst, 0, dst.length); + } + + public int read(byte[] dst, int off, int len) throws IOException { + int count = 0; + + while (len > 0) { + if (pos >= blockSize) + getBlock(); + + int avail = blockSize - pos; + int copy = Math.min(avail, len); + System.arraycopy(buffer, pos, dst, off, copy); + pos += copy; + off += copy; + len -= copy; + count += copy; + } + return count; + } + + public int read() throws IOException { + if (pos >= blockSize) { + getBlock(); + } + return buffer[pos++] & 0xff; + } + + public int readPlain(byte[] b, int off, int len) throws IOException { + if (pos != blockSize) + throw new IOException("Cannot read plain since crypto buffer is not aligned."); + int n = 0; + while (n < len) { + int cnt = internal_read(b, off + n, len - n); + if (cnt < 0) + throw new IOException("Cannot fill buffer, EOF reached."); + n += cnt; + } + return n; + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java b/src/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java index 7dc6c25..43d4e5a 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java +++ b/src/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto.cipher; import java.io.IOException; @@ -6,137 +5,117 @@ /** * CipherOutputStream. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: CipherOutputStream.java,v 1.4 2005/12/05 17:13:27 cplattne Exp $ */ -public class CipherOutputStream -{ - BlockCipher currentCipher; - OutputStream bo; - byte[] buffer; - byte[] enc; - int blockSize; - int pos; +public class CipherOutputStream { + final int BUFF_SIZE = 2048; + BlockCipher currentCipher; + OutputStream bo; + byte[] buffer; + byte[] enc; + int blockSize; /* - * We cannot use java.io.BufferedOutputStream, since that is not available + * We cannot use java.io.BufferedOutputStream, since that is not available * in J2ME. Everything could be improved here alot. */ - - final int BUFF_SIZE = 2048; - byte[] out_buffer = new byte[BUFF_SIZE]; - int out_buffer_pos = 0; - - public CipherOutputStream(BlockCipher tc, OutputStream bo) - { - this.bo = bo; - changeCipher(tc); - } - - private void internal_write(byte[] src, int off, int len) throws IOException - { - while (len > 0) - { - int space = BUFF_SIZE - out_buffer_pos; - int copy = (len > space) ? space : len; - - System.arraycopy(src, off, out_buffer, out_buffer_pos, copy); - - off += copy; - out_buffer_pos += copy; - len -= copy; - - if (out_buffer_pos >= BUFF_SIZE) - { - bo.write(out_buffer, 0, BUFF_SIZE); - out_buffer_pos = 0; - } - } - } - - private void internal_write(int b) throws IOException - { - out_buffer[out_buffer_pos++] = (byte) b; - if (out_buffer_pos >= BUFF_SIZE) - { - bo.write(out_buffer, 0, BUFF_SIZE); - out_buffer_pos = 0; - } - } - - public void flush() throws IOException - { - if (pos != 0) - throw new IOException("FATAL: cannot flush since crypto buffer is not aligned."); - - if (out_buffer_pos > 0) - { - bo.write(out_buffer, 0, out_buffer_pos); - out_buffer_pos = 0; - } - bo.flush(); - } - - public void changeCipher(BlockCipher bc) - { - this.currentCipher = bc; - blockSize = bc.getBlockSize(); - buffer = new byte[blockSize]; - enc = new byte[blockSize]; - pos = 0; - } - - private void writeBlock() throws IOException - { - try - { - currentCipher.transformBlock(buffer, 0, enc, 0); - } - catch (Exception e) - { - throw (IOException) new IOException("Error while decrypting block.").initCause(e); - } - - internal_write(enc, 0, blockSize); - pos = 0; - } - - public void write(byte[] src, int off, int len) throws IOException - { - while (len > 0) - { - int avail = blockSize - pos; - int copy = Math.min(avail, len); - - System.arraycopy(src, off, buffer, pos, copy); - pos += copy; - off += copy; - len -= copy; - - if (pos >= blockSize) - writeBlock(); - } - } - - public void write(int b) throws IOException - { - buffer[pos++] = (byte) b; - if (pos >= blockSize) - writeBlock(); - } - - public void writePlain(int b) throws IOException - { - if (pos != 0) - throw new IOException("Cannot write plain since crypto buffer is not aligned."); - internal_write(b); - } - - public void writePlain(byte[] b, int off, int len) throws IOException - { - if (pos != 0) - throw new IOException("Cannot write plain since crypto buffer is not aligned."); - internal_write(b, off, len); - } + int pos; + byte[] out_buffer = new byte[BUFF_SIZE]; + int out_buffer_pos = 0; + + public CipherOutputStream(BlockCipher tc, OutputStream bo) { + this.bo = bo; + changeCipher(tc); + } + + private void internal_write(byte[] src, int off, int len) throws IOException { + while (len > 0) { + int space = BUFF_SIZE - out_buffer_pos; + int copy = (len > space) ? space : len; + + System.arraycopy(src, off, out_buffer, out_buffer_pos, copy); + + off += copy; + out_buffer_pos += copy; + len -= copy; + + if (out_buffer_pos >= BUFF_SIZE) { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + } + + private void internal_write(int b) throws IOException { + out_buffer[out_buffer_pos++] = (byte) b; + if (out_buffer_pos >= BUFF_SIZE) { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + + public void flush() throws IOException { + if (pos != 0) + throw new IOException("FATAL: cannot flush since crypto buffer is not aligned."); + + if (out_buffer_pos > 0) { + bo.write(out_buffer, 0, out_buffer_pos); + out_buffer_pos = 0; + } + bo.flush(); + } + + public void changeCipher(BlockCipher bc) { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = 0; + } + + private void writeBlock() throws IOException { + try { + currentCipher.transformBlock(buffer, 0, enc, 0); + } catch (Exception e) { + throw (IOException) new IOException("Error while decrypting block.").initCause(e); + } + + internal_write(enc, 0, blockSize); + pos = 0; + } + + public void write(byte[] src, int off, int len) throws IOException { + while (len > 0) { + int avail = blockSize - pos; + int copy = Math.min(avail, len); + + System.arraycopy(src, off, buffer, pos, copy); + pos += copy; + off += copy; + len -= copy; + + if (pos >= blockSize) + writeBlock(); + } + } + + public void write(int b) throws IOException { + buffer[pos++] = (byte) b; + if (pos >= blockSize) + writeBlock(); + } + + public void writePlain(int b) throws IOException { + if (pos != 0) + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + internal_write(b); + } + + public void writePlain(byte[] b, int off, int len) throws IOException { + if (pos != 0) + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + internal_write(b, off, len); + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/DES.java b/src/ch/ethz/ssh2/crypto/cipher/DES.java index b4175b6..74f8685 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/DES.java +++ b/src/ch/ethz/ssh2/crypto/cipher/DES.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto.cipher; /* @@ -29,345 +28,299 @@ of this software and associated documentation files (the "Software"), to deal /** * DES. - * + * * @author See comments in the source file * @version $Id: DES.java,v 1.3 2005/12/05 17:13:27 cplattne Exp $ethz.ch - * */ -public class DES implements BlockCipher -{ - private int[] workingKey = null; - - /** - * standard constructor. - */ - public DES() - { - } - - /** - * initialise a DES cipher. - * - * @param encrypting - * whether or not we are for encryption. - * @param key - * the parameters required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - public void init(boolean encrypting, byte[] key) - { - this.workingKey = generateWorkingKey(encrypting, key, 0); - } - - public String getAlgorithmName() - { - return "DES"; - } - - public int getBlockSize() - { - return 8; - } - - public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) - { - if (workingKey == null) - { - throw new IllegalStateException("DES engine not initialised!"); - } - - desFunc(workingKey, in, inOff, out, outOff); - } - - public void reset() - { - } - - /** - * what follows is mainly taken from "Applied Cryptography", by Bruce - * Schneier, however it also bears great resemblance to Richard - * Outerbridge's D3DES... - */ - - static short[] Df_Key = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, - 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67 }; - - static short[] bytebit = { 0200, 0100, 040, 020, 010, 04, 02, 01 }; - - static int[] bigbyte = { 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, - 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 }; +public class DES implements BlockCipher { + /** + * what follows is mainly taken from "Applied Cryptography", by Bruce + * Schneier, however it also bears great resemblance to Richard + * Outerbridge's D3DES... + */ + + static short[] Df_Key = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, + 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67}; + static short[] bytebit = {0200, 0100, 040, 020, 010, 04, 02, 01}; + static int[] bigbyte = {0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, + 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1}; + static byte[] pc1 = {56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, + 4, 27, 19, 11, 3}; + static byte[] totrot = {1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28}; + static byte[] pc2 = {13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, + 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31}; + static int[] SP1 = {0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, + 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, + 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, + 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, + 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, + 0x00010400, 0x00000000, 0x01010004}; + static int[] SP2 = {0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, + 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, + 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, + 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, + 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, + 0x80100020, 0x80108020, 0x00108000}; + static int[] SP3 = {0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, + 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, + 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, + 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, + 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, + 0x00000008, 0x08020008, 0x00020200}; + static int[] SP4 = {0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, + 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, + 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, + 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, + 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, + 0x00800000, 0x00002000, 0x00802080}; /* - * Use the key schedule specified in the Standard (ANSI X3.92-1981). - */ - - static byte[] pc1 = { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, - 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, - 4, 27, 19, 11, 3 }; - - static byte[] totrot = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 }; - - static byte[] pc2 = { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, - 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 }; - - static int[] SP1 = { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, - 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, - 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, - 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, - 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, - 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, - 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, - 0x00010400, 0x00000000, 0x01010004 }; - - static int[] SP2 = { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, - 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, - 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, - 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, - 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, - 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, - 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, - 0x80100020, 0x80108020, 0x00108000 }; - - static int[] SP3 = { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, - 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, - 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, - 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, - 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, - 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, - 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, - 0x00000008, 0x08020008, 0x00020200 }; - - static int[] SP4 = { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, - 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, - 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, - 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, - 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, - 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, - 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, - 0x00800000, 0x00002000, 0x00802080 }; - - static int[] SP5 = { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, - 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, - 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, - 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, - 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, - 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, - 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, - 0x40080000, 0x02080100, 0x40000100 }; - - static int[] SP6 = { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, - 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, - 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, - 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, - 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, - 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, - 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, - 0x20000000, 0x00400010, 0x20004010 }; - - static int[] SP7 = { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, - 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, - 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, - 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, - 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, - 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, - 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, - 0x04000800, 0x00000800, 0x00200002 }; - - static int[] SP8 = { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, - 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, - 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, - 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, - 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, - 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, - 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, - 0x00040040, 0x10000000, 0x10041000 }; - - /** - * generate an integer based working key based on our secret key and what we - * processing we are planning to do. - * - * Acknowledgements for this routine go to James Gillogly & Phil Karn. - * (whoever, and wherever they are!). - */ - protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off) - { - int[] newKey = new int[32]; - boolean[] pc1m = new boolean[56], pcr = new boolean[56]; - - for (int j = 0; j < 56; j++) - { - int l = pc1[j]; - - pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0); - } - - for (int i = 0; i < 16; i++) - { - int l, m, n; - - if (encrypting) - { - m = i << 1; - } - else - { - m = (15 - i) << 1; - } - - n = m + 1; - newKey[m] = newKey[n] = 0; - - for (int j = 0; j < 28; j++) - { - l = j + totrot[i]; - if (l < 28) - { - pcr[j] = pc1m[l]; - } - else - { - pcr[j] = pc1m[l - 28]; - } - } - - for (int j = 28; j < 56; j++) - { - l = j + totrot[i]; - if (l < 56) - { - pcr[j] = pc1m[l]; - } - else - { - pcr[j] = pc1m[l - 28]; - } - } - - for (int j = 0; j < 24; j++) - { - if (pcr[pc2[j]]) - { - newKey[m] |= bigbyte[j]; - } - - if (pcr[pc2[j + 24]]) - { - newKey[n] |= bigbyte[j]; - } - } - } - - // - // store the processed key - // - for (int i = 0; i != 32; i += 2) - { - int i1, i2; - - i1 = newKey[i]; - i2 = newKey[i + 1]; - - newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10) - | ((i2 & 0x00000fc0) >>> 6); - - newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4) - | (i2 & 0x0000003f); - } - - return newKey; - } - - /** - * the DES engine. + * Use the key schedule specified in the Standard (ANSI X3.92-1981). */ - protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff) - { - int work, right, left; - - left = (in[inOff + 0] & 0xff) << 24; - left |= (in[inOff + 1] & 0xff) << 16; - left |= (in[inOff + 2] & 0xff) << 8; - left |= (in[inOff + 3] & 0xff); - - right = (in[inOff + 4] & 0xff) << 24; - right |= (in[inOff + 5] & 0xff) << 16; - right |= (in[inOff + 6] & 0xff) << 8; - right |= (in[inOff + 7] & 0xff); - - work = ((left >>> 4) ^ right) & 0x0f0f0f0f; - right ^= work; - left ^= (work << 4); - work = ((left >>> 16) ^ right) & 0x0000ffff; - right ^= work; - left ^= (work << 16); - work = ((right >>> 2) ^ left) & 0x33333333; - left ^= work; - right ^= (work << 2); - work = ((right >>> 8) ^ left) & 0x00ff00ff; - left ^= work; - right ^= (work << 8); - right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; - - for (int round = 0; round < 8; round++) - { - int fval; - - work = (right << 28) | (right >>> 4); - work ^= wKey[round * 4 + 0]; - fval = SP7[work & 0x3f]; - fval |= SP5[(work >>> 8) & 0x3f]; - fval |= SP3[(work >>> 16) & 0x3f]; - fval |= SP1[(work >>> 24) & 0x3f]; - work = right ^ wKey[round * 4 + 1]; - fval |= SP8[work & 0x3f]; - fval |= SP6[(work >>> 8) & 0x3f]; - fval |= SP4[(work >>> 16) & 0x3f]; - fval |= SP2[(work >>> 24) & 0x3f]; - left ^= fval; - work = (left << 28) | (left >>> 4); - work ^= wKey[round * 4 + 2]; - fval = SP7[work & 0x3f]; - fval |= SP5[(work >>> 8) & 0x3f]; - fval |= SP3[(work >>> 16) & 0x3f]; - fval |= SP1[(work >>> 24) & 0x3f]; - work = left ^ wKey[round * 4 + 3]; - fval |= SP8[work & 0x3f]; - fval |= SP6[(work >>> 8) & 0x3f]; - fval |= SP4[(work >>> 16) & 0x3f]; - fval |= SP2[(work >>> 24) & 0x3f]; - right ^= fval; - } - - right = (right << 31) | (right >>> 1); - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = (left << 31) | (left >>> 1); - work = ((left >>> 8) ^ right) & 0x00ff00ff; - right ^= work; - left ^= (work << 8); - work = ((left >>> 2) ^ right) & 0x33333333; - right ^= work; - left ^= (work << 2); - work = ((right >>> 16) ^ left) & 0x0000ffff; - left ^= work; - right ^= (work << 16); - work = ((right >>> 4) ^ left) & 0x0f0f0f0f; - left ^= work; - right ^= (work << 4); - - out[outOff + 0] = (byte) ((right >>> 24) & 0xff); - out[outOff + 1] = (byte) ((right >>> 16) & 0xff); - out[outOff + 2] = (byte) ((right >>> 8) & 0xff); - out[outOff + 3] = (byte) (right & 0xff); - out[outOff + 4] = (byte) ((left >>> 24) & 0xff); - out[outOff + 5] = (byte) ((left >>> 16) & 0xff); - out[outOff + 6] = (byte) ((left >>> 8) & 0xff); - out[outOff + 7] = (byte) (left & 0xff); - } + static int[] SP5 = {0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, + 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, + 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, + 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, + 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, + 0x40080000, 0x02080100, 0x40000100}; + static int[] SP6 = {0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, + 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, + 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, + 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, + 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, + 0x20000000, 0x00400010, 0x20004010}; + static int[] SP7 = {0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, + 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, + 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, + 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, + 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, + 0x04000800, 0x00000800, 0x00200002}; + static int[] SP8 = {0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, + 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, + 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, + 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, + 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, + 0x00040040, 0x10000000, 0x10041000}; + private int[] workingKey = null; + + /** + * standard constructor. + */ + public DES() { + } + + /** + * initialise a DES cipher. + * + * @param encrypting whether or not we are for encryption. + * @param key the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + this.workingKey = generateWorkingKey(encrypting, key, 0); + } + + public String getAlgorithmName() { + return "DES"; + } + + public int getBlockSize() { + return 8; + } + + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (workingKey == null) { + throw new IllegalStateException("DES engine not initialised!"); + } + + desFunc(workingKey, in, inOff, out, outOff); + } + + public void reset() { + } + + /** + * generate an integer based working key based on our secret key and what we + * processing we are planning to do. + *

    + * Acknowledgements for this routine go to James Gillogly & Phil Karn. + * (whoever, and wherever they are!). + */ + protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off) { + int[] newKey = new int[32]; + boolean[] pc1m = new boolean[56], pcr = new boolean[56]; + + for (int j = 0; j < 56; j++) { + int l = pc1[j]; + + pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0); + } + + for (int i = 0; i < 16; i++) { + int l, m, n; + + if (encrypting) { + m = i << 1; + } else { + m = (15 - i) << 1; + } + + n = m + 1; + newKey[m] = newKey[n] = 0; + + for (int j = 0; j < 28; j++) { + l = j + totrot[i]; + if (l < 28) { + pcr[j] = pc1m[l]; + } else { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 28; j < 56; j++) { + l = j + totrot[i]; + if (l < 56) { + pcr[j] = pc1m[l]; + } else { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 0; j < 24; j++) { + if (pcr[pc2[j]]) { + newKey[m] |= bigbyte[j]; + } + + if (pcr[pc2[j + 24]]) { + newKey[n] |= bigbyte[j]; + } + } + } + + // + // store the processed key + // + for (int i = 0; i != 32; i += 2) { + int i1, i2; + + i1 = newKey[i]; + i2 = newKey[i + 1]; + + newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10) + | ((i2 & 0x00000fc0) >>> 6); + + newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4) + | (i2 & 0x0000003f); + } + + return newKey; + } + + /** + * the DES engine. + */ + protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff) { + int work, right, left; + + left = (in[inOff + 0] & 0xff) << 24; + left |= (in[inOff + 1] & 0xff) << 16; + left |= (in[inOff + 2] & 0xff) << 8; + left |= (in[inOff + 3] & 0xff); + + right = (in[inOff + 4] & 0xff) << 24; + right |= (in[inOff + 5] & 0xff) << 16; + right |= (in[inOff + 6] & 0xff) << 8; + right |= (in[inOff + 7] & 0xff); + + work = ((left >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= (work << 4); + work = ((left >>> 16) ^ right) & 0x0000ffff; + right ^= work; + left ^= (work << 16); + work = ((right >>> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >>> 8) ^ left) & 0x00ff00ff; + left ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; + + for (int round = 0; round < 8; round++) { + int fval; + + work = (right << 28) | (right >>> 4); + work ^= wKey[round * 4 + 0]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = right ^ wKey[round * 4 + 1]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + left ^= fval; + work = (left << 28) | (left >>> 4); + work ^= wKey[round * 4 + 2]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = left ^ wKey[round * 4 + 3]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 31) | (left >>> 1); + work = ((left >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + left ^= (work << 8); + work = ((left >>> 2) ^ right) & 0x33333333; + right ^= work; + left ^= (work << 2); + work = ((right >>> 16) ^ left) & 0x0000ffff; + left ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= (work << 4); + + out[outOff + 0] = (byte) ((right >>> 24) & 0xff); + out[outOff + 1] = (byte) ((right >>> 16) & 0xff); + out[outOff + 2] = (byte) ((right >>> 8) & 0xff); + out[outOff + 3] = (byte) (right & 0xff); + out[outOff + 4] = (byte) ((left >>> 24) & 0xff); + out[outOff + 5] = (byte) ((left >>> 16) & 0xff); + out[outOff + 6] = (byte) ((left >>> 8) & 0xff); + out[outOff + 7] = (byte) (left & 0xff); + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/DESede.java b/src/ch/ethz/ssh2/crypto/cipher/DESede.java index 62e7f2c..3340a8b 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/DESede.java +++ b/src/ch/ethz/ssh2/crypto/cipher/DESede.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.crypto.cipher; /* @@ -29,77 +28,62 @@ of this software and associated documentation files (the "Software"), to deal /** * DESede. - * + * * @author See comments in the source file * @version $Id: DESede.java,v 1.3 2005/08/11 12:47:27 cplattne Exp $ethz.ch - * */ -public class DESede extends DES -{ - private int[] key1 = null; - private int[] key2 = null; - private int[] key3 = null; - - private boolean encrypt; - - /** - * standard constructor. - */ - public DESede() - { - } - - /** - * initialise a DES cipher. - * - * @param encrypting - * whether or not we are for encryption. - * @param key - * the parameters required to set up the cipher. - * @exception IllegalArgumentException - * if the params argument is inappropriate. - */ - public void init(boolean encrypting, byte[] key) - { - key1 = generateWorkingKey(encrypting, key, 0); - key2 = generateWorkingKey(!encrypting, key, 8); - key3 = generateWorkingKey(encrypting, key, 16); - - encrypt = encrypting; - } - - public String getAlgorithmName() - { - return "DESede"; - } - - public int getBlockSize() - { - return 8; - } - - public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) - { - if (key1 == null) - { - throw new IllegalStateException("DESede engine not initialised!"); - } - - if (encrypt) - { - desFunc(key1, in, inOff, out, outOff); - desFunc(key2, out, outOff, out, outOff); - desFunc(key3, out, outOff, out, outOff); - } - else - { - desFunc(key3, in, inOff, out, outOff); - desFunc(key2, out, outOff, out, outOff); - desFunc(key1, out, outOff, out, outOff); - } - } - - public void reset() - { - } +public class DESede extends DES { + private int[] key1 = null; + private int[] key2 = null; + private int[] key3 = null; + + private boolean encrypt; + + /** + * standard constructor. + */ + public DESede() { + } + + /** + * initialise a DES cipher. + * + * @param encrypting whether or not we are for encryption. + * @param key the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + key1 = generateWorkingKey(encrypting, key, 0); + key2 = generateWorkingKey(!encrypting, key, 8); + key3 = generateWorkingKey(encrypting, key, 16); + + encrypt = encrypting; + } + + public String getAlgorithmName() { + return "DESede"; + } + + public int getBlockSize() { + return 8; + } + + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (key1 == null) { + throw new IllegalStateException("DESede engine not initialised!"); + } + + if (encrypt) { + desFunc(key1, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key3, out, outOff, out, outOff); + } else { + desFunc(key3, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key1, out, outOff, out, outOff); + } + } + + public void reset() { + } } diff --git a/src/ch/ethz/ssh2/crypto/cipher/NullCipher.java b/src/ch/ethz/ssh2/crypto/cipher/NullCipher.java index 7de0c20..de544b3 100644 --- a/src/ch/ethz/ssh2/crypto/cipher/NullCipher.java +++ b/src/ch/ethz/ssh2/crypto/cipher/NullCipher.java @@ -2,34 +2,28 @@ /** * NullCipher. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: NullCipher.java,v 1.3 2005/12/07 10:25:48 cplattne Exp $ */ -public class NullCipher implements BlockCipher -{ - private int blockSize = 8; - - public NullCipher() - { - } +public class NullCipher implements BlockCipher { + private int blockSize = 8; - public NullCipher(int blockSize) - { - this.blockSize = blockSize; - } - - public void init(boolean forEncryption, byte[] key) - { - } + public NullCipher() { + } - public int getBlockSize() - { - return blockSize; - } + public NullCipher(int blockSize) { + this.blockSize = blockSize; + } - public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) - { - System.arraycopy(src, srcoff, dst, dstoff, blockSize); - } + public void init(boolean forEncryption, byte[] key) { + } + + public int getBlockSize() { + return blockSize; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + System.arraycopy(src, srcoff, dst, dstoff, blockSize); + } } diff --git a/src/ch/ethz/ssh2/crypto/dh/DhExchange.java b/src/ch/ethz/ssh2/crypto/dh/DhExchange.java index 48dd6cd..50bd2fb 100644 --- a/src/ch/ethz/ssh2/crypto/dh/DhExchange.java +++ b/src/ch/ethz/ssh2/crypto/dh/DhExchange.java @@ -1,145 +1,131 @@ - package ch.ethz.ssh2.crypto.dh; -import java.math.BigInteger; -import java.security.SecureRandom; - import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; import ch.ethz.ssh2.log.Logger; +import java.math.BigInteger; +import java.security.SecureRandom; + /** * DhExchange. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DhExchange.java,v 1.5 2006/02/14 19:43:15 cplattne Exp $ */ -public class DhExchange -{ - private static final Logger log = Logger.getLogger(DhExchange.class); +public class DhExchange { + static final BigInteger p1, p14; /* Given by the standard */ - - static final BigInteger p1, p14; - static final BigInteger g; - - BigInteger p; + static final BigInteger g; + private static final Logger log = Logger.getLogger(DhExchange.class); + + static { + final String p1_string = "17976931348623159077083915679378745319786029604875" + + "60117064444236841971802161585193689478337958649255415021805654859805036464" + + "40548199239100050792877003355816639229553136239076508735759914822574862575" + + "00742530207744771258955095793777842444242661733472762929938766870920560605" + + "0270810842907692932019128194467627007"; + + final String p14_string = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129" + + "024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0" + + "A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB" + + "6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A" + + "163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208" + + "552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36C" + + "E3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558171" + + "83995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"; + + p1 = new BigInteger(p1_string); + p14 = new BigInteger(p14_string, 16); + g = new BigInteger("2"); + } /* Client public and private */ - BigInteger e; - BigInteger x; + BigInteger p; + BigInteger e; /* Server public */ - - BigInteger f; + BigInteger x; /* Shared secret */ - - BigInteger k; - - static - { - final String p1_string = "17976931348623159077083915679378745319786029604875" - + "60117064444236841971802161585193689478337958649255415021805654859805036464" - + "40548199239100050792877003355816639229553136239076508735759914822574862575" - + "00742530207744771258955095793777842444242661733472762929938766870920560605" - + "0270810842907692932019128194467627007"; - - final String p14_string = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129" - + "024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0" - + "A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB" - + "6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A" - + "163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208" - + "552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36C" - + "E3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558171" - + "83995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"; - - p1 = new BigInteger(p1_string); - p14 = new BigInteger(p14_string, 16); - g = new BigInteger("2"); - } - - public DhExchange() - { - } - - public void init(int group, SecureRandom rnd) - { - k = null; - - if (group == 1) - p = p1; - else if (group == 14) - p = p14; - else - throw new IllegalArgumentException("Unknown DH group " + group); - - x = new BigInteger(p.bitLength() - 1, rnd); - - e = g.modPow(x, p); - } - - /** - * @return Returns the e. - * @throws IllegalStateException - */ - public BigInteger getE() - { - if (e == null) - throw new IllegalStateException("DhDsaExchange not initialized!"); - - return e; - } - - /** - * @return Returns the shared secret k. - * @throws IllegalStateException - */ - public BigInteger getK() - { - if (k == null) - throw new IllegalStateException("Shared secret not yet known, need f first!"); - - return k; - } - - /** - * @param f - */ - public void setF(BigInteger f) - { - if (e == null) - throw new IllegalStateException("DhDsaExchange not initialized!"); - - BigInteger zero = BigInteger.valueOf(0); - - if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) - throw new IllegalArgumentException("Invalid f specified!"); - - this.f = f; - this.k = f.modPow(x, p); - } - - public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, - byte[] serverKexPayload, byte[] hostKey) - { - HashForSSH2Types hash = new HashForSSH2Types("SHA1"); - - if (log.isEnabled()) - { - log.log(90, "Client: '" + new String(clientversion) + "'"); - log.log(90, "Server: '" + new String(serverversion) + "'"); - } - - hash.updateByteString(clientversion); - hash.updateByteString(serverversion); - hash.updateByteString(clientKexPayload); - hash.updateByteString(serverKexPayload); - hash.updateByteString(hostKey); - hash.updateBigInt(e); - hash.updateBigInt(f); - hash.updateBigInt(k); - - return hash.getDigest(); - } + BigInteger f; + BigInteger k; + + public DhExchange() { + } + + public void init(int group, SecureRandom rnd) { + k = null; + + if (group == 1) + p = p1; + else if (group == 14) + p = p14; + else + throw new IllegalArgumentException("Unknown DH group " + group); + + x = new BigInteger(p.bitLength() - 1, rnd); + + e = g.modPow(x, p); + } + + /** + * @return Returns the e. + * @throws IllegalStateException + */ + public BigInteger getE() { + if (e == null) + throw new IllegalStateException("DhDsaExchange not initialized!"); + + return e; + } + + /** + * @return Returns the shared secret k. + * @throws IllegalStateException + */ + public BigInteger getK() { + if (k == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return k; + } + + /** + * @param f + */ + public void setF(BigInteger f) { + if (e == null) + throw new IllegalStateException("DhDsaExchange not initialized!"); + + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) + throw new IllegalArgumentException("Invalid f specified!"); + + this.f = f; + this.k = f.modPow(x, p); + } + + public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, + byte[] serverKexPayload, byte[] hostKey) { + HashForSSH2Types hash = new HashForSSH2Types("SHA1"); + + if (log.isEnabled()) { + log.log(90, "Client: '" + new String(clientversion) + "'"); + log.log(90, "Server: '" + new String(serverversion) + "'"); + } + + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + hash.updateBigInt(e); + hash.updateBigInt(f); + hash.updateBigInt(k); + + return hash.getDigest(); + } } diff --git a/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java b/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java index fb32e5d..e87234d 100644 --- a/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java +++ b/src/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java @@ -1,112 +1,104 @@ - package ch.ethz.ssh2.crypto.dh; -import java.math.BigInteger; -import java.security.SecureRandom; - import ch.ethz.ssh2.DHGexParameters; import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; +import java.math.BigInteger; +import java.security.SecureRandom; + /** * DhGroupExchange. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DhGroupExchange.java,v 1.6 2006/09/20 12:51:37 cplattne Exp $ */ -public class DhGroupExchange -{ - /* Given by the standard */ +public class DhGroupExchange { + /* Given by the standard */ - private BigInteger p; - private BigInteger g; + private BigInteger p; + private BigInteger g; /* Client public and private */ - private BigInteger e; - private BigInteger x; + private BigInteger e; + private BigInteger x; /* Server public */ - private BigInteger f; + private BigInteger f; /* Shared secret */ - private BigInteger k; - - public DhGroupExchange(BigInteger p, BigInteger g) - { - this.p = p; - this.g = g; - } - - public void init(SecureRandom rnd) - { - k = null; - - x = new BigInteger(p.bitLength() - 1, rnd); - e = g.modPow(x, p); - } - - /** - * @return Returns the e. - */ - public BigInteger getE() - { - if (e == null) - throw new IllegalStateException("Not initialized!"); - - return e; - } - - /** - * @return Returns the shared secret k. - */ - public BigInteger getK() - { - if (k == null) - throw new IllegalStateException("Shared secret not yet known, need f first!"); - - return k; - } - - /** - * Sets f and calculates the shared secret. - */ - public void setF(BigInteger f) - { - if (e == null) - throw new IllegalStateException("Not initialized!"); - - BigInteger zero = BigInteger.valueOf(0); - - if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) - throw new IllegalArgumentException("Invalid f specified!"); - - this.f = f; - this.k = f.modPow(x, p); - } - - public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, - byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) - { - HashForSSH2Types hash = new HashForSSH2Types("SHA1"); - - hash.updateByteString(clientversion); - hash.updateByteString(serverversion); - hash.updateByteString(clientKexPayload); - hash.updateByteString(serverKexPayload); - hash.updateByteString(hostKey); - if (para.getMin_group_len() > 0) - hash.updateUINT32(para.getMin_group_len()); - hash.updateUINT32(para.getPref_group_len()); - if (para.getMax_group_len() > 0) - hash.updateUINT32(para.getMax_group_len()); - hash.updateBigInt(p); - hash.updateBigInt(g); - hash.updateBigInt(e); - hash.updateBigInt(f); - hash.updateBigInt(k); - - return hash.getDigest(); - } + private BigInteger k; + + public DhGroupExchange(BigInteger p, BigInteger g) { + this.p = p; + this.g = g; + } + + public void init(SecureRandom rnd) { + k = null; + + x = new BigInteger(p.bitLength() - 1, rnd); + e = g.modPow(x, p); + } + + /** + * @return Returns the e. + */ + public BigInteger getE() { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + return e; + } + + /** + * @return Returns the shared secret k. + */ + public BigInteger getK() { + if (k == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return k; + } + + /** + * Sets f and calculates the shared secret. + */ + public void setF(BigInteger f) { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) + throw new IllegalArgumentException("Invalid f specified!"); + + this.f = f; + this.k = f.modPow(x, p); + } + + public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, + byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) { + HashForSSH2Types hash = new HashForSSH2Types("SHA1"); + + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + if (para.getMin_group_len() > 0) + hash.updateUINT32(para.getMin_group_len()); + hash.updateUINT32(para.getPref_group_len()); + if (para.getMax_group_len() > 0) + hash.updateUINT32(para.getMax_group_len()); + hash.updateBigInt(p); + hash.updateBigInt(g); + hash.updateBigInt(e); + hash.updateBigInt(f); + hash.updateBigInt(k); + + return hash.getDigest(); + } } diff --git a/src/ch/ethz/ssh2/crypto/digest/Digest.java b/src/ch/ethz/ssh2/crypto/digest/Digest.java index 6d417b6..1d133cd 100644 --- a/src/ch/ethz/ssh2/crypto/digest/Digest.java +++ b/src/ch/ethz/ssh2/crypto/digest/Digest.java @@ -1,25 +1,23 @@ - package ch.ethz.ssh2.crypto.digest; /** * Digest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Digest.java,v 1.2 2005/08/11 12:47:29 cplattne Exp $ */ -public interface Digest -{ - public int getDigestLength(); +public interface Digest { + public int getDigestLength(); - public void update(byte b); + public void update(byte b); - public void update(byte[] b); + public void update(byte[] b); - public void update(byte b[], int off, int len); + public void update(byte b[], int off, int len); - public void reset(); + public void reset(); - public void digest(byte[] out); + public void digest(byte[] out); - public void digest(byte[] out, int off); + public void digest(byte[] out, int off); } diff --git a/src/ch/ethz/ssh2/crypto/digest/HMAC.java b/src/ch/ethz/ssh2/crypto/digest/HMAC.java index 8ecc50a..3ae5fc0 100644 --- a/src/ch/ethz/ssh2/crypto/digest/HMAC.java +++ b/src/ch/ethz/ssh2/crypto/digest/HMAC.java @@ -1,95 +1,83 @@ - package ch.ethz.ssh2.crypto.digest; /** * HMAC. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: HMAC.java,v 1.2 2006/02/02 09:11:03 cplattne Exp $ */ -public final class HMAC implements Digest -{ - Digest md; - byte[] k_xor_ipad; - byte[] k_xor_opad; - - byte[] tmp; - - int size; - - public HMAC(Digest md, byte[] key, int size) - { - this.md = md; - this.size = size; - - tmp = new byte[md.getDigestLength()]; - - int BLOCKSIZE = 64; - - k_xor_ipad = new byte[BLOCKSIZE]; - k_xor_opad = new byte[BLOCKSIZE]; - - if (key.length > BLOCKSIZE) - { - md.reset(); - md.update(key); - md.digest(tmp); - key = tmp; - } - - System.arraycopy(key, 0, k_xor_ipad, 0, key.length); - System.arraycopy(key, 0, k_xor_opad, 0, key.length); - - for (int i = 0; i < BLOCKSIZE; i++) - { - k_xor_ipad[i] ^= 0x36; - k_xor_opad[i] ^= 0x5C; - } - md.update(k_xor_ipad); - } - - public final int getDigestLength() - { - return size; - } - - public final void update(byte b) - { - md.update(b); - } - - public final void update(byte[] b) - { - md.update(b); - } - - public final void update(byte[] b, int off, int len) - { - md.update(b, off, len); - } - - public final void reset() - { - md.reset(); - md.update(k_xor_ipad); - } - - public final void digest(byte[] out) - { - digest(out, 0); - } - - public final void digest(byte[] out, int off) - { - md.digest(tmp); - - md.update(k_xor_opad); - md.update(tmp); - - md.digest(tmp); - - System.arraycopy(tmp, 0, out, off, size); - - md.update(k_xor_ipad); - } +public final class HMAC implements Digest { + Digest md; + byte[] k_xor_ipad; + byte[] k_xor_opad; + + byte[] tmp; + + int size; + + public HMAC(Digest md, byte[] key, int size) { + this.md = md; + this.size = size; + + tmp = new byte[md.getDigestLength()]; + + int BLOCKSIZE = 64; + + k_xor_ipad = new byte[BLOCKSIZE]; + k_xor_opad = new byte[BLOCKSIZE]; + + if (key.length > BLOCKSIZE) { + md.reset(); + md.update(key); + md.digest(tmp); + key = tmp; + } + + System.arraycopy(key, 0, k_xor_ipad, 0, key.length); + System.arraycopy(key, 0, k_xor_opad, 0, key.length); + + for (int i = 0; i < BLOCKSIZE; i++) { + k_xor_ipad[i] ^= 0x36; + k_xor_opad[i] ^= 0x5C; + } + md.update(k_xor_ipad); + } + + public final int getDigestLength() { + return size; + } + + public final void update(byte b) { + md.update(b); + } + + public final void update(byte[] b) { + md.update(b); + } + + public final void update(byte[] b, int off, int len) { + md.update(b, off, len); + } + + public final void reset() { + md.reset(); + md.update(k_xor_ipad); + } + + public final void digest(byte[] out) { + digest(out, 0); + } + + public final void digest(byte[] out, int off) { + md.digest(tmp); + + md.update(k_xor_opad); + md.update(tmp); + + md.digest(tmp); + + System.arraycopy(tmp, 0, out, off, size); + + md.update(k_xor_ipad); + } } diff --git a/src/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java b/src/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java index 967be4d..5766158 100644 --- a/src/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java +++ b/src/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java @@ -1,93 +1,75 @@ - package ch.ethz.ssh2.crypto.digest; import java.math.BigInteger; /** * HashForSSH2Types. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: HashForSSH2Types.java,v 1.3 2005/08/12 23:37:18 cplattne Exp $ */ -public class HashForSSH2Types -{ - Digest md; +public class HashForSSH2Types { + Digest md; - public HashForSSH2Types(Digest md) - { - this.md = md; - } + public HashForSSH2Types(Digest md) { + this.md = md; + } - public HashForSSH2Types(String type) - { - if (type.equals("SHA1")) - { - md = new SHA1(); - } - else if (type.equals("MD5")) - { - md = new MD5(); - } - else - throw new IllegalArgumentException("Unknown algorithm " + type); - } + public HashForSSH2Types(String type) { + if (type.equals("SHA1")) { + md = new SHA1(); + } else if (type.equals("MD5")) { + md = new MD5(); + } else + throw new IllegalArgumentException("Unknown algorithm " + type); + } - public void updateByte(byte b) - { - /* HACK - to test it with J2ME */ - byte[] tmp = new byte[1]; - tmp[0] = b; - md.update(tmp); - } + public void updateByte(byte b) { + /* HACK - to test it with J2ME */ + byte[] tmp = new byte[1]; + tmp[0] = b; + md.update(tmp); + } - public void updateBytes(byte[] b) - { - md.update(b); - } + public void updateBytes(byte[] b) { + md.update(b); + } - public void updateUINT32(int v) - { - md.update((byte) (v >> 24)); - md.update((byte) (v >> 16)); - md.update((byte) (v >> 8)); - md.update((byte) (v)); - } + public void updateUINT32(int v) { + md.update((byte) (v >> 24)); + md.update((byte) (v >> 16)); + md.update((byte) (v >> 8)); + md.update((byte) (v)); + } - public void updateByteString(byte[] b) - { - updateUINT32(b.length); - updateBytes(b); - } + public void updateByteString(byte[] b) { + updateUINT32(b.length); + updateBytes(b); + } - public void updateBigInt(BigInteger b) - { - updateByteString(b.toByteArray()); - } + public void updateBigInt(BigInteger b) { + updateByteString(b.toByteArray()); + } - public void reset() - { - md.reset(); - } + public void reset() { + md.reset(); + } - public int getDigestLength() - { - return md.getDigestLength(); - } + public int getDigestLength() { + return md.getDigestLength(); + } - public byte[] getDigest() - { - byte[] tmp = new byte[md.getDigestLength()]; - getDigest(tmp); - return tmp; - } + public byte[] getDigest() { + byte[] tmp = new byte[md.getDigestLength()]; + getDigest(tmp); + return tmp; + } - public void getDigest(byte[] out) - { - getDigest(out, 0); - } + public void getDigest(byte[] out) { + getDigest(out, 0); + } - public void getDigest(byte[] out, int off) - { - md.digest(out, off); - } + public void getDigest(byte[] out, int off) { + md.digest(out, off); + } } diff --git a/src/ch/ethz/ssh2/crypto/digest/MAC.java b/src/ch/ethz/ssh2/crypto/digest/MAC.java index 7c50fff..27d009e 100644 --- a/src/ch/ethz/ssh2/crypto/digest/MAC.java +++ b/src/ch/ethz/ssh2/crypto/digest/MAC.java @@ -1,88 +1,70 @@ - package ch.ethz.ssh2.crypto.digest; /** * MAC. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: MAC.java,v 1.4 2006/02/02 09:11:03 cplattne Exp $ */ -public final class MAC -{ - Digest mac; - int size; +public final class MAC { + Digest mac; + int size; - public final static String[] getMacList() - { - /* Higher Priority First */ + public MAC(String type, byte[] key) { + if (type.equals("hmac-sha1")) { + mac = new HMAC(new SHA1(), key, 20); + } else if (type.equals("hmac-sha1-96")) { + mac = new HMAC(new SHA1(), key, 12); + } else if (type.equals("hmac-md5")) { + mac = new HMAC(new MD5(), key, 16); + } else if (type.equals("hmac-md5-96")) { + mac = new HMAC(new MD5(), key, 12); + } else + throw new IllegalArgumentException("Unkown algorithm " + type); - return new String[] { "hmac-sha1-96", "hmac-sha1", "hmac-md5-96", "hmac-md5" }; - } + size = mac.getDigestLength(); + } - public final static void checkMacList(String[] macs) - { - for (int i = 0; i < macs.length; i++) - getKeyLen(macs[i]); - } + public final static String[] getMacList() { + /* Higher Priority First */ - public final static int getKeyLen(String type) - { - if (type.equals("hmac-sha1")) - return 20; - if (type.equals("hmac-sha1-96")) - return 20; - if (type.equals("hmac-md5")) - return 16; - if (type.equals("hmac-md5-96")) - return 16; - throw new IllegalArgumentException("Unkown algorithm " + type); - } + return new String[]{"hmac-sha1-96", "hmac-sha1", "hmac-md5-96", "hmac-md5"}; + } - public MAC(String type, byte[] key) - { - if (type.equals("hmac-sha1")) - { - mac = new HMAC(new SHA1(), key, 20); - } - else if (type.equals("hmac-sha1-96")) - { - mac = new HMAC(new SHA1(), key, 12); - } - else if (type.equals("hmac-md5")) - { - mac = new HMAC(new MD5(), key, 16); - } - else if (type.equals("hmac-md5-96")) - { - mac = new HMAC(new MD5(), key, 12); - } - else - throw new IllegalArgumentException("Unkown algorithm " + type); + public final static void checkMacList(String[] macs) { + for (int i = 0; i < macs.length; i++) + getKeyLen(macs[i]); + } - size = mac.getDigestLength(); - } + public final static int getKeyLen(String type) { + if (type.equals("hmac-sha1")) + return 20; + if (type.equals("hmac-sha1-96")) + return 20; + if (type.equals("hmac-md5")) + return 16; + if (type.equals("hmac-md5-96")) + return 16; + throw new IllegalArgumentException("Unkown algorithm " + type); + } - public final void initMac(int seq) - { - mac.reset(); - mac.update((byte) (seq >> 24)); - mac.update((byte) (seq >> 16)); - mac.update((byte) (seq >> 8)); - mac.update((byte) (seq)); - } + public final void initMac(int seq) { + mac.reset(); + mac.update((byte) (seq >> 24)); + mac.update((byte) (seq >> 16)); + mac.update((byte) (seq >> 8)); + mac.update((byte) (seq)); + } - public final void update(byte[] packetdata, int off, int len) - { - mac.update(packetdata, off, len); - } + public final void update(byte[] packetdata, int off, int len) { + mac.update(packetdata, off, len); + } - public final void getMac(byte[] out, int off) - { - mac.digest(out, off); - } + public final void getMac(byte[] out, int off) { + mac.digest(out, off); + } - public final int size() - { - return size; - } + public final int size() { + return size; + } } diff --git a/src/ch/ethz/ssh2/crypto/digest/MD5.java b/src/ch/ethz/ssh2/crypto/digest/MD5.java index e6df77c..85a1e8b 100644 --- a/src/ch/ethz/ssh2/crypto/digest/MD5.java +++ b/src/ch/ethz/ssh2/crypto/digest/MD5.java @@ -1,9 +1,8 @@ - package ch.ethz.ssh2.crypto.digest; /** * MD5. Based on the example code in RFC 1321. Optimized (...a little). - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: MD5.java,v 1.2 2006/02/02 09:11:03 cplattne Exp $ */ @@ -33,236 +32,214 @@ * */ -public final class MD5 implements Digest -{ - private int state0, state1, state2, state3; - private long count; - private final byte[] block = new byte[64]; - private final int x[] = new int[16]; - - private static final byte[] padding = new byte[] { (byte) 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - public MD5() - { - reset(); - } - - private static final int FF(int a, int b, int c, int d, int x, int s, int ac) - { - a += ((b & c) | ((~b) & d)) + x + ac; - return ((a << s) | (a >>> (32 - s))) + b; - } - - private static final int GG(int a, int b, int c, int d, int x, int s, int ac) - { - a += ((b & d) | (c & (~d))) + x + ac; - return ((a << s) | (a >>> (32 - s))) + b; - } - - private static final int HH(int a, int b, int c, int d, int x, int s, int ac) - { - a += (b ^ c ^ d) + x + ac; - return ((a << s) | (a >>> (32 - s))) + b; - } - - private static final int II(int a, int b, int c, int d, int x, int s, int ac) - { - a += (c ^ (b | (~d))) + x + ac; - return ((a << s) | (a >>> (32 - s))) + b; - } - - private static final void encode(byte[] dst, int dstoff, int word) - { - dst[dstoff] = (byte) (word); - dst[dstoff + 1] = (byte) (word >> 8); - dst[dstoff + 2] = (byte) (word >> 16); - dst[dstoff + 3] = (byte) (word >> 24); - } - - private final void transform(byte[] src, int pos) - { - int a = state0; - int b = state1; - int c = state2; - int d = state3; - - for (int i = 0; i < 16; i++, pos += 4) - { - x[i] = (src[pos] & 0xff) | ((src[pos + 1] & 0xff) << 8) | ((src[pos + 2] & 0xff) << 16) - | ((src[pos + 3] & 0xff) << 24); - } +public final class MD5 implements Digest { + private static final byte[] padding = new byte[]{(byte) 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + private final byte[] block = new byte[64]; + private final int x[] = new int[16]; + private int state0, state1, state2, state3; + private long count; + + public MD5() { + reset(); + } + + private static final int FF(int a, int b, int c, int d, int x, int s, int ac) { + a += ((b & c) | ((~b) & d)) + x + ac; + return ((a << s) | (a >>> (32 - s))) + b; + } + + private static final int GG(int a, int b, int c, int d, int x, int s, int ac) { + a += ((b & d) | (c & (~d))) + x + ac; + return ((a << s) | (a >>> (32 - s))) + b; + } + + private static final int HH(int a, int b, int c, int d, int x, int s, int ac) { + a += (b ^ c ^ d) + x + ac; + return ((a << s) | (a >>> (32 - s))) + b; + } + + private static final int II(int a, int b, int c, int d, int x, int s, int ac) { + a += (c ^ (b | (~d))) + x + ac; + return ((a << s) | (a >>> (32 - s))) + b; + } + + private static final void encode(byte[] dst, int dstoff, int word) { + dst[dstoff] = (byte) (word); + dst[dstoff + 1] = (byte) (word >> 8); + dst[dstoff + 2] = (byte) (word >> 16); + dst[dstoff + 3] = (byte) (word >> 24); + } + + private final void transform(byte[] src, int pos) { + int a = state0; + int b = state1; + int c = state2; + int d = state3; + + for (int i = 0; i < 16; i++, pos += 4) { + x[i] = (src[pos] & 0xff) | ((src[pos + 1] & 0xff) << 8) | ((src[pos + 2] & 0xff) << 16) + | ((src[pos + 3] & 0xff) << 24); + } /* Round 1 */ - a = FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */ - d = FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */ - c = FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */ - b = FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */ - a = FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */ - d = FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */ - c = FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */ - b = FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */ - a = FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */ - d = FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */ - c = FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */ - b = FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */ - a = FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */ - d = FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */ - c = FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */ - b = FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */ + a = FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */ + d = FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */ + c = FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */ + b = FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */ + a = FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */ + d = FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */ + c = FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */ + b = FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */ + a = FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */ + d = FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */ + c = FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */ + b = FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */ + a = FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */ + d = FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */ + c = FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */ + b = FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */ /* Round 2 */ - a = GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */ - d = GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */ - c = GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */ - b = GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */ - a = GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */ - d = GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */ - c = GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */ - b = GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */ - a = GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */ - d = GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */ - c = GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */ - b = GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */ - a = GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */ - d = GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */ - c = GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */ - b = GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */ + a = GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */ + d = GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */ + c = GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */ + b = GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */ + a = GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */ + d = GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */ + c = GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */ + b = GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */ + a = GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */ + d = GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */ + c = GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */ + b = GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */ + a = GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */ + d = GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */ + c = GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */ + b = GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */ /* Round 3 */ - a = HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */ - d = HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */ - c = HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */ - b = HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */ - a = HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */ - d = HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */ - c = HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */ - b = HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */ - a = HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */ - d = HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */ - c = HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */ - b = HH(b, c, d, a, x[6], 23, 0x4881d05); /* 44 */ - a = HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */ - d = HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */ - c = HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */ - b = HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */ + a = HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */ + d = HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */ + c = HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */ + b = HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */ + a = HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */ + d = HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */ + c = HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */ + b = HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */ + a = HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */ + d = HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */ + c = HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */ + b = HH(b, c, d, a, x[6], 23, 0x4881d05); /* 44 */ + a = HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */ + d = HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */ + c = HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */ + b = HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */ /* Round 4 */ - a = II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */ - d = II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */ - c = II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */ - b = II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */ - a = II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */ - d = II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */ - c = II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */ - b = II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */ - a = II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */ - d = II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */ - c = II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */ - b = II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */ - a = II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */ - d = II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */ - c = II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */ - b = II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */ - - state0 += a; - state1 += b; - state2 += c; - state3 += d; - } - - public final void reset() - { - count = 0; - - state0 = 0x67452301; - state1 = 0xefcdab89; - state2 = 0x98badcfe; - state3 = 0x10325476; + a = II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */ + d = II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */ + c = II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */ + b = II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */ + a = II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */ + d = II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */ + c = II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */ + b = II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */ + a = II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */ + d = II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */ + c = II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */ + b = II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */ + a = II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */ + d = II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */ + c = II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */ + b = II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */ + + state0 += a; + state1 += b; + state2 += c; + state3 += d; + } + + public final void reset() { + count = 0; + + state0 = 0x67452301; + state1 = 0xefcdab89; + state2 = 0x98badcfe; + state3 = 0x10325476; /* Clear traces in memory... */ - for (int i = 0; i < 16; i++) - x[i] = 0; - } - - public final void update(byte b) - { - final int space = 64 - ((int) (count & 0x3f)); - - count++; - - block[64 - space] = b; - - if (space == 1) - transform(block, 0); - } - - public final void update(byte[] buff, int pos, int len) - { - int space = 64 - ((int) (count & 0x3f)); - - count += len; - - while (len > 0) - { - if (len < space) - { - System.arraycopy(buff, pos, block, 64 - space, len); - break; - } - - if (space == 64) - { - transform(buff, pos); - } - else - { - System.arraycopy(buff, pos, block, 64 - space, space); - transform(block, 0); - } - - pos += space; - len -= space; - space = 64; - } - } - - public final void update(byte[] b) - { - update(b, 0, b.length); - } - - public final void digest(byte[] dst, int pos) - { - byte[] bits = new byte[8]; - - encode(bits, 0, (int) (count << 3)); - encode(bits, 4, (int) (count >> 29)); - - int idx = (int) count & 0x3f; - int padLen = (idx < 56) ? (56 - idx) : (120 - idx); - - update(padding, 0, padLen); - update(bits, 0, 8); - - encode(dst, pos, state0); - encode(dst, pos + 4, state1); - encode(dst, pos + 8, state2); - encode(dst, pos + 12, state3); - - reset(); - } - - public final void digest(byte[] dst) - { - digest(dst, 0); - } - - public final int getDigestLength() - { - return 16; - } + for (int i = 0; i < 16; i++) + x[i] = 0; + } + + public final void update(byte b) { + final int space = 64 - ((int) (count & 0x3f)); + + count++; + + block[64 - space] = b; + + if (space == 1) + transform(block, 0); + } + + public final void update(byte[] buff, int pos, int len) { + int space = 64 - ((int) (count & 0x3f)); + + count += len; + + while (len > 0) { + if (len < space) { + System.arraycopy(buff, pos, block, 64 - space, len); + break; + } + + if (space == 64) { + transform(buff, pos); + } else { + System.arraycopy(buff, pos, block, 64 - space, space); + transform(block, 0); + } + + pos += space; + len -= space; + space = 64; + } + } + + public final void update(byte[] b) { + update(b, 0, b.length); + } + + public final void digest(byte[] dst, int pos) { + byte[] bits = new byte[8]; + + encode(bits, 0, (int) (count << 3)); + encode(bits, 4, (int) (count >> 29)); + + int idx = (int) count & 0x3f; + int padLen = (idx < 56) ? (56 - idx) : (120 - idx); + + update(padding, 0, padLen); + update(bits, 0, 8); + + encode(dst, pos, state0); + encode(dst, pos + 4, state1); + encode(dst, pos + 8, state2); + encode(dst, pos + 12, state3); + + reset(); + } + + public final void digest(byte[] dst) { + digest(dst, 0); + } + + public final int getDigestLength() { + return 16; + } } diff --git a/src/ch/ethz/ssh2/crypto/digest/SHA1.java b/src/ch/ethz/ssh2/crypto/digest/SHA1.java index 792b8b3..a198c85 100644 --- a/src/ch/ethz/ssh2/crypto/digest/SHA1.java +++ b/src/ch/ethz/ssh2/crypto/digest/SHA1.java @@ -1,218 +1,44 @@ - package ch.ethz.ssh2.crypto.digest; /** - * * SHA-1 implementation based on FIPS PUB 180-1. - * + *

    * (http://www.itl.nist.gov/fipspubs/fip180-1.htm) - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: SHA1.java,v 1.4 2006/02/02 09:11:03 cplattne Exp $ */ -public final class SHA1 implements Digest -{ - private int H0, H1, H2, H3, H4; - - private final byte msg[] = new byte[64]; - private final int[] w = new int[80]; - private int currentPos; - private long currentLen; - - public SHA1() - { - reset(); - } - - public final int getDigestLength() - { - return 20; - } - - public final void reset() - { - H0 = 0x67452301; - H1 = 0xEFCDAB89; - H2 = 0x98BADCFE; - H3 = 0x10325476; - H4 = 0xC3D2E1F0; - - currentPos = 0; - currentLen = 0; - } - - public final void update(byte b[], int off, int len) - { - for (int i = off; i < (off + len); i++) - update(b[i]); - } - - public final void update(byte b[]) - { - for (int i = 0; i < b.length; i++) - update(b[i]); - } - - public final void update(byte b) - { - // System.out.println(pos + "->" + b); - msg[currentPos++] = b; - currentLen += 8; - if (currentPos == 64) - { - perform(); - currentPos = 0; - } - } - - private static final String toHexString(byte[] b) - { - final String hexChar = "0123456789ABCDEF"; - - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < b.length; i++) - { - sb.append(hexChar.charAt((b[i] >> 4) & 0x0f)); - sb.append(hexChar.charAt(b[i] & 0x0f)); - } - return sb.toString(); - } - - private final void putInt(byte[] b, int pos, int val) - { - b[pos] = (byte) (val >> 24); - b[pos + 1] = (byte) (val >> 16); - b[pos + 2] = (byte) (val >> 8); - b[pos + 3] = (byte) val; - } - - public final void digest(byte[] out) - { - digest(out, 0); - } - - public final void digest(byte[] out, int off) - { - long l = currentLen; - - update((byte) 0x80); - - // padding could be done more efficiently... - while (currentPos != 56) - update((byte) 0); - - update((byte) (l >> 56)); - update((byte) (l >> 48)); - update((byte) (l >> 40)); - update((byte) (l >> 32)); - - update((byte) (l >> 24)); - update((byte) (l >> 16)); - update((byte) (l >> 8)); - update((byte) (l)); - - // debug(80, H0, H1, H2, H3, H4); - - putInt(out, off, H0); - putInt(out, off + 4, H1); - putInt(out, off + 8, H2); - putInt(out, off + 12, H3); - putInt(out, off + 16, H4); - - reset(); - } - - /* - * private void debug(int t, int A, int B, int C, int D, int E) { - * System.out.println(t + ": " + Integer.toHexString(A).toUpperCase() + ", " + - * Integer.toHexString(B).toUpperCase() + ", " + - * Integer.toHexString(C).toUpperCase() + "," + - * Integer.toHexString(D).toUpperCase() + ", " + - * Integer.toHexString(E).toUpperCase()); } - */ - private final void perform() - { - for (int i = 0; i < 16; i++) - w[i] = ((msg[i * 4] & 0xff) << 24) | ((msg[i * 4 + 1] & 0xff) << 16) | ((msg[i * 4 + 2] & 0xff) << 8) - | ((msg[i * 4 + 3] & 0xff)); - - for (int t = 16; t < 80; t++) - { - int x = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; - w[t] = ((x << 1) | (x >>> 31)); - } - - int A = H0; - int B = H1; - int C = H2; - int D = H3; - int E = H4; - - int T; - - for (int t = 0; t <= 19; t++) - { - T = ((A << 5) | (A >>> 27)) + ((B & C) | ((~B) & D)) + E + w[t] + 0x5A827999; - E = D; - D = C; - C = ((B << 30) | (B >>> 2)); - B = A; - A = T; - // debug(t, A, B, C, D, E); - } - - for (int t = 20; t <= 39; t++) - { - T = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + w[t] + 0x6ED9EBA1; - E = D; - D = C; - C = ((B << 30) | (B >>> 2)); - B = A; - A = T; - // debug(t, A, B, C, D, E); - } - - for (int t = 40; t <= 59; t++) - { - T = ((A << 5) | (A >>> 27)) + ((B & C) | (B & D) | (C & D)) + E + w[t] + 0x8F1BBCDC; - E = D; - D = C; - C = ((B << 30) | (B >>> 2)); - B = A; - A = T; - // debug(t, A, B, C, D, E); - } - - for (int t = 60; t <= 79; t++) - { - T = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + w[t] + 0xCA62C1D6; - E = D; - D = C; - C = ((B << 30) | (B >>> 2)); - B = A; - A = T; - // debug(t, A, B, C, D, E); - } - - H0 = H0 + A; - H1 = H1 + B; - H2 = H2 + C; - H3 = H3 + D; - H4 = H4 + E; - - // debug(80, H0, H1, H2, H3, H4); - } - - public static void main(String[] args) - { - SHA1 sha = new SHA1(); - - byte[] dig1 = new byte[20]; - byte[] dig2 = new byte[20]; - byte[] dig3 = new byte[20]; +public final class SHA1 implements Digest { + private final byte msg[] = new byte[64]; + private final int[] w = new int[80]; + private int H0, H1, H2, H3, H4; + private int currentPos; + private long currentLen; + + public SHA1() { + reset(); + } + + private static final String toHexString(byte[] b) { + final String hexChar = "0123456789ABCDEF"; + + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < b.length; i++) { + sb.append(hexChar.charAt((b[i] >> 4) & 0x0f)); + sb.append(hexChar.charAt(b[i] & 0x0f)); + } + return sb.toString(); + } + + public static void main(String[] args) { + SHA1 sha = new SHA1(); + + byte[] dig1 = new byte[20]; + byte[] dig2 = new byte[20]; + byte[] dig3 = new byte[20]; /* - * We do not specify a charset name for getBytes(), since we assume that + * We do not specify a charset name for getBytes(), since we assume that * the JVM's default encoder maps the _used_ ASCII characters exactly as * getBytes("US-ASCII") would do. (Ah, yes, too lazy to catch the * exception that can be thrown by getBytes("US-ASCII")). Note: This has @@ -220,38 +46,189 @@ public static void main(String[] args) * test code. */ - sha.update("abc".getBytes()); - sha.digest(dig1); - - sha.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq".getBytes()); - sha.digest(dig2); - - for (int i = 0; i < 1000000; i++) - sha.update((byte) 'a'); - sha.digest(dig3); - - String dig1_res = toHexString(dig1); - String dig2_res = toHexString(dig2); - String dig3_res = toHexString(dig3); - - String dig1_ref = "A9993E364706816ABA3E25717850C26C9CD0D89D"; - String dig2_ref = "84983E441C3BD26EBAAE4AA1F95129E5E54670F1"; - String dig3_ref = "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"; - - if (dig1_res.equals(dig1_ref)) - System.out.println("SHA-1 Test 1 OK."); - else - System.out.println("SHA-1 Test 1 FAILED."); - - if (dig2_res.equals(dig2_ref)) - System.out.println("SHA-1 Test 2 OK."); - else - System.out.println("SHA-1 Test 2 FAILED."); - - if (dig3_res.equals(dig3_ref)) - System.out.println("SHA-1 Test 3 OK."); - else - System.out.println("SHA-1 Test 3 FAILED."); - - } + sha.update("abc".getBytes()); + sha.digest(dig1); + + sha.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq".getBytes()); + sha.digest(dig2); + + for (int i = 0; i < 1000000; i++) + sha.update((byte) 'a'); + sha.digest(dig3); + + String dig1_res = toHexString(dig1); + String dig2_res = toHexString(dig2); + String dig3_res = toHexString(dig3); + + String dig1_ref = "A9993E364706816ABA3E25717850C26C9CD0D89D"; + String dig2_ref = "84983E441C3BD26EBAAE4AA1F95129E5E54670F1"; + String dig3_ref = "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"; + + if (dig1_res.equals(dig1_ref)) + System.out.println("SHA-1 Test 1 OK."); + else + System.out.println("SHA-1 Test 1 FAILED."); + + if (dig2_res.equals(dig2_ref)) + System.out.println("SHA-1 Test 2 OK."); + else + System.out.println("SHA-1 Test 2 FAILED."); + + if (dig3_res.equals(dig3_ref)) + System.out.println("SHA-1 Test 3 OK."); + else + System.out.println("SHA-1 Test 3 FAILED."); + + } + + public final int getDigestLength() { + return 20; + } + + public final void reset() { + H0 = 0x67452301; + H1 = 0xEFCDAB89; + H2 = 0x98BADCFE; + H3 = 0x10325476; + H4 = 0xC3D2E1F0; + + currentPos = 0; + currentLen = 0; + } + + public final void update(byte b[], int off, int len) { + for (int i = off; i < (off + len); i++) + update(b[i]); + } + + public final void update(byte b[]) { + for (int i = 0; i < b.length; i++) + update(b[i]); + } + + public final void update(byte b) { + // System.out.println(pos + "->" + b); + msg[currentPos++] = b; + currentLen += 8; + if (currentPos == 64) { + perform(); + currentPos = 0; + } + } + + private final void putInt(byte[] b, int pos, int val) { + b[pos] = (byte) (val >> 24); + b[pos + 1] = (byte) (val >> 16); + b[pos + 2] = (byte) (val >> 8); + b[pos + 3] = (byte) val; + } + + public final void digest(byte[] out) { + digest(out, 0); + } + + public final void digest(byte[] out, int off) { + long l = currentLen; + + update((byte) 0x80); + + // padding could be done more efficiently... + while (currentPos != 56) + update((byte) 0); + + update((byte) (l >> 56)); + update((byte) (l >> 48)); + update((byte) (l >> 40)); + update((byte) (l >> 32)); + + update((byte) (l >> 24)); + update((byte) (l >> 16)); + update((byte) (l >> 8)); + update((byte) (l)); + + // debug(80, H0, H1, H2, H3, H4); + + putInt(out, off, H0); + putInt(out, off + 4, H1); + putInt(out, off + 8, H2); + putInt(out, off + 12, H3); + putInt(out, off + 16, H4); + + reset(); + } + + /* + * private void debug(int t, int A, int B, int C, int D, int E) { + * System.out.println(t + ": " + Integer.toHexString(A).toUpperCase() + ", " + + * Integer.toHexString(B).toUpperCase() + ", " + + * Integer.toHexString(C).toUpperCase() + "," + + * Integer.toHexString(D).toUpperCase() + ", " + + * Integer.toHexString(E).toUpperCase()); } + */ + private final void perform() { + for (int i = 0; i < 16; i++) + w[i] = ((msg[i * 4] & 0xff) << 24) | ((msg[i * 4 + 1] & 0xff) << 16) | ((msg[i * 4 + 2] & 0xff) << 8) + | ((msg[i * 4 + 3] & 0xff)); + + for (int t = 16; t < 80; t++) { + int x = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + w[t] = ((x << 1) | (x >>> 31)); + } + + int A = H0; + int B = H1; + int C = H2; + int D = H3; + int E = H4; + + int T; + + for (int t = 0; t <= 19; t++) { + T = ((A << 5) | (A >>> 27)) + ((B & C) | ((~B) & D)) + E + w[t] + 0x5A827999; + E = D; + D = C; + C = ((B << 30) | (B >>> 2)); + B = A; + A = T; + // debug(t, A, B, C, D, E); + } + + for (int t = 20; t <= 39; t++) { + T = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + w[t] + 0x6ED9EBA1; + E = D; + D = C; + C = ((B << 30) | (B >>> 2)); + B = A; + A = T; + // debug(t, A, B, C, D, E); + } + + for (int t = 40; t <= 59; t++) { + T = ((A << 5) | (A >>> 27)) + ((B & C) | (B & D) | (C & D)) + E + w[t] + 0x8F1BBCDC; + E = D; + D = C; + C = ((B << 30) | (B >>> 2)); + B = A; + A = T; + // debug(t, A, B, C, D, E); + } + + for (int t = 60; t <= 79; t++) { + T = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + w[t] + 0xCA62C1D6; + E = D; + D = C; + C = ((B << 30) | (B >>> 2)); + B = A; + A = T; + // debug(t, A, B, C, D, E); + } + + H0 = H0 + A; + H1 = H1 + B; + H2 = H2 + C; + H3 = H3 + D; + H4 = H4 + E; + + // debug(80, H0, H1, H2, H3, H4); + } } diff --git a/src/ch/ethz/ssh2/log/Logger.java b/src/ch/ethz/ssh2/log/Logger.java index afd5877..100b27c 100644 --- a/src/ch/ethz/ssh2/log/Logger.java +++ b/src/ch/ethz/ssh2/log/Logger.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.log; /** @@ -6,44 +5,37 @@ * Is not based on log4j (to reduce external dependencies). * However, if needed, something like log4j could easily be * hooked in. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Logger.java,v 1.7 2005/12/07 13:14:24 cplattne Exp $ */ -public class Logger -{ - private static final boolean enabled = false; - private static final int logLevel = 99; - - private String className; - - public final static Logger getLogger(Class x) - { - return new Logger(x); - } - - public Logger(Class x) - { - this.className = x.getName(); - } - - public final boolean isEnabled() - { - return enabled; - } - - public void log(int level, String message) - { - long now = System.currentTimeMillis(); - - if ((enabled) && (level <= logLevel)) - { - synchronized (this) - { - System.err.println(now + " : " + className + ": " + message); - // or send it to log4j or whatever... - } - } - } +public class Logger { + private static final boolean enabled = false; + private static final int logLevel = 99; + + private String className; + + public Logger(Class x) { + this.className = x.getName(); + } + + public final static Logger getLogger(Class x) { + return new Logger(x); + } + + public final boolean isEnabled() { + return enabled; + } + + public void log(int level, String message) { + long now = System.currentTimeMillis(); + + if ((enabled) && (level <= logLevel)) { + synchronized (this) { + System.err.println(now + " : " + className + ": " + message); + // or send it to log4j or whatever... + } + } + } } diff --git a/src/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java b/src/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java index be7ea92..2750a1b 100644 --- a/src/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java +++ b/src/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java @@ -4,63 +4,57 @@ /** * PacketChannelOpenConfirmation. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketChannelOpenConfirmation.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketChannelOpenConfirmation -{ - byte[] payload; - - public int recipientChannelID; - public int senderChannelID; - public int initialWindowSize; - public int maxPacketSize; - - public PacketChannelOpenConfirmation(int recipientChannelID, int senderChannelID, int initialWindowSize, - int maxPacketSize) - { - this.recipientChannelID = recipientChannelID; - this.senderChannelID = senderChannelID; - this.initialWindowSize = initialWindowSize; - this.maxPacketSize = maxPacketSize; - } - - public PacketChannelOpenConfirmation(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) - throw new IOException( - "This is not a SSH_MSG_CHANNEL_OPEN_CONFIRMATION! (" - + packet_type + ")"); - - recipientChannelID = tr.readUINT32(); - senderChannelID = tr.readUINT32(); - initialWindowSize = tr.readUINT32(); - maxPacketSize = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION); - tw.writeUINT32(recipientChannelID); - tw.writeUINT32(senderChannelID); - tw.writeUINT32(initialWindowSize); - tw.writeUINT32(maxPacketSize); - payload = tw.getBytes(); - } - return payload; - } +public class PacketChannelOpenConfirmation { + public int recipientChannelID; + public int senderChannelID; + public int initialWindowSize; + public int maxPacketSize; + byte[] payload; + + public PacketChannelOpenConfirmation(int recipientChannelID, int senderChannelID, int initialWindowSize, + int maxPacketSize) { + this.recipientChannelID = recipientChannelID; + this.senderChannelID = senderChannelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + } + + public PacketChannelOpenConfirmation(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) + throw new IOException( + "This is not a SSH_MSG_CHANNEL_OPEN_CONFIRMATION! (" + + packet_type + ")"); + + recipientChannelID = tr.readUINT32(); + senderChannelID = tr.readUINT32(); + initialWindowSize = tr.readUINT32(); + maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(senderChannelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java b/src/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java index 2968f7d..578a1cf 100644 --- a/src/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java +++ b/src/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java @@ -4,63 +4,57 @@ /** * PacketChannelOpenFailure. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketChannelOpenFailure.java,v 1.1 2005/12/05 17:13:27 cplattne Exp $ */ -public class PacketChannelOpenFailure -{ - byte[] payload; - - public int recipientChannelID; - public int reasonCode; - public String description; - public String languageTag; - - public PacketChannelOpenFailure(int recipientChannelID, int reasonCode, String description, - String languageTag) - { - this.recipientChannelID = recipientChannelID; - this.reasonCode = reasonCode; - this.description = description; - this.languageTag = languageTag; - } - - public PacketChannelOpenFailure(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_FAILURE) - throw new IOException( - "This is not a SSH_MSG_CHANNEL_OPEN_FAILURE! (" - + packet_type + ")"); - - recipientChannelID = tr.readUINT32(); - reasonCode = tr.readUINT32(); - description = tr.readString(); - languageTag = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_FAILURE packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE); - tw.writeUINT32(recipientChannelID); - tw.writeUINT32(reasonCode); - tw.writeString(description); - tw.writeString(languageTag); - payload = tw.getBytes(); - } - return payload; - } +public class PacketChannelOpenFailure { + public int recipientChannelID; + public int reasonCode; + public String description; + public String languageTag; + byte[] payload; + + public PacketChannelOpenFailure(int recipientChannelID, int reasonCode, String description, + String languageTag) { + this.recipientChannelID = recipientChannelID; + this.reasonCode = reasonCode; + this.description = description; + this.languageTag = languageTag; + } + + public PacketChannelOpenFailure(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_FAILURE) + throw new IOException( + "This is not a SSH_MSG_CHANNEL_OPEN_FAILURE! (" + + packet_type + ")"); + + recipientChannelID = tr.readUINT32(); + reasonCode = tr.readUINT32(); + description = tr.readString(); + languageTag = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN_FAILURE packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(reasonCode); + tw.writeString(description); + tw.writeString(languageTag); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java b/src/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java index 865794f..850936d 100644 --- a/src/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java +++ b/src/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java @@ -4,54 +4,48 @@ /** * PacketChannelWindowAdjust. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketChannelWindowAdjust.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketChannelWindowAdjust -{ - byte[] payload; - - public int recipientChannelID; - public int windowChange; - - public PacketChannelWindowAdjust(int recipientChannelID, int windowChange) - { - this.recipientChannelID = recipientChannelID; - this.windowChange = windowChange; - } - - public PacketChannelWindowAdjust(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST) - throw new IOException( - "This is not a SSH_MSG_CHANNEL_WINDOW_ADJUST! (" - + packet_type + ")"); - - recipientChannelID = tr.readUINT32(); - windowChange = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_WINDOW_ADJUST packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST); - tw.writeUINT32(recipientChannelID); - tw.writeUINT32(windowChange); - payload = tw.getBytes(); - } - return payload; - } +public class PacketChannelWindowAdjust { + public int recipientChannelID; + public int windowChange; + byte[] payload; + + public PacketChannelWindowAdjust(int recipientChannelID, int windowChange) { + this.recipientChannelID = recipientChannelID; + this.windowChange = windowChange; + } + + public PacketChannelWindowAdjust(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST) + throw new IOException( + "This is not a SSH_MSG_CHANNEL_WINDOW_ADJUST! (" + + packet_type + ")"); + + recipientChannelID = tr.readUINT32(); + windowChange = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_WINDOW_ADJUST packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(windowChange); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketDisconnect.java b/src/ch/ethz/ssh2/packets/PacketDisconnect.java index 20395cf..07254e0 100644 --- a/src/ch/ethz/ssh2/packets/PacketDisconnect.java +++ b/src/ch/ethz/ssh2/packets/PacketDisconnect.java @@ -1,56 +1,52 @@ package ch.ethz.ssh2.packets; + import java.io.IOException; /** * PacketDisconnect. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketDisconnect.java,v 1.3 2005/08/29 14:24:58 cplattne Exp $ */ -public class PacketDisconnect -{ - byte[] payload; - - int reason; - String desc; - String lang; - - public PacketDisconnect(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_DISCONNECT) - throw new IOException("This is not a Disconnect Packet! (" - + packet_type + ")"); - - reason = tr.readUINT32(); - desc = tr.readString(); - lang = tr.readString(); - } - - public PacketDisconnect(int reason, String desc, String lang) - { - this.reason = reason; - this.desc = desc; - this.lang = lang; - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_DISCONNECT); - tw.writeUINT32(reason); - tw.writeString(desc); - tw.writeString(lang); - payload = tw.getBytes(); - } - return payload; - } +public class PacketDisconnect { + byte[] payload; + + int reason; + String desc; + String lang; + + public PacketDisconnect(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_DISCONNECT) + throw new IOException("This is not a Disconnect Packet! (" + + packet_type + ")"); + + reason = tr.readUINT32(); + desc = tr.readString(); + lang = tr.readString(); + } + + public PacketDisconnect(int reason, String desc, String lang) { + this.reason = reason; + this.desc = desc; + this.lang = lang; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_DISCONNECT); + tw.writeUINT32(reason); + tw.writeString(desc); + tw.writeString(lang); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java b/src/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java index 2783dd7..b1d2122 100644 --- a/src/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java @@ -1,41 +1,35 @@ - package ch.ethz.ssh2.packets; /** * PacketGlobalCancelForwardRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketGlobalCancelForwardRequest.java,v 1.1 2005/12/05 17:13:27 cplattne Exp $ */ -public class PacketGlobalCancelForwardRequest -{ - byte[] payload; +public class PacketGlobalCancelForwardRequest { + public boolean wantReply; + public String bindAddress; + public int bindPort; + byte[] payload; - public boolean wantReply; - public String bindAddress; - public int bindPort; + public PacketGlobalCancelForwardRequest(boolean wantReply, String bindAddress, int bindPort) { + this.wantReply = wantReply; + this.bindAddress = bindAddress; + this.bindPort = bindPort; + } - public PacketGlobalCancelForwardRequest(boolean wantReply, String bindAddress, int bindPort) - { - this.wantReply = wantReply; - this.bindAddress = bindAddress; - this.bindPort = bindPort; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - - tw.writeString("cancel-tcpip-forward"); - tw.writeBoolean(wantReply); - tw.writeString(bindAddress); - tw.writeUINT32(bindPort); + tw.writeString("cancel-tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); - payload = tw.getBytes(); - } - return payload; - } + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java b/src/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java index a9fe448..389a7ab 100644 --- a/src/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java @@ -1,41 +1,35 @@ - package ch.ethz.ssh2.packets; /** * PacketGlobalForwardRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketGlobalForwardRequest.java,v 1.1 2005/12/05 17:13:27 cplattne Exp $ */ -public class PacketGlobalForwardRequest -{ - byte[] payload; +public class PacketGlobalForwardRequest { + public boolean wantReply; + public String bindAddress; + public int bindPort; + byte[] payload; - public boolean wantReply; - public String bindAddress; - public int bindPort; + public PacketGlobalForwardRequest(boolean wantReply, String bindAddress, int bindPort) { + this.wantReply = wantReply; + this.bindAddress = bindAddress; + this.bindPort = bindPort; + } - public PacketGlobalForwardRequest(boolean wantReply, String bindAddress, int bindPort) - { - this.wantReply = wantReply; - this.bindAddress = bindAddress; - this.bindPort = bindPort; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); - - tw.writeString("tcpip-forward"); - tw.writeBoolean(wantReply); - tw.writeString(bindAddress); - tw.writeUINT32(bindPort); + tw.writeString("tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); - payload = tw.getBytes(); - } - return payload; - } + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketIgnore.java b/src/ch/ethz/ssh2/packets/PacketIgnore.java index 55726e1..fabaea5 100644 --- a/src/ch/ethz/ssh2/packets/PacketIgnore.java +++ b/src/ch/ethz/ssh2/packets/PacketIgnore.java @@ -1,50 +1,44 @@ - package ch.ethz.ssh2.packets; import java.io.IOException; /** * PacketIgnore. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketIgnore.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketIgnore -{ - byte[] payload; - - byte[] body; +public class PacketIgnore { + byte[] payload; - public void setBody(byte[] body) - { - this.body = body; - payload = null; - } + byte[] body; - public PacketIgnore(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); + public PacketIgnore(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); + TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); + int packet_type = tr.readByte(); - if (packet_type != Packets.SSH_MSG_IGNORE) - throw new IOException("This is not a SSH_MSG_IGNORE packet! (" + packet_type + ")"); + if (packet_type != Packets.SSH_MSG_IGNORE) + throw new IOException("This is not a SSH_MSG_IGNORE packet! (" + packet_type + ")"); /* Could parse String body */ - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_IGNORE); - tw.writeString(body, 0, body.length); - payload = tw.getBytes(); - } - return payload; - } + } + + public void setBody(byte[] body) { + this.body = body; + payload = null; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_IGNORE); + tw.writeString(body, 0, body.length); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDHInit.java b/src/ch/ethz/ssh2/packets/PacketKexDHInit.java index 57f32e4..4981e4b 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDHInit.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDHInit.java @@ -4,30 +4,26 @@ /** * PacketKexDHInit. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDHInit.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketKexDHInit -{ - byte[] payload; +public class PacketKexDHInit { + byte[] payload; - BigInteger e; + BigInteger e; - public PacketKexDHInit(BigInteger e) - { - this.e = e; - } + public PacketKexDHInit(BigInteger e) { + this.e = e; + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEXDH_INIT); - tw.writeMPInt(e); - payload = tw.getBytes(); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXDH_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDHReply.java b/src/ch/ethz/ssh2/packets/PacketKexDHReply.java index 808550b..a378a69 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDHReply.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDHReply.java @@ -5,50 +5,45 @@ /** * PacketKexDHReply. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDHReply.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketKexDHReply -{ - byte[] payload; - - byte[] hostKey; - BigInteger f; - byte[] signature; - - public PacketKexDHReply(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_KEXDH_REPLY) - throw new IOException("This is not a SSH_MSG_KEXDH_REPLY! (" - + packet_type + ")"); - - hostKey = tr.readByteString(); - f = tr.readMPINT(); - signature = tr.readByteString(); - - if (tr.remain() != 0) throw new IOException("PADDING IN SSH_MSG_KEXDH_REPLY!"); - } - - public BigInteger getF() - { - return f; - } - - public byte[] getHostKey() - { - return hostKey; - } - - public byte[] getSignature() - { - return signature; - } +public class PacketKexDHReply { + byte[] payload; + + byte[] hostKey; + BigInteger f; + byte[] signature; + + public PacketKexDHReply(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXDH_REPLY) + throw new IOException("This is not a SSH_MSG_KEXDH_REPLY! (" + + packet_type + ")"); + + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); + + if (tr.remain() != 0) throw new IOException("PADDING IN SSH_MSG_KEXDH_REPLY!"); + } + + public BigInteger getF() { + return f; + } + + public byte[] getHostKey() { + return hostKey; + } + + public byte[] getSignature() { + return signature; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java b/src/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java index b750d02..1d314b3 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java @@ -5,45 +5,41 @@ /** * PacketKexDhGexGroup. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDhGexGroup.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketKexDhGexGroup -{ - byte[] payload; +public class PacketKexDhGexGroup { + byte[] payload; - BigInteger p; - BigInteger g; + BigInteger p; + BigInteger g; - public PacketKexDhGexGroup(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); + public PacketKexDhGexGroup(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); + TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); + int packet_type = tr.readByte(); - if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_GROUP) - throw new IllegalArgumentException( - "This is not a SSH_MSG_KEX_DH_GEX_GROUP! (" + packet_type - + ")"); + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_GROUP) + throw new IllegalArgumentException( + "This is not a SSH_MSG_KEX_DH_GEX_GROUP! (" + packet_type + + ")"); - p = tr.readMPINT(); - g = tr.readMPINT(); + p = tr.readMPINT(); + g = tr.readMPINT(); - if (tr.remain() != 0) - throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_GROUP!"); - } + if (tr.remain() != 0) + throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_GROUP!"); + } - public BigInteger getG() - { - return g; - } + public BigInteger getG() { + return g; + } - public BigInteger getP() - { - return p; - } + public BigInteger getP() { + return p; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDhGexInit.java b/src/ch/ethz/ssh2/packets/PacketKexDhGexInit.java index 906d7fe..ccf6023 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDhGexInit.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexInit.java @@ -4,30 +4,26 @@ /** * PacketKexDhGexInit. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDhGexInit.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketKexDhGexInit -{ - byte[] payload; +public class PacketKexDhGexInit { + byte[] payload; - BigInteger e; + BigInteger e; - public PacketKexDhGexInit(BigInteger e) - { - this.e = e; - } + public PacketKexDhGexInit(BigInteger e) { + this.e = e; + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_INIT); - tw.writeMPInt(e); - payload = tw.getBytes(); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDhGexReply.java b/src/ch/ethz/ssh2/packets/PacketKexDhGexReply.java index 716a496..7f1fdab 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDhGexReply.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexReply.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.packets; import java.io.IOException; @@ -6,50 +5,45 @@ /** * PacketKexDhGexReply. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDhGexReply.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketKexDhGexReply -{ - byte[] payload; +public class PacketKexDhGexReply { + byte[] payload; - byte[] hostKey; - BigInteger f; - byte[] signature; + byte[] hostKey; + BigInteger f; + byte[] signature; - public PacketKexDhGexReply(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); + public PacketKexDhGexReply(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); + TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); + int packet_type = tr.readByte(); - if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_REPLY) - throw new IOException("This is not a SSH_MSG_KEX_DH_GEX_REPLY! (" + packet_type + ")"); + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_REPLY) + throw new IOException("This is not a SSH_MSG_KEX_DH_GEX_REPLY! (" + packet_type + ")"); - hostKey = tr.readByteString(); - f = tr.readMPINT(); - signature = tr.readByteString(); + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); - if (tr.remain() != 0) - throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_REPLY!"); - } + if (tr.remain() != 0) + throw new IOException("PADDING IN SSH_MSG_KEX_DH_GEX_REPLY!"); + } - public BigInteger getF() - { - return f; - } + public BigInteger getF() { + return f; + } - public byte[] getHostKey() - { - return hostKey; - } + public byte[] getHostKey() { + return hostKey; + } - public byte[] getSignature() - { - return signature; - } + public byte[] getSignature() { + return signature; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java b/src/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java index a0022d7..efdbbae 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java @@ -4,36 +4,32 @@ /** * PacketKexDhGexRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDhGexRequest.java,v 1.4 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketKexDhGexRequest -{ - byte[] payload; +public class PacketKexDhGexRequest { + byte[] payload; - int min; - int n; - int max; + int min; + int n; + int max; - public PacketKexDhGexRequest(DHGexParameters para) - { - this.min = para.getMin_group_len(); - this.n = para.getPref_group_len(); - this.max = para.getMax_group_len(); - } + public PacketKexDhGexRequest(DHGexParameters para) { + this.min = para.getMin_group_len(); + this.n = para.getPref_group_len(); + this.max = para.getMax_group_len(); + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST); - tw.writeUINT32(min); - tw.writeUINT32(n); - tw.writeUINT32(max); - payload = tw.getBytes(); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST); + tw.writeUINT32(min); + tw.writeUINT32(n); + tw.writeUINT32(max); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java b/src/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java index c0f5620..bf632a3 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java +++ b/src/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java @@ -1,34 +1,29 @@ - package ch.ethz.ssh2.packets; import ch.ethz.ssh2.DHGexParameters; /** * PacketKexDhGexRequestOld. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexDhGexRequestOld.java,v 1.2 2006/09/20 12:52:46 cplattne Exp $ */ -public class PacketKexDhGexRequestOld -{ - byte[] payload; +public class PacketKexDhGexRequestOld { + byte[] payload; - int n; + int n; - public PacketKexDhGexRequestOld(DHGexParameters para) - { - this.n = para.getPref_group_len(); - } + public PacketKexDhGexRequestOld(DHGexParameters para) { + this.n = para.getPref_group_len(); + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST_OLD); - tw.writeUINT32(n); - payload = tw.getBytes(); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST_OLD); + tw.writeUINT32(n); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketKexInit.java b/src/ch/ethz/ssh2/packets/PacketKexInit.java index 98ab28b..32bbf72 100644 --- a/src/ch/ethz/ssh2/packets/PacketKexInit.java +++ b/src/ch/ethz/ssh2/packets/PacketKexInit.java @@ -1,164 +1,144 @@ - package ch.ethz.ssh2.packets; -import java.io.IOException; -import java.security.SecureRandom; - import ch.ethz.ssh2.crypto.CryptoWishList; import ch.ethz.ssh2.transport.KexParameters; +import java.io.IOException; +import java.security.SecureRandom; + /** * PacketKexInit. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketKexInit.java,v 1.4 2006/02/14 19:43:15 cplattne Exp $ */ -public class PacketKexInit -{ - byte[] payload; - - KexParameters kp = new KexParameters(); - - public PacketKexInit(CryptoWishList cwl, SecureRandom rnd) - { - kp.cookie = new byte[16]; - rnd.nextBytes(kp.cookie); - - kp.kex_algorithms = cwl.kexAlgorithms; - kp.server_host_key_algorithms = cwl.serverHostKeyAlgorithms; - kp.encryption_algorithms_client_to_server = cwl.c2s_enc_algos; - kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; - kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; - kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; - kp.compression_algorithms_client_to_server = new String[] { "none" }; - kp.compression_algorithms_server_to_client = new String[] { "none" }; - kp.languages_client_to_server = new String[] {}; - kp.languages_server_to_client = new String[] {}; - kp.first_kex_packet_follows = false; - kp.reserved_field1 = 0; - } - - public PacketKexInit(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_KEXINIT) - throw new IOException("This is not a KexInitPacket! (" + packet_type + ")"); - - kp.cookie = tr.readBytes(16); - kp.kex_algorithms = tr.readNameList(); - kp.server_host_key_algorithms = tr.readNameList(); - kp.encryption_algorithms_client_to_server = tr.readNameList(); - kp.encryption_algorithms_server_to_client = tr.readNameList(); - kp.mac_algorithms_client_to_server = tr.readNameList(); - kp.mac_algorithms_server_to_client = tr.readNameList(); - kp.compression_algorithms_client_to_server = tr.readNameList(); - kp.compression_algorithms_server_to_client = tr.readNameList(); - kp.languages_client_to_server = tr.readNameList(); - kp.languages_server_to_client = tr.readNameList(); - kp.first_kex_packet_follows = tr.readBoolean(); - kp.reserved_field1 = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in KexInitPacket!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_KEXINIT); - tw.writeBytes(kp.cookie, 0, 16); - tw.writeNameList(kp.kex_algorithms); - tw.writeNameList(kp.server_host_key_algorithms); - tw.writeNameList(kp.encryption_algorithms_client_to_server); - tw.writeNameList(kp.encryption_algorithms_server_to_client); - tw.writeNameList(kp.mac_algorithms_client_to_server); - tw.writeNameList(kp.mac_algorithms_server_to_client); - tw.writeNameList(kp.compression_algorithms_client_to_server); - tw.writeNameList(kp.compression_algorithms_server_to_client); - tw.writeNameList(kp.languages_client_to_server); - tw.writeNameList(kp.languages_server_to_client); - tw.writeBoolean(kp.first_kex_packet_follows); - tw.writeUINT32(kp.reserved_field1); - payload = tw.getBytes(); - } - return payload; - } - - public KexParameters getKexParameters() - { - return kp; - } - - public String[] getCompression_algorithms_client_to_server() - { - return kp.compression_algorithms_client_to_server; - } - - public String[] getCompression_algorithms_server_to_client() - { - return kp.compression_algorithms_server_to_client; - } - - public byte[] getCookie() - { - return kp.cookie; - } - - public String[] getEncryption_algorithms_client_to_server() - { - return kp.encryption_algorithms_client_to_server; - } - - public String[] getEncryption_algorithms_server_to_client() - { - return kp.encryption_algorithms_server_to_client; - } - - public boolean isFirst_kex_packet_follows() - { - return kp.first_kex_packet_follows; - } - - public String[] getKex_algorithms() - { - return kp.kex_algorithms; - } - - public String[] getLanguages_client_to_server() - { - return kp.languages_client_to_server; - } - - public String[] getLanguages_server_to_client() - { - return kp.languages_server_to_client; - } - - public String[] getMac_algorithms_client_to_server() - { - return kp.mac_algorithms_client_to_server; - } - - public String[] getMac_algorithms_server_to_client() - { - return kp.mac_algorithms_server_to_client; - } - - public int getReserved_field1() - { - return kp.reserved_field1; - } - - public String[] getServer_host_key_algorithms() - { - return kp.server_host_key_algorithms; - } +public class PacketKexInit { + byte[] payload; + + KexParameters kp = new KexParameters(); + + public PacketKexInit(CryptoWishList cwl, SecureRandom rnd) { + kp.cookie = new byte[16]; + rnd.nextBytes(kp.cookie); + + kp.kex_algorithms = cwl.kexAlgorithms; + kp.server_host_key_algorithms = cwl.serverHostKeyAlgorithms; + kp.encryption_algorithms_client_to_server = cwl.c2s_enc_algos; + kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; + kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; + kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; + kp.compression_algorithms_client_to_server = new String[]{"none"}; + kp.compression_algorithms_server_to_client = new String[]{"none"}; + kp.languages_client_to_server = new String[]{}; + kp.languages_server_to_client = new String[]{}; + kp.first_kex_packet_follows = false; + kp.reserved_field1 = 0; + } + + public PacketKexInit(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXINIT) + throw new IOException("This is not a KexInitPacket! (" + packet_type + ")"); + + kp.cookie = tr.readBytes(16); + kp.kex_algorithms = tr.readNameList(); + kp.server_host_key_algorithms = tr.readNameList(); + kp.encryption_algorithms_client_to_server = tr.readNameList(); + kp.encryption_algorithms_server_to_client = tr.readNameList(); + kp.mac_algorithms_client_to_server = tr.readNameList(); + kp.mac_algorithms_server_to_client = tr.readNameList(); + kp.compression_algorithms_client_to_server = tr.readNameList(); + kp.compression_algorithms_server_to_client = tr.readNameList(); + kp.languages_client_to_server = tr.readNameList(); + kp.languages_server_to_client = tr.readNameList(); + kp.first_kex_packet_follows = tr.readBoolean(); + kp.reserved_field1 = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in KexInitPacket!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXINIT); + tw.writeBytes(kp.cookie, 0, 16); + tw.writeNameList(kp.kex_algorithms); + tw.writeNameList(kp.server_host_key_algorithms); + tw.writeNameList(kp.encryption_algorithms_client_to_server); + tw.writeNameList(kp.encryption_algorithms_server_to_client); + tw.writeNameList(kp.mac_algorithms_client_to_server); + tw.writeNameList(kp.mac_algorithms_server_to_client); + tw.writeNameList(kp.compression_algorithms_client_to_server); + tw.writeNameList(kp.compression_algorithms_server_to_client); + tw.writeNameList(kp.languages_client_to_server); + tw.writeNameList(kp.languages_server_to_client); + tw.writeBoolean(kp.first_kex_packet_follows); + tw.writeUINT32(kp.reserved_field1); + payload = tw.getBytes(); + } + return payload; + } + + public KexParameters getKexParameters() { + return kp; + } + + public String[] getCompression_algorithms_client_to_server() { + return kp.compression_algorithms_client_to_server; + } + + public String[] getCompression_algorithms_server_to_client() { + return kp.compression_algorithms_server_to_client; + } + + public byte[] getCookie() { + return kp.cookie; + } + + public String[] getEncryption_algorithms_client_to_server() { + return kp.encryption_algorithms_client_to_server; + } + + public String[] getEncryption_algorithms_server_to_client() { + return kp.encryption_algorithms_server_to_client; + } + + public boolean isFirst_kex_packet_follows() { + return kp.first_kex_packet_follows; + } + + public String[] getKex_algorithms() { + return kp.kex_algorithms; + } + + public String[] getLanguages_client_to_server() { + return kp.languages_client_to_server; + } + + public String[] getLanguages_server_to_client() { + return kp.languages_server_to_client; + } + + public String[] getMac_algorithms_client_to_server() { + return kp.mac_algorithms_client_to_server; + } + + public String[] getMac_algorithms_server_to_client() { + return kp.mac_algorithms_server_to_client; + } + + public int getReserved_field1() { + return kp.reserved_field1; + } + + public String[] getServer_host_key_algorithms() { + return kp.server_host_key_algorithms; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketNewKeys.java b/src/ch/ethz/ssh2/packets/PacketNewKeys.java index 6f2e618..213e1dc 100644 --- a/src/ch/ethz/ssh2/packets/PacketNewKeys.java +++ b/src/ch/ethz/ssh2/packets/PacketNewKeys.java @@ -4,43 +4,38 @@ /** * PacketNewKeys. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketNewKeys.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketNewKeys -{ - byte[] payload; - - public PacketNewKeys() - { - } - - public PacketNewKeys(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_NEWKEYS) - throw new IOException("This is not a SSH_MSG_NEWKEYS! (" - + packet_type + ")"); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_NEWKEYS packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_NEWKEYS); - payload = tw.getBytes(); - } - return payload; - } +public class PacketNewKeys { + byte[] payload; + + public PacketNewKeys() { + } + + public PacketNewKeys(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_NEWKEYS) + throw new IOException("This is not a SSH_MSG_NEWKEYS! (" + + packet_type + ")"); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_NEWKEYS packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_NEWKEYS); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java b/src/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java index 4ef346d..b6f9484 100644 --- a/src/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java +++ b/src/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java @@ -3,54 +3,50 @@ /** * PacketOpenDirectTCPIPChannel. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketOpenDirectTCPIPChannel.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketOpenDirectTCPIPChannel -{ - byte[] payload; - - int channelID; - int initialWindowSize; - int maxPacketSize; - - String host_to_connect; - int port_to_connect; - String originator_IP_address; - int originator_port; - - public PacketOpenDirectTCPIPChannel(int channelID, int initialWindowSize, int maxPacketSize, - String host_to_connect, int port_to_connect, String originator_IP_address, - int originator_port) - { - this.channelID = channelID; - this.initialWindowSize = initialWindowSize; - this.maxPacketSize = maxPacketSize; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - this.originator_IP_address = originator_IP_address; - this.originator_port = originator_port; - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); - tw.writeString("direct-tcpip"); - tw.writeUINT32(channelID); - tw.writeUINT32(initialWindowSize); - tw.writeUINT32(maxPacketSize); - tw.writeString(host_to_connect); - tw.writeUINT32(port_to_connect); - tw.writeString(originator_IP_address); - tw.writeUINT32(originator_port); - - payload = tw.getBytes(); - } - return payload; - } +public class PacketOpenDirectTCPIPChannel { + byte[] payload; + + int channelID; + int initialWindowSize; + int maxPacketSize; + + String host_to_connect; + int port_to_connect; + String originator_IP_address; + int originator_port; + + public PacketOpenDirectTCPIPChannel(int channelID, int initialWindowSize, int maxPacketSize, + String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) { + this.channelID = channelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + this.originator_IP_address = originator_IP_address; + this.originator_port = originator_port; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("direct-tcpip"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + tw.writeString(host_to_connect); + tw.writeUINT32(port_to_connect); + tw.writeString(originator_IP_address); + tw.writeUINT32(originator_port); + + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java b/src/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java index 4c465e4..6556a75 100644 --- a/src/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java +++ b/src/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java @@ -4,59 +4,54 @@ /** * PacketOpenSessionChannel. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketOpenSessionChannel.java,v 1.2 2005/08/24 17:54:10 cplattne Exp $ */ -public class PacketOpenSessionChannel -{ - byte[] payload; - - int channelID; - int initialWindowSize; - int maxPacketSize; - - public PacketOpenSessionChannel(int channelID, int initialWindowSize, - int maxPacketSize) - { - this.channelID = channelID; - this.initialWindowSize = initialWindowSize; - this.maxPacketSize = maxPacketSize; - } - - public PacketOpenSessionChannel(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN) - throw new IOException("This is not a SSH_MSG_CHANNEL_OPEN! (" - + packet_type + ")"); - - channelID = tr.readUINT32(); - initialWindowSize = tr.readUINT32(); - maxPacketSize = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); - tw.writeString("session"); - tw.writeUINT32(channelID); - tw.writeUINT32(initialWindowSize); - tw.writeUINT32(maxPacketSize); - payload = tw.getBytes(); - } - return payload; - } +public class PacketOpenSessionChannel { + byte[] payload; + + int channelID; + int initialWindowSize; + int maxPacketSize; + + public PacketOpenSessionChannel(int channelID, int initialWindowSize, + int maxPacketSize) { + this.channelID = channelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + } + + public PacketOpenSessionChannel(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN) + throw new IOException("This is not a SSH_MSG_CHANNEL_OPEN! (" + + packet_type + ")"); + + channelID = tr.readUINT32(); + initialWindowSize = tr.readUINT32(); + maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_CHANNEL_OPEN packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("session"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketServiceAccept.java b/src/ch/ethz/ssh2/packets/PacketServiceAccept.java index 68f63a5..2e9ec12 100644 --- a/src/ch/ethz/ssh2/packets/PacketServiceAccept.java +++ b/src/ch/ethz/ssh2/packets/PacketServiceAccept.java @@ -4,49 +4,44 @@ /** * PacketServiceAccept. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketServiceAccept.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketServiceAccept -{ - byte[] payload; - - String serviceName; - - public PacketServiceAccept(String serviceName) - { - this.serviceName = serviceName; - } - - public PacketServiceAccept(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_SERVICE_ACCEPT) - throw new IOException("This is not a SSH_MSG_SERVICE_ACCEPT! (" - + packet_type + ")"); - - serviceName = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_SERVICE_ACCEPT packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_SERVICE_ACCEPT); - tw.writeString(serviceName); - payload = tw.getBytes(); - } - return payload; - } +public class PacketServiceAccept { + byte[] payload; + + String serviceName; + + public PacketServiceAccept(String serviceName) { + this.serviceName = serviceName; + } + + public PacketServiceAccept(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_ACCEPT) + throw new IOException("This is not a SSH_MSG_SERVICE_ACCEPT! (" + + packet_type + ")"); + + serviceName = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_SERVICE_ACCEPT packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_ACCEPT); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketServiceRequest.java b/src/ch/ethz/ssh2/packets/PacketServiceRequest.java index 2b15225..1a6bcce 100644 --- a/src/ch/ethz/ssh2/packets/PacketServiceRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketServiceRequest.java @@ -4,49 +4,44 @@ /** * PacketServiceRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketServiceRequest.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketServiceRequest -{ - byte[] payload; - - String serviceName; - - public PacketServiceRequest(String serviceName) - { - this.serviceName = serviceName; - } - - public PacketServiceRequest(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_SERVICE_REQUEST) - throw new IOException("This is not a SSH_MSG_SERVICE_REQUEST! (" - + packet_type + ")"); - - serviceName = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_SERVICE_REQUEST packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_SERVICE_REQUEST); - tw.writeString(serviceName); - payload = tw.getBytes(); - } - return payload; - } +public class PacketServiceRequest { + byte[] payload; + + String serviceName; + + public PacketServiceRequest(String serviceName) { + this.serviceName = serviceName; + } + + public PacketServiceRequest(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_REQUEST) + throw new IOException("This is not a SSH_MSG_SERVICE_REQUEST! (" + + packet_type + ")"); + + serviceName = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_SERVICE_REQUEST packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_REQUEST); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketSessionExecCommand.java b/src/ch/ethz/ssh2/packets/PacketSessionExecCommand.java index bb9e607..2902674 100644 --- a/src/ch/ethz/ssh2/packets/PacketSessionExecCommand.java +++ b/src/ch/ethz/ssh2/packets/PacketSessionExecCommand.java @@ -3,37 +3,32 @@ /** * PacketSessionExecCommand. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketSessionExecCommand.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketSessionExecCommand -{ - byte[] payload; +public class PacketSessionExecCommand { + public int recipientChannelID; + public boolean wantReply; + public String command; + byte[] payload; - public int recipientChannelID; - public boolean wantReply; - public String command; + public PacketSessionExecCommand(int recipientChannelID, boolean wantReply, String command) { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + this.command = command; + } - public PacketSessionExecCommand(int recipientChannelID, boolean wantReply, String command) - { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.command = command; - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("exec"); - tw.writeBoolean(wantReply); - tw.writeString(command); - payload = tw.getBytes(); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("exec"); + tw.writeBoolean(wantReply); + tw.writeString(command); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java b/src/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java index 612e68d..ee1ca9f 100644 --- a/src/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java @@ -3,55 +3,50 @@ /** * PacketSessionPtyRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketSessionPtyRequest.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketSessionPtyRequest -{ - byte[] payload; +public class PacketSessionPtyRequest { + public int recipientChannelID; + public boolean wantReply; + public String term; + public int character_width; + public int character_height; + public int pixel_width; + public int pixel_height; + public byte[] terminal_modes; + byte[] payload; - public int recipientChannelID; - public boolean wantReply; - public String term; - public int character_width; - public int character_height; - public int pixel_width; - public int pixel_height; - public byte[] terminal_modes; + public PacketSessionPtyRequest(int recipientChannelID, boolean wantReply, String term, + int character_width, int character_height, int pixel_width, int pixel_height, + byte[] terminal_modes) { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + this.term = term; + this.character_width = character_width; + this.character_height = character_height; + this.pixel_width = pixel_width; + this.pixel_height = pixel_height; + this.terminal_modes = terminal_modes; + } - public PacketSessionPtyRequest(int recipientChannelID, boolean wantReply, String term, - int character_width, int character_height, int pixel_width, int pixel_height, - byte[] terminal_modes) - { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.term = term; - this.character_width = character_width; - this.character_height = character_height; - this.pixel_width = pixel_width; - this.pixel_height = pixel_height; - this.terminal_modes = terminal_modes; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("pty-req"); + tw.writeBoolean(wantReply); + tw.writeString(term); + tw.writeUINT32(character_width); + tw.writeUINT32(character_height); + tw.writeUINT32(pixel_width); + tw.writeUINT32(pixel_height); + tw.writeString(terminal_modes, 0, terminal_modes.length); - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("pty-req"); - tw.writeBoolean(wantReply); - tw.writeString(term); - tw.writeUINT32(character_width); - tw.writeUINT32(character_height); - tw.writeUINT32(pixel_width); - tw.writeUINT32(pixel_height); - tw.writeString(terminal_modes, 0, terminal_modes.length); - - payload = tw.getBytes(); - } - return payload; - } + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketSessionStartShell.java b/src/ch/ethz/ssh2/packets/PacketSessionStartShell.java index eb2e7b3..a0a13cb 100644 --- a/src/ch/ethz/ssh2/packets/PacketSessionStartShell.java +++ b/src/ch/ethz/ssh2/packets/PacketSessionStartShell.java @@ -1,36 +1,30 @@ - package ch.ethz.ssh2.packets; /** * PacketSessionStartShell. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketSessionStartShell.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketSessionStartShell -{ - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; +public class PacketSessionStartShell { + public int recipientChannelID; + public boolean wantReply; + byte[] payload; - public PacketSessionStartShell(int recipientChannelID, boolean wantReply) - { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - } + public PacketSessionStartShell(int recipientChannelID, boolean wantReply) { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("shell"); - tw.writeBoolean(wantReply); - payload = tw.getBytes(); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("shell"); + tw.writeBoolean(wantReply); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java b/src/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java index 8889934..21bb3bf 100644 --- a/src/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java @@ -3,38 +3,33 @@ /** * PacketSessionSubsystemRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketSessionSubsystemRequest.java,v 1.2 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketSessionSubsystemRequest -{ - byte[] payload; +public class PacketSessionSubsystemRequest { + public int recipientChannelID; + public boolean wantReply; + public String subsystem; + byte[] payload; - public int recipientChannelID; - public boolean wantReply; - public String subsystem; + public PacketSessionSubsystemRequest(int recipientChannelID, boolean wantReply, String subsystem) { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + this.subsystem = subsystem; + } - public PacketSessionSubsystemRequest(int recipientChannelID, boolean wantReply, String subsystem) - { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - this.subsystem = subsystem; - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("subsystem"); - tw.writeBoolean(wantReply); - tw.writeString(subsystem); - payload = tw.getBytes(); - tw.getBytes(payload); - } - return payload; - } + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("subsystem"); + tw.writeBoolean(wantReply); + tw.writeString(subsystem); + payload = tw.getBytes(); + tw.getBytes(payload); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketSessionX11Request.java b/src/ch/ethz/ssh2/packets/PacketSessionX11Request.java index 6438ad6..132d7a2 100644 --- a/src/ch/ethz/ssh2/packets/PacketSessionX11Request.java +++ b/src/ch/ethz/ssh2/packets/PacketSessionX11Request.java @@ -1,53 +1,46 @@ - package ch.ethz.ssh2.packets; /** * PacketSessionX11Request. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketSessionX11Request.java,v 1.2 2005/12/05 17:13:27 cplattne Exp $ */ -public class PacketSessionX11Request -{ - byte[] payload; - - public int recipientChannelID; - public boolean wantReply; - - public boolean singleConnection; - String x11AuthenticationProtocol; - String x11AuthenticationCookie; - int x11ScreenNumber; - - public PacketSessionX11Request(int recipientChannelID, boolean wantReply, boolean singleConnection, - String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) - { - this.recipientChannelID = recipientChannelID; - this.wantReply = wantReply; - - this.singleConnection = singleConnection; - this.x11AuthenticationProtocol = x11AuthenticationProtocol; - this.x11AuthenticationCookie = x11AuthenticationCookie; - this.x11ScreenNumber = x11ScreenNumber; - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); - tw.writeUINT32(recipientChannelID); - tw.writeString("x11-req"); - tw.writeBoolean(wantReply); - - tw.writeBoolean(singleConnection); - tw.writeString(x11AuthenticationProtocol); - tw.writeString(x11AuthenticationCookie); - tw.writeUINT32(x11ScreenNumber); - - payload = tw.getBytes(); - } - return payload; - } +public class PacketSessionX11Request { + public int recipientChannelID; + public boolean wantReply; + public boolean singleConnection; + byte[] payload; + String x11AuthenticationProtocol; + String x11AuthenticationCookie; + int x11ScreenNumber; + + public PacketSessionX11Request(int recipientChannelID, boolean wantReply, boolean singleConnection, + String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) { + this.recipientChannelID = recipientChannelID; + this.wantReply = wantReply; + + this.singleConnection = singleConnection; + this.x11AuthenticationProtocol = x11AuthenticationProtocol; + this.x11AuthenticationCookie = x11AuthenticationCookie; + this.x11ScreenNumber = x11ScreenNumber; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("x11-req"); + tw.writeBoolean(wantReply); + + tw.writeBoolean(singleConnection); + tw.writeString(x11AuthenticationProtocol); + tw.writeString(x11AuthenticationCookie); + tw.writeUINT32(x11ScreenNumber); + + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthBanner.java b/src/ch/ethz/ssh2/packets/PacketUserauthBanner.java index b2eb1b1..049c03f 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthBanner.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthBanner.java @@ -4,57 +4,51 @@ /** * PacketUserauthBanner. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthBanner.java,v 1.2 2005/08/24 17:54:08 cplattne Exp $ */ -public class PacketUserauthBanner -{ - byte[] payload; - - String message; - String language; - - public PacketUserauthBanner(String message, String language) - { - this.message = message; - this.language = language; - } - - public String getBanner() - { - return message; - } - - public PacketUserauthBanner(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_BANNER) - throw new IOException("This is not a SSH_MSG_USERAUTH_BANNER! (" + packet_type + ")"); - - message = tr.readString("UTF-8"); - language = tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_BANNER); - tw.writeString(message); - tw.writeString(language); - payload = tw.getBytes(); - } - return payload; - } +public class PacketUserauthBanner { + byte[] payload; + + String message; + String language; + + public PacketUserauthBanner(String message, String language) { + this.message = message; + this.language = language; + } + + public PacketUserauthBanner(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_BANNER) + throw new IOException("This is not a SSH_MSG_USERAUTH_BANNER! (" + packet_type + ")"); + + message = tr.readString("UTF-8"); + language = tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); + } + + public String getBanner() { + return message; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_BANNER); + tw.writeString(message); + tw.writeString(language); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthFailure.java b/src/ch/ethz/ssh2/packets/PacketUserauthFailure.java index a606b51..f13d037 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthFailure.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthFailure.java @@ -1,53 +1,47 @@ - package ch.ethz.ssh2.packets; import java.io.IOException; /** * PacketUserauthBanner. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthFailure.java,v 1.3 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketUserauthFailure -{ - byte[] payload; +public class PacketUserauthFailure { + byte[] payload; - String[] authThatCanContinue; - boolean partialSuccess; + String[] authThatCanContinue; + boolean partialSuccess; - public PacketUserauthFailure(String[] authThatCanContinue, boolean partialSuccess) - { - this.authThatCanContinue = authThatCanContinue; - this.partialSuccess = partialSuccess; - } + public PacketUserauthFailure(String[] authThatCanContinue, boolean partialSuccess) { + this.authThatCanContinue = authThatCanContinue; + this.partialSuccess = partialSuccess; + } - public PacketUserauthFailure(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); + public PacketUserauthFailure(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); + TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); + int packet_type = tr.readByte(); - if (packet_type != Packets.SSH_MSG_USERAUTH_FAILURE) - throw new IOException("This is not a SSH_MSG_USERAUTH_FAILURE! (" + packet_type + ")"); + if (packet_type != Packets.SSH_MSG_USERAUTH_FAILURE) + throw new IOException("This is not a SSH_MSG_USERAUTH_FAILURE! (" + packet_type + ")"); - authThatCanContinue = tr.readNameList(); - partialSuccess = tr.readBoolean(); + authThatCanContinue = tr.readNameList(); + partialSuccess = tr.readBoolean(); - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_FAILURE packet!"); - } + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_FAILURE packet!"); + } - public String[] getAuthThatCanContinue() - { - return authThatCanContinue; - } + public String[] getAuthThatCanContinue() { + return authThatCanContinue; + } - public boolean isPartialSuccess() - { - return partialSuccess; - } + public boolean isPartialSuccess() { + return partialSuccess; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java b/src/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java index 94a7b8b..4c8464f 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java @@ -1,84 +1,74 @@ - package ch.ethz.ssh2.packets; import java.io.IOException; /** * PacketUserauthInfoRequest. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthInfoRequest.java,v 1.2 2005/07/21 15:28:51 cplattne Exp $ */ -public class PacketUserauthInfoRequest -{ - byte[] payload; - - String name; - String instruction; - String languageTag; - int numPrompts; - - String prompt[]; - boolean echo[]; - - public PacketUserauthInfoRequest(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_INFO_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_INFO_REQUEST! (" + packet_type + ")"); - - name = tr.readString(); - instruction = tr.readString(); - languageTag = tr.readString(); - - numPrompts = tr.readUINT32(); - - prompt = new String[numPrompts]; - echo = new boolean[numPrompts]; - - for (int i = 0; i < numPrompts; i++) - { - prompt[i] = tr.readString(); - echo[i] = tr.readBoolean(); - } - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_INFO_REQUEST packet!"); - } - - public boolean[] getEcho() - { - return echo; - } - - public String getInstruction() - { - return instruction; - } - - public String getLanguageTag() - { - return languageTag; - } - - public String getName() - { - return name; - } - - public int getNumPrompts() - { - return numPrompts; - } - - public String[] getPrompt() - { - return prompt; - } +public class PacketUserauthInfoRequest { + byte[] payload; + + String name; + String instruction; + String languageTag; + int numPrompts; + + String prompt[]; + boolean echo[]; + + public PacketUserauthInfoRequest(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_INFO_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_INFO_REQUEST! (" + packet_type + ")"); + + name = tr.readString(); + instruction = tr.readString(); + languageTag = tr.readString(); + + numPrompts = tr.readUINT32(); + + prompt = new String[numPrompts]; + echo = new boolean[numPrompts]; + + for (int i = 0; i < numPrompts; i++) { + prompt[i] = tr.readString(); + echo[i] = tr.readBoolean(); + } + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_INFO_REQUEST packet!"); + } + + public boolean[] getEcho() { + return echo; + } + + public String getInstruction() { + return instruction; + } + + public String getLanguageTag() { + return languageTag; + } + + public String getName() { + return name; + } + + public int getNumPrompts() { + return numPrompts; + } + + public String[] getPrompt() { + return prompt; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java b/src/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java index 149d4bc..02f22c8 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java @@ -1,35 +1,30 @@ - package ch.ethz.ssh2.packets; /** * PacketUserauthInfoResponse. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthInfoResponse.java,v 1.3 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketUserauthInfoResponse -{ - byte[] payload; +public class PacketUserauthInfoResponse { + byte[] payload; - String[] responses; + String[] responses; - public PacketUserauthInfoResponse(String[] responses) - { - this.responses = responses; - } + public PacketUserauthInfoResponse(String[] responses) { + this.responses = responses; + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_INFO_RESPONSE); - tw.writeUINT32(responses.length); - for (int i = 0; i < responses.length; i++) - tw.writeString(responses[i]); + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_INFO_RESPONSE); + tw.writeUINT32(responses.length); + for (int i = 0; i < responses.length; i++) + tw.writeString(responses[i]); - payload = tw.getBytes(); - } - return payload; - } + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java b/src/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java index f1f0c9a..0497e80 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java @@ -1,42 +1,37 @@ - package ch.ethz.ssh2.packets; /** * PacketUserauthRequestInteractive. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthRequestInteractive.java,v 1.3 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketUserauthRequestInteractive -{ - byte[] payload; +public class PacketUserauthRequestInteractive { + byte[] payload; - String userName; - String serviceName; - String[] submethods; + String userName; + String serviceName; + String[] submethods; - public PacketUserauthRequestInteractive(String serviceName, String user, String[] submethods) - { - this.serviceName = serviceName; - this.userName = user; - this.submethods = submethods; - } + public PacketUserauthRequestInteractive(String serviceName, String user, String[] submethods) { + this.serviceName = serviceName; + this.userName = user; + this.submethods = submethods; + } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("keyboard-interactive"); - tw.writeString(""); // draft-ietf-secsh-newmodes-04.txt says that - // the language tag should be empty. - tw.writeNameList(submethods); + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("keyboard-interactive"); + tw.writeString(""); // draft-ietf-secsh-newmodes-04.txt says that + // the language tag should be empty. + tw.writeNameList(submethods); - payload = tw.getBytes(); - } - return payload; - } + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java b/src/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java index b3354a0..ddccda7 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java @@ -4,58 +4,53 @@ /** * PacketUserauthRequestPassword. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthRequestNone.java,v 1.2 2005/08/24 17:54:08 cplattne Exp $ */ -public class PacketUserauthRequestNone -{ - byte[] payload; - - String userName; - String serviceName; - - public PacketUserauthRequestNone(String serviceName, String user) - { - this.serviceName = serviceName; - this.userName = user; - } - - public PacketUserauthRequestNone(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); - - userName = tr.readString(); - serviceName = tr.readString(); - - String method = tr.readString(); - - if (method.equals("none") == false) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type none!"); - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("none"); - payload = tw.getBytes(); - } - return payload; - } +public class PacketUserauthRequestNone { + byte[] payload; + + String userName; + String serviceName; + + public PacketUserauthRequestNone(String serviceName, String user) { + this.serviceName = serviceName; + this.userName = user; + } + + public PacketUserauthRequestNone(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); + + userName = tr.readString(); + serviceName = tr.readString(); + + String method = tr.readString(); + + if (method.equals("none") == false) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type none!"); + + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("none"); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java b/src/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java index 58a8f4e..cf484ef 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java @@ -4,64 +4,59 @@ /** * PacketUserauthRequestPassword. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthRequestPassword.java,v 1.3 2005/08/24 17:54:09 cplattne Exp $ */ -public class PacketUserauthRequestPassword -{ - byte[] payload; +public class PacketUserauthRequestPassword { + byte[] payload; - String userName; - String serviceName; - String password; + String userName; + String serviceName; + String password; - public PacketUserauthRequestPassword(String serviceName, String user, String pass) - { - this.serviceName = serviceName; - this.userName = user; - this.password = pass; - } + public PacketUserauthRequestPassword(String serviceName, String user, String pass) { + this.serviceName = serviceName; + this.userName = user; + this.password = pass; + } - public PacketUserauthRequestPassword(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); + public PacketUserauthRequestPassword(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); - TypesReader tr = new TypesReader(payload, off, len); + TypesReader tr = new TypesReader(payload, off, len); - int packet_type = tr.readByte(); + int packet_type = tr.readByte(); - if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + packet_type + ")"); - userName = tr.readString(); - serviceName = tr.readString(); + userName = tr.readString(); + serviceName = tr.readString(); - String method = tr.readString(); + String method = tr.readString(); - if (method.equals("password") == false) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type password!"); + if (method.equals("password") == false) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST with type password!"); /* ... */ - - if (tr.remain() != 0) - throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); - } - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("password"); - tw.writeBoolean(false); - tw.writeString(password); - payload = tw.getBytes(); - } - return payload; - } + if (tr.remain() != 0) + throw new IOException("Padding in SSH_MSG_USERAUTH_REQUEST packet!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("password"); + tw.writeBoolean(false); + tw.writeString(password); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java b/src/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java index c09cbdd..a684b74 100644 --- a/src/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java +++ b/src/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java @@ -4,62 +4,57 @@ /** * PacketUserauthRequestPublicKey. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: PacketUserauthRequestPublicKey.java,v 1.2 2005/08/24 17:54:08 cplattne Exp $ */ -public class PacketUserauthRequestPublicKey -{ - byte[] payload; - - String userName; - String serviceName; - String password; - String pkAlgoName; - byte[] pk; - byte[] sig; - - public PacketUserauthRequestPublicKey(String serviceName, String user, - String pkAlgorithmName, byte[] pk, byte[] sig) - { - this.serviceName = serviceName; - this.userName = user; - this.pkAlgoName = pkAlgorithmName; - this.pk = pk; - this.sig = sig; - } - - public PacketUserauthRequestPublicKey(byte payload[], int off, int len) throws IOException - { - this.payload = new byte[len]; - System.arraycopy(payload, off, this.payload, 0, len); - - TypesReader tr = new TypesReader(payload, off, len); - - int packet_type = tr.readByte(); - - if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) - throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" - + packet_type + ")"); - - throw new IOException("Not implemented!"); - } - - public byte[] getPayload() - { - if (payload == null) - { - TypesWriter tw = new TypesWriter(); - tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); - tw.writeString(userName); - tw.writeString(serviceName); - tw.writeString("publickey"); - tw.writeBoolean(true); - tw.writeString(pkAlgoName); - tw.writeString(pk, 0, pk.length); - tw.writeString(sig, 0, sig.length); - payload = tw.getBytes(); - } - return payload; - } +public class PacketUserauthRequestPublicKey { + byte[] payload; + + String userName; + String serviceName; + String password; + String pkAlgoName; + byte[] pk; + byte[] sig; + + public PacketUserauthRequestPublicKey(String serviceName, String user, + String pkAlgorithmName, byte[] pk, byte[] sig) { + this.serviceName = serviceName; + this.userName = user; + this.pkAlgoName = pkAlgorithmName; + this.pk = pk; + this.sig = sig; + } + + public PacketUserauthRequestPublicKey(byte payload[], int off, int len) throws IOException { + this.payload = new byte[len]; + System.arraycopy(payload, off, this.payload, 0, len); + + TypesReader tr = new TypesReader(payload, off, len); + + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) + throw new IOException("This is not a SSH_MSG_USERAUTH_REQUEST! (" + + packet_type + ")"); + + throw new IOException("Not implemented!"); + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(userName); + tw.writeString(serviceName); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(pkAlgoName); + tw.writeString(pk, 0, pk.length); + tw.writeString(sig, 0, sig.length); + payload = tw.getBytes(); + } + return payload; + } } diff --git a/src/ch/ethz/ssh2/packets/Packets.java b/src/ch/ethz/ssh2/packets/Packets.java index 67533bb..6cdc250 100644 --- a/src/ch/ethz/ssh2/packets/Packets.java +++ b/src/ch/ethz/ssh2/packets/Packets.java @@ -1,149 +1,144 @@ - package ch.ethz.ssh2.packets; /** * Packets. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Packets.java,v 1.6 2006/09/20 12:51:37 cplattne Exp $ */ -public class Packets -{ - public static final int SSH_MSG_DISCONNECT = 1; - public static final int SSH_MSG_IGNORE = 2; - public static final int SSH_MSG_UNIMPLEMENTED = 3; - public static final int SSH_MSG_DEBUG = 4; - public static final int SSH_MSG_SERVICE_REQUEST = 5; - public static final int SSH_MSG_SERVICE_ACCEPT = 6; - - public static final int SSH_MSG_KEXINIT = 20; - public static final int SSH_MSG_NEWKEYS = 21; - - public static final int SSH_MSG_KEXDH_INIT = 30; - public static final int SSH_MSG_KEXDH_REPLY = 31; - - public static final int SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; - public static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34; - public static final int SSH_MSG_KEX_DH_GEX_GROUP = 31; - public static final int SSH_MSG_KEX_DH_GEX_INIT = 32; - public static final int SSH_MSG_KEX_DH_GEX_REPLY = 33; - - public static final int SSH_MSG_USERAUTH_REQUEST = 50; - public static final int SSH_MSG_USERAUTH_FAILURE = 51; - public static final int SSH_MSG_USERAUTH_SUCCESS = 52; - public static final int SSH_MSG_USERAUTH_BANNER = 53; - public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60; - public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61; - - public static final int SSH_MSG_GLOBAL_REQUEST = 80; - public static final int SSH_MSG_REQUEST_SUCCESS = 81; - public static final int SSH_MSG_REQUEST_FAILURE = 82; - - public static final int SSH_MSG_CHANNEL_OPEN = 90; - public static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; - public static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92; - public static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; - public static final int SSH_MSG_CHANNEL_DATA = 94; - public static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; - public static final int SSH_MSG_CHANNEL_EOF = 96; - public static final int SSH_MSG_CHANNEL_CLOSE = 97; - public static final int SSH_MSG_CHANNEL_REQUEST = 98; - public static final int SSH_MSG_CHANNEL_SUCCESS = 99; - public static final int SSH_MSG_CHANNEL_FAILURE = 100; - - public static final int SSH_EXTENDED_DATA_STDERR = 1; - - public static final int SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1; - public static final int SSH_DISCONNECT_PROTOCOL_ERROR = 2; - public static final int SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3; - public static final int SSH_DISCONNECT_RESERVED = 4; - public static final int SSH_DISCONNECT_MAC_ERROR = 5; - public static final int SSH_DISCONNECT_COMPRESSION_ERROR = 6; - public static final int SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7; - public static final int SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8; - public static final int SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9; - public static final int SSH_DISCONNECT_CONNECTION_LOST = 10; - public static final int SSH_DISCONNECT_BY_APPLICATION = 11; - public static final int SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12; - public static final int SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13; - public static final int SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14; - public static final int SSH_DISCONNECT_ILLEGAL_USER_NAME = 15; - - public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; - public static final int SSH_OPEN_CONNECT_FAILED = 2; - public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; - public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; - - private static final String[] reverseNames = new String[101]; - - static - { - reverseNames[1] = "SSH_MSG_DISCONNECT"; - reverseNames[2] = "SSH_MSG_IGNORE"; - reverseNames[3] = "SSH_MSG_UNIMPLEMENTED"; - reverseNames[4] = "SSH_MSG_DEBUG"; - reverseNames[5] = "SSH_MSG_SERVICE_REQUEST"; - reverseNames[6] = "SSH_MSG_SERVICE_ACCEPT"; - - reverseNames[20] = "SSH_MSG_KEXINIT"; - reverseNames[21] = "SSH_MSG_NEWKEYS"; - - reverseNames[30] = "SSH_MSG_KEXDH_INIT"; - reverseNames[31] = "SSH_MSG_KEXDH_REPLY/SSH_MSG_KEX_DH_GEX_GROUP"; - reverseNames[32] = "SSH_MSG_KEX_DH_GEX_INIT"; - reverseNames[33] = "SSH_MSG_KEX_DH_GEX_REPLY"; - reverseNames[34] = "SSH_MSG_KEX_DH_GEX_REQUEST"; - - reverseNames[50] = "SSH_MSG_USERAUTH_REQUEST"; - reverseNames[51] = "SSH_MSG_USERAUTH_FAILURE"; - reverseNames[52] = "SSH_MSG_USERAUTH_SUCCESS"; - reverseNames[53] = "SSH_MSG_USERAUTH_BANNER"; - - reverseNames[60] = "SSH_MSG_USERAUTH_INFO_REQUEST"; - reverseNames[61] = "SSH_MSG_USERAUTH_INFO_RESPONSE"; - - reverseNames[80] = "SSH_MSG_GLOBAL_REQUEST"; - reverseNames[81] = "SSH_MSG_REQUEST_SUCCESS"; - reverseNames[82] = "SSH_MSG_REQUEST_FAILURE"; - - reverseNames[90] = "SSH_MSG_CHANNEL_OPEN"; - reverseNames[91] = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; - reverseNames[92] = "SSH_MSG_CHANNEL_OPEN_FAILURE"; - reverseNames[93] = "SSH_MSG_CHANNEL_WINDOW_ADJUST"; - reverseNames[94] = "SSH_MSG_CHANNEL_DATA"; - reverseNames[95] = "SSH_MSG_CHANNEL_EXTENDED_DATA"; - reverseNames[96] = "SSH_MSG_CHANNEL_EOF"; - reverseNames[97] = "SSH_MSG_CHANNEL_CLOSE"; - reverseNames[98] = "SSH_MSG_CHANNEL_REQUEST"; - reverseNames[99] = "SSH_MSG_CHANNEL_SUCCESS"; - reverseNames[100] = "SSH_MSG_CHANNEL_FAILURE"; - } - - public static final String getMessageName(int type) - { - String res = null; - - if ((type >= 0) && (type < reverseNames.length)) - { - res = reverseNames[type]; - } - - return (res == null) ? ("UNKNOWN MSG " + type) : res; - } - - // public static final void debug(String tag, byte[] msg) - // { - // System.err.println(tag + " Type: " + msg[0] + ", LEN: " + msg.length); - // - // for (int i = 0; i < msg.length; i++) - // { - // if (((msg[i] >= 'a') && (msg[i] <= 'z')) || ((msg[i] >= 'A') && (msg[i] <= 'Z')) - // || ((msg[i] >= '0') && (msg[i] <= '9')) || (msg[i] == ' ')) - // System.err.print((char) msg[i]); - // else - // System.err.print("."); - // } - // System.err.println(); - // System.err.flush(); - // } +public class Packets { + public static final int SSH_MSG_DISCONNECT = 1; + public static final int SSH_MSG_IGNORE = 2; + public static final int SSH_MSG_UNIMPLEMENTED = 3; + public static final int SSH_MSG_DEBUG = 4; + public static final int SSH_MSG_SERVICE_REQUEST = 5; + public static final int SSH_MSG_SERVICE_ACCEPT = 6; + + public static final int SSH_MSG_KEXINIT = 20; + public static final int SSH_MSG_NEWKEYS = 21; + + public static final int SSH_MSG_KEXDH_INIT = 30; + public static final int SSH_MSG_KEXDH_REPLY = 31; + + public static final int SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; + public static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34; + public static final int SSH_MSG_KEX_DH_GEX_GROUP = 31; + public static final int SSH_MSG_KEX_DH_GEX_INIT = 32; + public static final int SSH_MSG_KEX_DH_GEX_REPLY = 33; + + public static final int SSH_MSG_USERAUTH_REQUEST = 50; + public static final int SSH_MSG_USERAUTH_FAILURE = 51; + public static final int SSH_MSG_USERAUTH_SUCCESS = 52; + public static final int SSH_MSG_USERAUTH_BANNER = 53; + public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60; + public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61; + + public static final int SSH_MSG_GLOBAL_REQUEST = 80; + public static final int SSH_MSG_REQUEST_SUCCESS = 81; + public static final int SSH_MSG_REQUEST_FAILURE = 82; + + public static final int SSH_MSG_CHANNEL_OPEN = 90; + public static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; + public static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92; + public static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; + public static final int SSH_MSG_CHANNEL_DATA = 94; + public static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; + public static final int SSH_MSG_CHANNEL_EOF = 96; + public static final int SSH_MSG_CHANNEL_CLOSE = 97; + public static final int SSH_MSG_CHANNEL_REQUEST = 98; + public static final int SSH_MSG_CHANNEL_SUCCESS = 99; + public static final int SSH_MSG_CHANNEL_FAILURE = 100; + + public static final int SSH_EXTENDED_DATA_STDERR = 1; + + public static final int SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1; + public static final int SSH_DISCONNECT_PROTOCOL_ERROR = 2; + public static final int SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3; + public static final int SSH_DISCONNECT_RESERVED = 4; + public static final int SSH_DISCONNECT_MAC_ERROR = 5; + public static final int SSH_DISCONNECT_COMPRESSION_ERROR = 6; + public static final int SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7; + public static final int SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8; + public static final int SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9; + public static final int SSH_DISCONNECT_CONNECTION_LOST = 10; + public static final int SSH_DISCONNECT_BY_APPLICATION = 11; + public static final int SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12; + public static final int SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13; + public static final int SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14; + public static final int SSH_DISCONNECT_ILLEGAL_USER_NAME = 15; + + public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; + public static final int SSH_OPEN_CONNECT_FAILED = 2; + public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; + public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; + + private static final String[] reverseNames = new String[101]; + + static { + reverseNames[1] = "SSH_MSG_DISCONNECT"; + reverseNames[2] = "SSH_MSG_IGNORE"; + reverseNames[3] = "SSH_MSG_UNIMPLEMENTED"; + reverseNames[4] = "SSH_MSG_DEBUG"; + reverseNames[5] = "SSH_MSG_SERVICE_REQUEST"; + reverseNames[6] = "SSH_MSG_SERVICE_ACCEPT"; + + reverseNames[20] = "SSH_MSG_KEXINIT"; + reverseNames[21] = "SSH_MSG_NEWKEYS"; + + reverseNames[30] = "SSH_MSG_KEXDH_INIT"; + reverseNames[31] = "SSH_MSG_KEXDH_REPLY/SSH_MSG_KEX_DH_GEX_GROUP"; + reverseNames[32] = "SSH_MSG_KEX_DH_GEX_INIT"; + reverseNames[33] = "SSH_MSG_KEX_DH_GEX_REPLY"; + reverseNames[34] = "SSH_MSG_KEX_DH_GEX_REQUEST"; + + reverseNames[50] = "SSH_MSG_USERAUTH_REQUEST"; + reverseNames[51] = "SSH_MSG_USERAUTH_FAILURE"; + reverseNames[52] = "SSH_MSG_USERAUTH_SUCCESS"; + reverseNames[53] = "SSH_MSG_USERAUTH_BANNER"; + + reverseNames[60] = "SSH_MSG_USERAUTH_INFO_REQUEST"; + reverseNames[61] = "SSH_MSG_USERAUTH_INFO_RESPONSE"; + + reverseNames[80] = "SSH_MSG_GLOBAL_REQUEST"; + reverseNames[81] = "SSH_MSG_REQUEST_SUCCESS"; + reverseNames[82] = "SSH_MSG_REQUEST_FAILURE"; + + reverseNames[90] = "SSH_MSG_CHANNEL_OPEN"; + reverseNames[91] = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; + reverseNames[92] = "SSH_MSG_CHANNEL_OPEN_FAILURE"; + reverseNames[93] = "SSH_MSG_CHANNEL_WINDOW_ADJUST"; + reverseNames[94] = "SSH_MSG_CHANNEL_DATA"; + reverseNames[95] = "SSH_MSG_CHANNEL_EXTENDED_DATA"; + reverseNames[96] = "SSH_MSG_CHANNEL_EOF"; + reverseNames[97] = "SSH_MSG_CHANNEL_CLOSE"; + reverseNames[98] = "SSH_MSG_CHANNEL_REQUEST"; + reverseNames[99] = "SSH_MSG_CHANNEL_SUCCESS"; + reverseNames[100] = "SSH_MSG_CHANNEL_FAILURE"; + } + + public static final String getMessageName(int type) { + String res = null; + + if ((type >= 0) && (type < reverseNames.length)) { + res = reverseNames[type]; + } + + return (res == null) ? ("UNKNOWN MSG " + type) : res; + } + + // public static final void debug(String tag, byte[] msg) + // { + // System.err.println(tag + " Type: " + msg[0] + ", LEN: " + msg.length); + // + // for (int i = 0; i < msg.length; i++) + // { + // if (((msg[i] >= 'a') && (msg[i] <= 'z')) || ((msg[i] >= 'A') && (msg[i] <= 'Z')) + // || ((msg[i] >= '0') && (msg[i] <= '9')) || (msg[i] == ' ')) + // System.err.print((char) msg[i]); + // else + // System.err.print("."); + // } + // System.err.println(); + // System.err.flush(); + // } } diff --git a/src/ch/ethz/ssh2/packets/TypesReader.java b/src/ch/ethz/ssh2/packets/TypesReader.java index dded81a..89e4eba 100644 --- a/src/ch/ethz/ssh2/packets/TypesReader.java +++ b/src/ch/ethz/ssh2/packets/TypesReader.java @@ -1,175 +1,158 @@ - package ch.ethz.ssh2.packets; +import ch.ethz.ssh2.util.Tokenizer; + import java.io.IOException; import java.math.BigInteger; -import ch.ethz.ssh2.util.Tokenizer; - /** * TypesReader. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: TypesReader.java,v 1.6 2006/08/31 20:04:29 cplattne Exp $ */ -public class TypesReader -{ - byte[] arr; - int pos = 0; - int max = 0; - - public TypesReader(byte[] arr) - { - this.arr = arr; - pos = 0; - max = arr.length; - } - - public TypesReader(byte[] arr, int off) - { - this.arr = arr; - this.pos = off; - this.max = arr.length; +public class TypesReader { + byte[] arr; + int pos = 0; + int max = 0; + + public TypesReader(byte[] arr) { + this.arr = arr; + pos = 0; + max = arr.length; + } + + public TypesReader(byte[] arr, int off) { + this.arr = arr; + this.pos = off; + this.max = arr.length; + + if ((pos < 0) || (pos > arr.length)) + throw new IllegalArgumentException("Illegal offset."); + } + + public TypesReader(byte[] arr, int off, int len) { + this.arr = arr; + this.pos = off; + this.max = off + len; + + if ((pos < 0) || (pos > arr.length)) + throw new IllegalArgumentException("Illegal offset."); + + if ((max < 0) || (max > arr.length)) + throw new IllegalArgumentException("Illegal length."); + } - if ((pos < 0) || (pos > arr.length)) - throw new IllegalArgumentException("Illegal offset."); - } - - public TypesReader(byte[] arr, int off, int len) - { - this.arr = arr; - this.pos = off; - this.max = off + len; - - if ((pos < 0) || (pos > arr.length)) - throw new IllegalArgumentException("Illegal offset."); - - if ((max < 0) || (max > arr.length)) - throw new IllegalArgumentException("Illegal length."); - } - - public int readByte() throws IOException - { - if (pos >= max) - throw new IOException("Packet too short."); + public int readByte() throws IOException { + if (pos >= max) + throw new IOException("Packet too short."); - return (arr[pos++] & 0xff); - } + return (arr[pos++] & 0xff); + } - public byte[] readBytes(int len) throws IOException - { - if ((pos + len) > max) - throw new IOException("Packet too short."); + public byte[] readBytes(int len) throws IOException { + if ((pos + len) > max) + throw new IOException("Packet too short."); - byte[] res = new byte[len]; + byte[] res = new byte[len]; - System.arraycopy(arr, pos, res, 0, len); - pos += len; + System.arraycopy(arr, pos, res, 0, len); + pos += len; - return res; - } + return res; + } - public void readBytes(byte[] dst, int off, int len) throws IOException - { - if ((pos + len) > max) - throw new IOException("Packet too short."); + public void readBytes(byte[] dst, int off, int len) throws IOException { + if ((pos + len) > max) + throw new IOException("Packet too short."); - System.arraycopy(arr, pos, dst, off, len); - pos += len; - } + System.arraycopy(arr, pos, dst, off, len); + pos += len; + } - public boolean readBoolean() throws IOException - { - if (pos >= max) - throw new IOException("Packet too short."); + public boolean readBoolean() throws IOException { + if (pos >= max) + throw new IOException("Packet too short."); - return (arr[pos++] != 0); - } + return (arr[pos++] != 0); + } - public int readUINT32() throws IOException - { - if ((pos + 4) > max) - throw new IOException("Packet too short."); + public int readUINT32() throws IOException { + if ((pos + 4) > max) + throw new IOException("Packet too short."); - return ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) - | (arr[pos++] & 0xff); - } + return ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); + } - public long readUINT64() throws IOException - { - if ((pos + 8) > max) - throw new IOException("Packet too short."); + public long readUINT64() throws IOException { + if ((pos + 8) > max) + throw new IOException("Packet too short."); - long high = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) - | (arr[pos++] & 0xff); /* sign extension may take place - will be shifted away =) */ + long high = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - will be shifted away =) */ - long low = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) - | (arr[pos++] & 0xff); /* sign extension may take place - handle below */ + long low = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - handle below */ - return (high << 32) | (low & 0xffffffffl); /* see Java language spec (15.22.1, 5.6.2) */ - } + return (high << 32) | (low & 0xffffffffl); /* see Java language spec (15.22.1, 5.6.2) */ + } - public BigInteger readMPINT() throws IOException - { - BigInteger b; + public BigInteger readMPINT() throws IOException { + BigInteger b; - byte raw[] = readByteString(); + byte raw[] = readByteString(); - if (raw.length == 0) - b = BigInteger.ZERO; - else - b = new BigInteger(raw); + if (raw.length == 0) + b = BigInteger.ZERO; + else + b = new BigInteger(raw); - return b; - } + return b; + } - public byte[] readByteString() throws IOException - { - int len = readUINT32(); + public byte[] readByteString() throws IOException { + int len = readUINT32(); - if ((len + pos) > max) - throw new IOException("Malformed SSH byte string."); + if ((len + pos) > max) + throw new IOException("Malformed SSH byte string."); - byte[] res = new byte[len]; - System.arraycopy(arr, pos, res, 0, len); - pos += len; - return res; - } + byte[] res = new byte[len]; + System.arraycopy(arr, pos, res, 0, len); + pos += len; + return res; + } - public String readString(String charsetName) throws IOException - { - int len = readUINT32(); + public String readString(String charsetName) throws IOException { + int len = readUINT32(); - if ((len + pos) > max) - throw new IOException("Malformed SSH string."); + if ((len + pos) > max) + throw new IOException("Malformed SSH string."); - String res = (charsetName == null) ? new String(arr, pos, len) : new String(arr, pos, len, charsetName); - pos += len; + String res = (charsetName == null) ? new String(arr, pos, len) : new String(arr, pos, len, charsetName); + pos += len; - return res; - } + return res; + } - public String readString() throws IOException - { - int len = readUINT32(); + public String readString() throws IOException { + int len = readUINT32(); - if ((len + pos) > max) - throw new IOException("Malformed SSH string."); + if ((len + pos) > max) + throw new IOException("Malformed SSH string."); - String res = new String(arr, pos, len); - pos += len; + String res = new String(arr, pos, len); + pos += len; - return res; - } + return res; + } - public String[] readNameList() throws IOException - { - return Tokenizer.parseTokens(readString(), ','); - } + public String[] readNameList() throws IOException { + return Tokenizer.parseTokens(readString(), ','); + } - public int remain() - { - return max - pos; - } + public int remain() { + return max - pos; + } } diff --git a/src/ch/ethz/ssh2/packets/TypesWriter.java b/src/ch/ethz/ssh2/packets/TypesWriter.java index 3c3588d..de5f6e2 100644 --- a/src/ch/ethz/ssh2/packets/TypesWriter.java +++ b/src/ch/ethz/ssh2/packets/TypesWriter.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.packets; import java.io.UnsupportedEncodingException; @@ -6,154 +5,134 @@ /** * TypesWriter. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: TypesWriter.java,v 1.6 2006/08/31 20:04:29 cplattne Exp $ */ -public class TypesWriter -{ - byte arr[]; - int pos; - - public TypesWriter() - { - arr = new byte[256]; - pos = 0; - } - - private void resize(int len) - { - byte new_arr[] = new byte[len]; - System.arraycopy(arr, 0, new_arr, 0, arr.length); - arr = new_arr; - } - - public int length() - { - return pos; - } - - public byte[] getBytes() - { - byte[] dst = new byte[pos]; - System.arraycopy(arr, 0, dst, 0, pos); - return dst; - } - - public void getBytes(byte dst[]) - { - System.arraycopy(arr, 0, dst, 0, pos); - } - - public void writeUINT32(int val, int off) - { - if ((off + 4) > arr.length) - resize(off + 32); - - arr[off++] = (byte) (val >> 24); - arr[off++] = (byte) (val >> 16); - arr[off++] = (byte) (val >> 8); - arr[off++] = (byte) val; - } - - public void writeUINT32(int val) - { - writeUINT32(val, pos); - pos += 4; - } - - public void writeUINT64(long val) - { - if ((pos + 8) > arr.length) - resize(arr.length + 32); - - arr[pos++] = (byte) (val >> 56); - arr[pos++] = (byte) (val >> 48); - arr[pos++] = (byte) (val >> 40); - arr[pos++] = (byte) (val >> 32); - arr[pos++] = (byte) (val >> 24); - arr[pos++] = (byte) (val >> 16); - arr[pos++] = (byte) (val >> 8); - arr[pos++] = (byte) val; - } - - public void writeBoolean(boolean v) - { - if ((pos + 1) > arr.length) - resize(arr.length + 32); - - arr[pos++] = v ? (byte) 1 : (byte) 0; - } - - public void writeByte(int v, int off) - { - if ((off + 1) > arr.length) - resize(off + 32); - - arr[off] = (byte) v; - } - - public void writeByte(int v) - { - writeByte(v, pos); - pos++; - } - - public void writeMPInt(BigInteger b) - { - byte raw[] = b.toByteArray(); - - if ((raw.length == 1) && (raw[0] == 0)) - writeUINT32(0); /* String with zero bytes of data */ - else - writeString(raw, 0, raw.length); - } - - public void writeBytes(byte[] buff) - { - writeBytes(buff, 0, buff.length); - } - - public void writeBytes(byte[] buff, int off, int len) - { - if ((pos + len) > arr.length) - resize(arr.length + len + 32); - - System.arraycopy(buff, off, arr, pos, len); - pos += len; - } - - public void writeString(byte[] buff, int off, int len) - { - writeUINT32(len); - writeBytes(buff, off, len); - } - - public void writeString(String v) - { - byte[] b = v.getBytes(); - - writeUINT32(b.length); - writeBytes(b, 0, b.length); - } - - public void writeString(String v, String charsetName) throws UnsupportedEncodingException - { - byte[] b = (charsetName == null) ? v.getBytes() : v.getBytes(charsetName); - - writeUINT32(b.length); - writeBytes(b, 0, b.length); - } - - public void writeNameList(String v[]) - { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < v.length; i++) - { - if (i > 0) - sb.append(','); - sb.append(v[i]); - } - writeString(sb.toString()); - } +public class TypesWriter { + byte arr[]; + int pos; + + public TypesWriter() { + arr = new byte[256]; + pos = 0; + } + + private void resize(int len) { + byte new_arr[] = new byte[len]; + System.arraycopy(arr, 0, new_arr, 0, arr.length); + arr = new_arr; + } + + public int length() { + return pos; + } + + public byte[] getBytes() { + byte[] dst = new byte[pos]; + System.arraycopy(arr, 0, dst, 0, pos); + return dst; + } + + public void getBytes(byte dst[]) { + System.arraycopy(arr, 0, dst, 0, pos); + } + + public void writeUINT32(int val, int off) { + if ((off + 4) > arr.length) + resize(off + 32); + + arr[off++] = (byte) (val >> 24); + arr[off++] = (byte) (val >> 16); + arr[off++] = (byte) (val >> 8); + arr[off++] = (byte) val; + } + + public void writeUINT32(int val) { + writeUINT32(val, pos); + pos += 4; + } + + public void writeUINT64(long val) { + if ((pos + 8) > arr.length) + resize(arr.length + 32); + + arr[pos++] = (byte) (val >> 56); + arr[pos++] = (byte) (val >> 48); + arr[pos++] = (byte) (val >> 40); + arr[pos++] = (byte) (val >> 32); + arr[pos++] = (byte) (val >> 24); + arr[pos++] = (byte) (val >> 16); + arr[pos++] = (byte) (val >> 8); + arr[pos++] = (byte) val; + } + + public void writeBoolean(boolean v) { + if ((pos + 1) > arr.length) + resize(arr.length + 32); + + arr[pos++] = v ? (byte) 1 : (byte) 0; + } + + public void writeByte(int v, int off) { + if ((off + 1) > arr.length) + resize(off + 32); + + arr[off] = (byte) v; + } + + public void writeByte(int v) { + writeByte(v, pos); + pos++; + } + + public void writeMPInt(BigInteger b) { + byte raw[] = b.toByteArray(); + + if ((raw.length == 1) && (raw[0] == 0)) + writeUINT32(0); /* String with zero bytes of data */ + else + writeString(raw, 0, raw.length); + } + + public void writeBytes(byte[] buff) { + writeBytes(buff, 0, buff.length); + } + + public void writeBytes(byte[] buff, int off, int len) { + if ((pos + len) > arr.length) + resize(arr.length + len + 32); + + System.arraycopy(buff, off, arr, pos, len); + pos += len; + } + + public void writeString(byte[] buff, int off, int len) { + writeUINT32(len); + writeBytes(buff, off, len); + } + + public void writeString(String v) { + byte[] b = v.getBytes(); + + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeString(String v, String charsetName) throws UnsupportedEncodingException { + byte[] b = (charsetName == null) ? v.getBytes() : v.getBytes(charsetName); + + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeNameList(String v[]) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < v.length; i++) { + if (i > 0) + sb.append(','); + sb.append(v[i]); + } + writeString(sb.toString()); + } } diff --git a/src/ch/ethz/ssh2/sftp/AttrTextHints.java b/src/ch/ethz/ssh2/sftp/AttrTextHints.java index 9bffa44..1f2c063 100644 --- a/src/ch/ethz/ssh2/sftp/AttrTextHints.java +++ b/src/ch/ethz/ssh2/sftp/AttrTextHints.java @@ -1,38 +1,34 @@ - package ch.ethz.ssh2.sftp; /** - * * Values for the 'text-hint' field in the SFTP ATTRS data type. * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: AttrTextHints.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class AttrTextHints -{ - /** - * The server knows the file is a text file, and should be opened - * using the SSH_FXF_ACCESS_TEXT_MODE flag. - */ - public static final int SSH_FILEXFER_ATTR_KNOWN_TEXT = 0x00; +public class AttrTextHints { + /** + * The server knows the file is a text file, and should be opened + * using the SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_TEXT = 0x00; - /** - * The server has applied a heuristic or other mechanism and - * believes that the file should be opened with the - * SSH_FXF_ACCESS_TEXT_MODE flag. - */ - public static final int SSH_FILEXFER_ATTR_GUESSED_TEXT = 0x01; + /** + * The server has applied a heuristic or other mechanism and + * believes that the file should be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_TEXT = 0x01; - /** - * The server knows the file has binary content. - */ - public static final int SSH_FILEXFER_ATTR_KNOWN_BINARY = 0x02; + /** + * The server knows the file has binary content. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_BINARY = 0x02; - /** - * The server has applied a heuristic or other mechanism and - * believes has binary content, and should not be opened with the - * SSH_FXF_ACCESS_TEXT_MODE flag. - */ - public static final int SSH_FILEXFER_ATTR_GUESSED_BINARY = 0x03; + /** + * The server has applied a heuristic or other mechanism and + * believes has binary content, and should not be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_BINARY = 0x03; } diff --git a/src/ch/ethz/ssh2/sftp/AttribBits.java b/src/ch/ethz/ssh2/sftp/AttribBits.java index 117b85d..850b49a 100644 --- a/src/ch/ethz/ssh2/sftp/AttribBits.java +++ b/src/ch/ethz/ssh2/sftp/AttribBits.java @@ -1,8 +1,6 @@ - package ch.ethz.ssh2.sftp; /** - * * SFTP Attribute Bits for the "attrib-bits" and "attrib-bits-valid" fields * of the SFTP ATTR data type. *

    @@ -17,113 +15,111 @@ * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: AttribBits.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class AttribBits -{ - /** - * Advisory, read-only bit. This bit is not part of the access - * control information on the file, but is rather an advisory field - * indicating that the file should not be written. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001; +public class AttribBits { + /** + * Advisory, read-only bit. This bit is not part of the access + * control information on the file, but is rather an advisory field + * indicating that the file should not be written. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001; - /** - * The file is part of the operating system. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002; + /** + * The file is part of the operating system. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002; - /** - * File SHOULD NOT be shown to user unless specifically requested. - * For example, most UNIX systems SHOULD set this bit if the filename - * begins with a 'period'. This bit may be read-only (see section 5.4 of - * the SFTP standard draft). Most UNIX systems will not allow this to be - * changed. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; + /** + * File SHOULD NOT be shown to user unless specifically requested. + * For example, most UNIX systems SHOULD set this bit if the filename + * begins with a 'period'. This bit may be read-only (see section 5.4 of + * the SFTP standard draft). Most UNIX systems will not allow this to be + * changed. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; - /** - * This attribute applies only to directories. This attribute is - * always read-only, and cannot be modified. This attribute means - * that files and directory names in this directory should be compared - * without regard to case. - *

    - * It is recommended that where possible, the server's filesystem be - * allowed to do comparisons. For example, if a client wished to prompt - * a user before overwriting a file, it should not compare the new name - * with the previously retrieved list of names in the directory. Rather, - * it should first try to create the new file by specifying - * SSH_FXF_CREATE_NEW flag. Then, if this fails and returns - * SSH_FX_FILE_ALREADY_EXISTS, it should prompt the user and then retry - * the create specifying SSH_FXF_CREATE_TRUNCATE. - *

    - * Unless otherwise specified, filenames are assumed to be case sensitive. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008; + /** + * This attribute applies only to directories. This attribute is + * always read-only, and cannot be modified. This attribute means + * that files and directory names in this directory should be compared + * without regard to case. + *

    + * It is recommended that where possible, the server's filesystem be + * allowed to do comparisons. For example, if a client wished to prompt + * a user before overwriting a file, it should not compare the new name + * with the previously retrieved list of names in the directory. Rather, + * it should first try to create the new file by specifying + * SSH_FXF_CREATE_NEW flag. Then, if this fails and returns + * SSH_FX_FILE_ALREADY_EXISTS, it should prompt the user and then retry + * the create specifying SSH_FXF_CREATE_TRUNCATE. + *

    + * Unless otherwise specified, filenames are assumed to be case sensitive. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008; - /** - * The file should be included in backup / archive operations. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010; + /** + * The file should be included in backup / archive operations. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010; - /** - * The file is stored on disk using file-system level transparent - * encryption. This flag does not affect the file data on the wire - * (for either READ or WRITE requests.) - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020; + /** + * The file is stored on disk using file-system level transparent + * encryption. This flag does not affect the file data on the wire + * (for either READ or WRITE requests.) + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020; - /** - * The file is stored on disk using file-system level transparent - * compression. This flag does not affect the file data on the wire. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040; + /** + * The file is stored on disk using file-system level transparent + * compression. This flag does not affect the file data on the wire. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040; - /** - * The file is a sparse file; this means that file blocks that have - * not been explicitly written are not stored on disk. For example, if - * a client writes a buffer at 10 M from the beginning of the file, - * the blocks between the previous EOF marker and the 10 M offset would - * not consume physical disk space. - *

    - * Some servers may store all files as sparse files, in which case - * this bit will be unconditionally set. Other servers may not have - * a mechanism for determining if the file is sparse, and so the file - * MAY be stored sparse even if this flag is not set. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080; + /** + * The file is a sparse file; this means that file blocks that have + * not been explicitly written are not stored on disk. For example, if + * a client writes a buffer at 10 M from the beginning of the file, + * the blocks between the previous EOF marker and the 10 M offset would + * not consume physical disk space. + *

    + * Some servers may store all files as sparse files, in which case + * this bit will be unconditionally set. Other servers may not have + * a mechanism for determining if the file is sparse, and so the file + * MAY be stored sparse even if this flag is not set. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080; - /** - * Opening the file without either the SSH_FXF_ACCESS_APPEND_DATA or - * the SSH_FXF_ACCESS_APPEND_DATA_ATOMIC flag (see section 8.1.1.3 - * of the SFTP standard draft) MUST result in an - * SSH_FX_INVALID_PARAMETER error. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100; + /** + * Opening the file without either the SSH_FXF_ACCESS_APPEND_DATA or + * the SSH_FXF_ACCESS_APPEND_DATA_ATOMIC flag (see section 8.1.1.3 + * of the SFTP standard draft) MUST result in an + * SSH_FX_INVALID_PARAMETER error. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100; - /** - * The file cannot be deleted or renamed, no hard link can be created - * to this file, and no data can be written to the file. - *

    - * This bit implies a stronger level of protection than - * SSH_FILEXFER_ATTR_FLAGS_READONLY, the file permission mask or ACLs. - * Typically even the superuser cannot write to immutable files, and - * only the superuser can set or remove the bit. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200; + /** + * The file cannot be deleted or renamed, no hard link can be created + * to this file, and no data can be written to the file. + *

    + * This bit implies a stronger level of protection than + * SSH_FILEXFER_ATTR_FLAGS_READONLY, the file permission mask or ACLs. + * Typically even the superuser cannot write to immutable files, and + * only the superuser can set or remove the bit. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200; - /** - * When the file is modified, the changes are written synchronously - * to the disk. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400; + /** + * When the file is modified, the changes are written synchronously + * to the disk. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400; - /** - * The server MAY include this bit in a directory listing or realpath - * response. It indicates there was a failure in the translation to UTF-8. - * If this flag is included, the server SHOULD also include the - * UNTRANSLATED_NAME attribute. - */ - public static final int SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR = 0x00000800; + /** + * The server MAY include this bit in a directory listing or realpath + * response. It indicates there was a failure in the translation to UTF-8. + * If this flag is included, the server SHOULD also include the + * UNTRANSLATED_NAME attribute. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR = 0x00000800; } diff --git a/src/ch/ethz/ssh2/sftp/AttribFlags.java b/src/ch/ethz/ssh2/sftp/AttribFlags.java index bd13ff5..60f7710 100644 --- a/src/ch/ethz/ssh2/sftp/AttribFlags.java +++ b/src/ch/ethz/ssh2/sftp/AttribFlags.java @@ -1,112 +1,109 @@ - package ch.ethz.ssh2.sftp; /** - * * Attribute Flags. The 'valid-attribute-flags' field in * the SFTP ATTRS data type specifies which of the fields are actually present. * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: AttribFlags.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class AttribFlags -{ - /** - * Indicates that the 'allocation-size' field is present. - */ - public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; - - /** Protocol version 6: - * 0x00000002 was used in a previous version of this protocol. - * It is now a reserved value and MUST NOT appear in the mask. - * Some future version of this protocol may reuse this value. - */ - public static final int SSH_FILEXFER_ATTR_V3_UIDGID = 0x00000002; - - /** - * Indicates that the 'permissions' field is present. - */ - public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; - - /** - * Indicates that the 'atime' and 'mtime' field are present - * (protocol v3). - */ - public static final int SSH_FILEXFER_ATTR_V3_ACMODTIME = 0x00000008; - - /** - * Indicates that the 'atime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; - - /** - * Indicates that the 'createtime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; - - /** - * Indicates that the 'mtime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; - - /** - * Indicates that the 'acl' field is present. - */ - public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; - - /** - * Indicates that the 'owner' and 'group' fields are present. - */ - public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; - - /** - * Indicates that additionally to the 'atime', 'createtime', - * 'mtime' and 'ctime' fields (if present), there is also - * 'atime-nseconds', 'createtime-nseconds', 'mtime-nseconds' - * and 'ctime-nseconds'. - */ - public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; - - /** - * Indicates that the 'attrib-bits' and 'attrib-bits-valid' - * fields are present. - */ - public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; - - /** - * Indicates that the 'allocation-size' field is present. - */ - public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; - - /** - * Indicates that the 'text-hint' field is present. - */ - public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; - - /** - * Indicates that the 'mime-type' field is present. - */ - public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; - - /** - * Indicates that the 'link-count' field is present. - */ - public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; - - /** - * Indicates that the 'untranslated-name' field is present. - */ - public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; - - /** - * Indicates that the 'ctime' field is present. - */ - public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; - - /** - * Indicates that the 'extended-count' field (and probablby some - * 'extensions') is present. - */ - public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; +public class AttribFlags { + /** + * Indicates that the 'allocation-size' field is present. + */ + public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; + + /** + * Protocol version 6: + * 0x00000002 was used in a previous version of this protocol. + * It is now a reserved value and MUST NOT appear in the mask. + * Some future version of this protocol may reuse this value. + */ + public static final int SSH_FILEXFER_ATTR_V3_UIDGID = 0x00000002; + + /** + * Indicates that the 'permissions' field is present. + */ + public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + + /** + * Indicates that the 'atime' and 'mtime' field are present + * (protocol v3). + */ + public static final int SSH_FILEXFER_ATTR_V3_ACMODTIME = 0x00000008; + + /** + * Indicates that the 'atime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; + + /** + * Indicates that the 'createtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; + + /** + * Indicates that the 'mtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; + + /** + * Indicates that the 'acl' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; + + /** + * Indicates that the 'owner' and 'group' fields are present. + */ + public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; + + /** + * Indicates that additionally to the 'atime', 'createtime', + * 'mtime' and 'ctime' fields (if present), there is also + * 'atime-nseconds', 'createtime-nseconds', 'mtime-nseconds' + * and 'ctime-nseconds'. + */ + public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; + + /** + * Indicates that the 'attrib-bits' and 'attrib-bits-valid' + * fields are present. + */ + public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; + + /** + * Indicates that the 'allocation-size' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; + + /** + * Indicates that the 'text-hint' field is present. + */ + public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; + + /** + * Indicates that the 'mime-type' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; + + /** + * Indicates that the 'link-count' field is present. + */ + public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; + + /** + * Indicates that the 'untranslated-name' field is present. + */ + public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; + + /** + * Indicates that the 'ctime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; + + /** + * Indicates that the 'extended-count' field (and probablby some + * 'extensions') is present. + */ + public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; } diff --git a/src/ch/ethz/ssh2/sftp/AttribPermissions.java b/src/ch/ethz/ssh2/sftp/AttribPermissions.java index 0e4330a..832368b 100644 --- a/src/ch/ethz/ssh2/sftp/AttribPermissions.java +++ b/src/ch/ethz/ssh2/sftp/AttribPermissions.java @@ -1,8 +1,6 @@ - package ch.ethz.ssh2.sftp; /** - * * Permissions for the 'permissions' field in the SFTP ATTRS data type. *

    * "The 'permissions' field contains a bit mask specifying file permissions. @@ -11,22 +9,20 @@ * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: AttribPermissions.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class AttribPermissions -{ - /* Octal values! */ +public class AttribPermissions { + /* Octal values! */ - public static final int S_IRUSR = 0400; - public static final int S_IWUSR = 0200; - public static final int S_IXUSR = 0100; - public static final int S_IRGRP = 0040; - public static final int S_IWGRP = 0020; - public static final int S_IXGRP = 0010; - public static final int S_IROTH = 0004; - public static final int S_IWOTH = 0002; - public static final int S_IXOTH = 0001; - public static final int S_ISUID = 04000; - public static final int S_ISGID = 02000; - public static final int S_ISVTX = 01000; + public static final int S_IRUSR = 0400; + public static final int S_IWUSR = 0200; + public static final int S_IXUSR = 0100; + public static final int S_IRGRP = 0040; + public static final int S_IWGRP = 0020; + public static final int S_IXGRP = 0010; + public static final int S_IROTH = 0004; + public static final int S_IWOTH = 0002; + public static final int S_IXOTH = 0001; + public static final int S_ISUID = 04000; + public static final int S_ISGID = 02000; + public static final int S_ISVTX = 01000; } diff --git a/src/ch/ethz/ssh2/sftp/AttribTypes.java b/src/ch/ethz/ssh2/sftp/AttribTypes.java index 6d5e50f..a16e923 100644 --- a/src/ch/ethz/ssh2/sftp/AttribTypes.java +++ b/src/ch/ethz/ssh2/sftp/AttribTypes.java @@ -1,8 +1,6 @@ - package ch.ethz.ssh2.sftp; /** - * * Types for the 'type' field in the SFTP ATTRS data type. *

    * "On a POSIX system, these values would be derived from the mode field @@ -12,17 +10,15 @@ * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: AttribTypes.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class AttribTypes -{ - public static final int SSH_FILEXFER_TYPE_REGULAR = 1; - public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2; - public static final int SSH_FILEXFER_TYPE_SYMLINK = 3; - public static final int SSH_FILEXFER_TYPE_SPECIAL = 4; - public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5; - public static final int SSH_FILEXFER_TYPE_SOCKET = 6; - public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; - public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; - public static final int SSH_FILEXFER_TYPE_FIFO = 9; +public class AttribTypes { + public static final int SSH_FILEXFER_TYPE_REGULAR = 1; + public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2; + public static final int SSH_FILEXFER_TYPE_SYMLINK = 3; + public static final int SSH_FILEXFER_TYPE_SPECIAL = 4; + public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5; + public static final int SSH_FILEXFER_TYPE_SOCKET = 6; + public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; + public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; + public static final int SSH_FILEXFER_TYPE_FIFO = 9; } diff --git a/src/ch/ethz/ssh2/sftp/ErrorCodes.java b/src/ch/ethz/ssh2/sftp/ErrorCodes.java index 6c0952e..ab3f8ad 100644 --- a/src/ch/ethz/ssh2/sftp/ErrorCodes.java +++ b/src/ch/ethz/ssh2/sftp/ErrorCodes.java @@ -1,94 +1,89 @@ - package ch.ethz.ssh2.sftp; /** - * * SFTP Error Codes * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ErrorCodes.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class ErrorCodes -{ - public static final int SSH_FX_OK = 0; - public static final int SSH_FX_EOF = 1; - public static final int SSH_FX_NO_SUCH_FILE = 2; - public static final int SSH_FX_PERMISSION_DENIED = 3; - public static final int SSH_FX_FAILURE = 4; - public static final int SSH_FX_BAD_MESSAGE = 5; - public static final int SSH_FX_NO_CONNECTION = 6; - public static final int SSH_FX_CONNECTION_LOST = 7; - public static final int SSH_FX_OP_UNSUPPORTED = 8; - public static final int SSH_FX_INVALID_HANDLE = 9; - public static final int SSH_FX_NO_SUCH_PATH = 10; - public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; - public static final int SSH_FX_WRITE_PROTECT = 12; - public static final int SSH_FX_NO_MEDIA = 13; - public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14; - public static final int SSH_FX_QUOTA_EXCEEDED = 15; - public static final int SSH_FX_UNKNOWN_PRINCIPAL = 16; - public static final int SSH_FX_LOCK_CONFLICT = 17; - public static final int SSH_FX_DIR_NOT_EMPTY = 18; - public static final int SSH_FX_NOT_A_DIRECTORY = 19; - public static final int SSH_FX_INVALID_FILENAME = 20; - public static final int SSH_FX_LINK_LOOP = 21; - public static final int SSH_FX_CANNOT_DELETE = 22; - public static final int SSH_FX_INVALID_PARAMETER = 23; - public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24; - public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25; - public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26; - public static final int SSH_FX_DELETE_PENDING = 27; - public static final int SSH_FX_FILE_CORRUPT = 28; +public class ErrorCodes { + public static final int SSH_FX_OK = 0; + public static final int SSH_FX_EOF = 1; + public static final int SSH_FX_NO_SUCH_FILE = 2; + public static final int SSH_FX_PERMISSION_DENIED = 3; + public static final int SSH_FX_FAILURE = 4; + public static final int SSH_FX_BAD_MESSAGE = 5; + public static final int SSH_FX_NO_CONNECTION = 6; + public static final int SSH_FX_CONNECTION_LOST = 7; + public static final int SSH_FX_OP_UNSUPPORTED = 8; + public static final int SSH_FX_INVALID_HANDLE = 9; + public static final int SSH_FX_NO_SUCH_PATH = 10; + public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; + public static final int SSH_FX_WRITE_PROTECT = 12; + public static final int SSH_FX_NO_MEDIA = 13; + public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14; + public static final int SSH_FX_QUOTA_EXCEEDED = 15; + public static final int SSH_FX_UNKNOWN_PRINCIPAL = 16; + public static final int SSH_FX_LOCK_CONFLICT = 17; + public static final int SSH_FX_DIR_NOT_EMPTY = 18; + public static final int SSH_FX_NOT_A_DIRECTORY = 19; + public static final int SSH_FX_INVALID_FILENAME = 20; + public static final int SSH_FX_LINK_LOOP = 21; + public static final int SSH_FX_CANNOT_DELETE = 22; + public static final int SSH_FX_INVALID_PARAMETER = 23; + public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24; + public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25; + public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26; + public static final int SSH_FX_DELETE_PENDING = 27; + public static final int SSH_FX_FILE_CORRUPT = 28; - private static final String[][] messages = { + private static final String[][] messages = { - { "SSH_FX_OK", "Indicates successful completion of the operation." }, - { "SSH_FX_EOF", - "An attempt to read past the end-of-file was made; or, there are no more directory entries to return." }, - { "SSH_FX_NO_SUCH_FILE", "A reference was made to a file which does not exist." }, - { "SSH_FX_PERMISSION_DENIED", "The user does not have sufficient permissions to perform the operation." }, - { "SSH_FX_FAILURE", "An error occured, but no specific error code exists to describe the failure." }, - { "SSH_FX_BAD_MESSAGE", "A badly formatted packet or other SFTP protocol incompatibility was detected." }, - { "SSH_FX_NO_CONNECTION", "There is no connection to the server." }, - { "SSH_FX_CONNECTION_LOST", "The connection to the server was lost." }, - { "SSH_FX_OP_UNSUPPORTED", - "An attempted operation could not be completed by the server because the server does not support the operation." }, - { "SSH_FX_INVALID_HANDLE", "The handle value was invalid." }, - { "SSH_FX_NO_SUCH_PATH", "The file path does not exist or is invalid." }, - { "SSH_FX_FILE_ALREADY_EXISTS", "The file already exists." }, - { "SSH_FX_WRITE_PROTECT", "The file is on read-only media, or the media is write protected." }, - { "SSH_FX_NO_MEDIA", - "The requested operation cannot be completed because there is no media available in the drive." }, - { "SSH_FX_NO_SPACE_ON_FILESYSTEM", - "The requested operation cannot be completed because there is no free space on the filesystem." }, - { "SSH_FX_QUOTA_EXCEEDED", - "The operation cannot be completed because it would exceed the user's storage quota." }, - { - "SSH_FX_UNKNOWN_PRINCIPAL", - "A principal referenced by the request (either the 'owner', 'group', or 'who' field of an ACL), was unknown. The error specific data contains the problematic names." }, - { "SSH_FX_LOCK_CONFLICT", "The file could not be opened because it is locked by another process." }, - { "SSH_FX_DIR_NOT_EMPTY", "The directory is not empty." }, - { "SSH_FX_NOT_A_DIRECTORY", "The specified file is not a directory." }, - { "SSH_FX_INVALID_FILENAME", "The filename is not valid." }, - { "SSH_FX_LINK_LOOP", "Too many symbolic links encountered." }, - { "SSH_FX_CANNOT_DELETE", - "The file cannot be deleted. One possible reason is that the advisory READONLY attribute-bit is set." }, - { "SSH_FX_INVALID_PARAMETER", - "One of the parameters was out of range, or the parameters specified cannot be used together." }, - { "SSH_FX_FILE_IS_A_DIRECTORY", - "The specifed file was a directory in a context where a directory cannot be used." }, - { "SSH_FX_BYTE_RANGE_LOCK_CONFLICT", - " A read or write operation failed because another process's mandatory byte-range lock overlaps with the request." }, - { "SSH_FX_BYTE_RANGE_LOCK_REFUSED", "A request for a byte range lock was refused." }, - { "SSH_FX_DELETE_PENDING", "An operation was attempted on a file for which a delete operation is pending." }, - { "SSH_FX_FILE_CORRUPT", "The file is corrupt; an filesystem integrity check should be run." } }; + {"SSH_FX_OK", "Indicates successful completion of the operation."}, + {"SSH_FX_EOF", + "An attempt to read past the end-of-file was made; or, there are no more directory entries to return."}, + {"SSH_FX_NO_SUCH_FILE", "A reference was made to a file which does not exist."}, + {"SSH_FX_PERMISSION_DENIED", "The user does not have sufficient permissions to perform the operation."}, + {"SSH_FX_FAILURE", "An error occured, but no specific error code exists to describe the failure."}, + {"SSH_FX_BAD_MESSAGE", "A badly formatted packet or other SFTP protocol incompatibility was detected."}, + {"SSH_FX_NO_CONNECTION", "There is no connection to the server."}, + {"SSH_FX_CONNECTION_LOST", "The connection to the server was lost."}, + {"SSH_FX_OP_UNSUPPORTED", + "An attempted operation could not be completed by the server because the server does not support the operation."}, + {"SSH_FX_INVALID_HANDLE", "The handle value was invalid."}, + {"SSH_FX_NO_SUCH_PATH", "The file path does not exist or is invalid."}, + {"SSH_FX_FILE_ALREADY_EXISTS", "The file already exists."}, + {"SSH_FX_WRITE_PROTECT", "The file is on read-only media, or the media is write protected."}, + {"SSH_FX_NO_MEDIA", + "The requested operation cannot be completed because there is no media available in the drive."}, + {"SSH_FX_NO_SPACE_ON_FILESYSTEM", + "The requested operation cannot be completed because there is no free space on the filesystem."}, + {"SSH_FX_QUOTA_EXCEEDED", + "The operation cannot be completed because it would exceed the user's storage quota."}, + { + "SSH_FX_UNKNOWN_PRINCIPAL", + "A principal referenced by the request (either the 'owner', 'group', or 'who' field of an ACL), was unknown. The error specific data contains the problematic names."}, + {"SSH_FX_LOCK_CONFLICT", "The file could not be opened because it is locked by another process."}, + {"SSH_FX_DIR_NOT_EMPTY", "The directory is not empty."}, + {"SSH_FX_NOT_A_DIRECTORY", "The specified file is not a directory."}, + {"SSH_FX_INVALID_FILENAME", "The filename is not valid."}, + {"SSH_FX_LINK_LOOP", "Too many symbolic links encountered."}, + {"SSH_FX_CANNOT_DELETE", + "The file cannot be deleted. One possible reason is that the advisory READONLY attribute-bit is set."}, + {"SSH_FX_INVALID_PARAMETER", + "One of the parameters was out of range, or the parameters specified cannot be used together."}, + {"SSH_FX_FILE_IS_A_DIRECTORY", + "The specifed file was a directory in a context where a directory cannot be used."}, + {"SSH_FX_BYTE_RANGE_LOCK_CONFLICT", + " A read or write operation failed because another process's mandatory byte-range lock overlaps with the request."}, + {"SSH_FX_BYTE_RANGE_LOCK_REFUSED", "A request for a byte range lock was refused."}, + {"SSH_FX_DELETE_PENDING", "An operation was attempted on a file for which a delete operation is pending."}, + {"SSH_FX_FILE_CORRUPT", "The file is corrupt; an filesystem integrity check should be run."}}; - public static final String[] getDescription(int errorCode) - { - if ((errorCode < 0) || (errorCode >= messages.length)) - return null; + public static final String[] getDescription(int errorCode) { + if ((errorCode < 0) || (errorCode >= messages.length)) + return null; - return messages[errorCode]; - } + return messages[errorCode]; + } } diff --git a/src/ch/ethz/ssh2/sftp/OpenFlags.java b/src/ch/ethz/ssh2/sftp/OpenFlags.java index d9db700..b7d0de1 100644 --- a/src/ch/ethz/ssh2/sftp/OpenFlags.java +++ b/src/ch/ethz/ssh2/sftp/OpenFlags.java @@ -1,10 +1,8 @@ - package ch.ethz.ssh2.sftp; /** - * * SFTP Open Flags. - * + *

    * The following table is provided to assist in mapping POSIX semantics * to equivalent SFTP file open parameters: *

    @@ -54,170 +52,169 @@ * * * - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: OpenFlags.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ */ -public class OpenFlags -{ - /** - * Disposition is a 3 bit field that controls how the file is opened. - * The server MUST support these bits (possible enumaration values: - * SSH_FXF_CREATE_NEW, SSH_FXF_CREATE_TRUNCATE, SSH_FXF_OPEN_EXISTING, - * SSH_FXF_OPEN_OR_CREATE or SSH_FXF_TRUNCATE_EXISTING). - */ - public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007; - - /** - * A new file is created; if the file already exists, the server - * MUST return status SSH_FX_FILE_ALREADY_EXISTS. - */ - public static final int SSH_FXF_CREATE_NEW = 0x00000000; - - /** - * A new file is created; if the file already exists, it is opened - * and truncated. - */ - public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001; - - /** - * An existing file is opened. If the file does not exist, the - * server MUST return SSH_FX_NO_SUCH_FILE. If a directory in the - * path does not exist, the server SHOULD return - * SSH_FX_NO_SUCH_PATH. It is also acceptable if the server - * returns SSH_FX_NO_SUCH_FILE in this case. - */ - public static final int SSH_FXF_OPEN_EXISTING = 0x00000002; - - /** - * If the file exists, it is opened. If the file does not exist, - * it is created. - */ - public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003; - - /** - * An existing file is opened and truncated. If the file does not - * exist, the server MUST return the same error codes as defined - * for SSH_FXF_OPEN_EXISTING. - */ - public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004; - - /** - * Data is always written at the end of the file. The offset field - * of the SSH_FXP_WRITE requests are ignored. - *

    - * Data is not required to be appended atomically. This means that - * if multiple writers attempt to append data simultaneously, data - * from the first may be lost. However, data MAY be appended - * atomically. - */ - public static final int SSH_FXF_ACCESS_APPEND_DATA = 0x00000008; - - /** - * Data is always written at the end of the file. The offset field - * of the SSH_FXP_WRITE requests are ignored. - *

    - * Data MUST be written atomically so that there is no chance that - * multiple appenders can collide and result in data being lost. - *

    - * If both append flags are specified, the server SHOULD use atomic - * append if it is available, but SHOULD use non-atomic appends - * otherwise. The server SHOULD NOT fail the request in this case. - */ - public static final int SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010; - - /** - * Indicates that the server should treat the file as text and - * convert it to the canonical newline convention in use. - * (See Determining Server Newline Convention in section 5.3 in the - * SFTP standard draft). - *

    - * When a file is opened with this flag, the offset field in the read - * and write functions is ignored. - *

    - * Servers MUST process multiple, parallel reads and writes correctly - * in this mode. Naturally, it is permissible for them to do this by - * serializing the requests. - *

    - * Clients SHOULD use the SSH_FXF_ACCESS_APPEND_DATA flag to append - * data to a text file rather then using write with a calculated offset. - */ - public static final int SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; - - /** - * The server MUST guarantee that no other handle has been opened - * with ACE4_READ_DATA access, and that no other handle will be - * opened with ACE4_READ_DATA access until the client closes the - * handle. (This MUST apply both to other clients and to other - * processes on the server.) - *

    - * If there is a conflicting lock the server MUST return - * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking - * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. - *

    - * Other handles MAY be opened for ACE4_WRITE_DATA or any other - * combination of accesses, as long as ACE4_READ_DATA is not included - * in the mask. - */ - public static final int SSH_FXF_ACCESS_BLOCK_READ = 0x00000040; - - /** - * The server MUST guarantee that no other handle has been opened - * with ACE4_WRITE_DATA or ACE4_APPEND_DATA access, and that no other - * handle will be opened with ACE4_WRITE_DATA or ACE4_APPEND_DATA - * access until the client closes the handle. (This MUST apply both - * to other clients and to other processes on the server.) - *

    - * If there is a conflicting lock the server MUST return - * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking - * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. - *

    - * Other handles MAY be opened for ACE4_READ_DATA or any other - * combination of accesses, as long as neither ACE4_WRITE_DATA nor - * ACE4_APPEND_DATA are included in the mask. - */ - public static final int SSH_FXF_ACCESS_BLOCK_WRITE = 0x00000080; - - /** - * The server MUST guarantee that no other handle has been opened - * with ACE4_DELETE access, opened with the - * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that no other handle - * will be opened with ACE4_DELETE access or with the - * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that the file itself - * is not deleted in any other way until the client closes the handle. - *

    - * If there is a conflicting lock the server MUST return - * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking - * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. - */ - public static final int SSH_FXF_ACCESS_BLOCK_DELETE = 0x00000100; - - /** - * If this bit is set, the above BLOCK modes are advisory. In advisory - * mode, only other accesses that specify a BLOCK mode need be - * considered when determining whether the BLOCK can be granted, - * and the server need not prevent I/O operations that violate the - * block mode. - *

    - * The server MAY perform mandatory locking even if the BLOCK_ADVISORY - * bit is set. - */ - public static final int SSH_FXF_ACCESS_BLOCK_ADVISORY = 0x00000200; - - /** - * If the final component of the path is a symlink, then the open - * MUST fail, and the error SSH_FX_LINK_LOOP MUST be returned. - */ - public static final int SSH_FXF_ACCESS_NOFOLLOW = 0x00000400; - - /** - * The file should be deleted when the last handle to it is closed. - * (The last handle may not be an sftp-handle.) This MAY be emulated - * by a server if the OS doesn't support it by deleting the file when - * this handle is closed. - *

    - * It is implementation specific whether the directory entry is - * removed immediately or when the handle is closed. - */ - public static final int SSH_FXF_ACCESS_DELETE_ON_CLOSE = 0x00000800; +public class OpenFlags { + /** + * Disposition is a 3 bit field that controls how the file is opened. + * The server MUST support these bits (possible enumaration values: + * SSH_FXF_CREATE_NEW, SSH_FXF_CREATE_TRUNCATE, SSH_FXF_OPEN_EXISTING, + * SSH_FXF_OPEN_OR_CREATE or SSH_FXF_TRUNCATE_EXISTING). + */ + public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007; + + /** + * A new file is created; if the file already exists, the server + * MUST return status SSH_FX_FILE_ALREADY_EXISTS. + */ + public static final int SSH_FXF_CREATE_NEW = 0x00000000; + + /** + * A new file is created; if the file already exists, it is opened + * and truncated. + */ + public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001; + + /** + * An existing file is opened. If the file does not exist, the + * server MUST return SSH_FX_NO_SUCH_FILE. If a directory in the + * path does not exist, the server SHOULD return + * SSH_FX_NO_SUCH_PATH. It is also acceptable if the server + * returns SSH_FX_NO_SUCH_FILE in this case. + */ + public static final int SSH_FXF_OPEN_EXISTING = 0x00000002; + + /** + * If the file exists, it is opened. If the file does not exist, + * it is created. + */ + public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003; + + /** + * An existing file is opened and truncated. If the file does not + * exist, the server MUST return the same error codes as defined + * for SSH_FXF_OPEN_EXISTING. + */ + public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + *

    + * Data is not required to be appended atomically. This means that + * if multiple writers attempt to append data simultaneously, data + * from the first may be lost. However, data MAY be appended + * atomically. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA = 0x00000008; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + *

    + * Data MUST be written atomically so that there is no chance that + * multiple appenders can collide and result in data being lost. + *

    + * If both append flags are specified, the server SHOULD use atomic + * append if it is available, but SHOULD use non-atomic appends + * otherwise. The server SHOULD NOT fail the request in this case. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010; + + /** + * Indicates that the server should treat the file as text and + * convert it to the canonical newline convention in use. + * (See Determining Server Newline Convention in section 5.3 in the + * SFTP standard draft). + *

    + * When a file is opened with this flag, the offset field in the read + * and write functions is ignored. + *

    + * Servers MUST process multiple, parallel reads and writes correctly + * in this mode. Naturally, it is permissible for them to do this by + * serializing the requests. + *

    + * Clients SHOULD use the SSH_FXF_ACCESS_APPEND_DATA flag to append + * data to a text file rather then using write with a calculated offset. + */ + public static final int SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_READ_DATA access, and that no other handle will be + * opened with ACE4_READ_DATA access until the client closes the + * handle. (This MUST apply both to other clients and to other + * processes on the server.) + *

    + * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + *

    + * Other handles MAY be opened for ACE4_WRITE_DATA or any other + * combination of accesses, as long as ACE4_READ_DATA is not included + * in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_READ = 0x00000040; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_WRITE_DATA or ACE4_APPEND_DATA access, and that no other + * handle will be opened with ACE4_WRITE_DATA or ACE4_APPEND_DATA + * access until the client closes the handle. (This MUST apply both + * to other clients and to other processes on the server.) + *

    + * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + *

    + * Other handles MAY be opened for ACE4_READ_DATA or any other + * combination of accesses, as long as neither ACE4_WRITE_DATA nor + * ACE4_APPEND_DATA are included in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_WRITE = 0x00000080; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_DELETE access, opened with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that no other handle + * will be opened with ACE4_DELETE access or with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that the file itself + * is not deleted in any other way until the client closes the handle. + *

    + * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + */ + public static final int SSH_FXF_ACCESS_BLOCK_DELETE = 0x00000100; + + /** + * If this bit is set, the above BLOCK modes are advisory. In advisory + * mode, only other accesses that specify a BLOCK mode need be + * considered when determining whether the BLOCK can be granted, + * and the server need not prevent I/O operations that violate the + * block mode. + *

    + * The server MAY perform mandatory locking even if the BLOCK_ADVISORY + * bit is set. + */ + public static final int SSH_FXF_ACCESS_BLOCK_ADVISORY = 0x00000200; + + /** + * If the final component of the path is a symlink, then the open + * MUST fail, and the error SSH_FX_LINK_LOOP MUST be returned. + */ + public static final int SSH_FXF_ACCESS_NOFOLLOW = 0x00000400; + + /** + * The file should be deleted when the last handle to it is closed. + * (The last handle may not be an sftp-handle.) This MAY be emulated + * by a server if the OS doesn't support it by deleting the file when + * this handle is closed. + *

    + * It is implementation specific whether the directory entry is + * removed immediately or when the handle is closed. + */ + public static final int SSH_FXF_ACCESS_DELETE_ON_CLOSE = 0x00000800; } diff --git a/src/ch/ethz/ssh2/sftp/Packet.java b/src/ch/ethz/ssh2/sftp/Packet.java index a83bb99..e5cae7d 100644 --- a/src/ch/ethz/ssh2/sftp/Packet.java +++ b/src/ch/ethz/ssh2/sftp/Packet.java @@ -1,43 +1,39 @@ - package ch.ethz.ssh2.sftp; /** - * * SFTP Paket Types * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Packet.java,v 1.2 2006/08/02 12:05:00 cplattne Exp $ - * */ -public class Packet -{ - public static final int SSH_FXP_INIT = 1; - public static final int SSH_FXP_VERSION = 2; - public static final int SSH_FXP_OPEN = 3; - public static final int SSH_FXP_CLOSE = 4; - public static final int SSH_FXP_READ = 5; - public static final int SSH_FXP_WRITE = 6; - public static final int SSH_FXP_LSTAT = 7; - public static final int SSH_FXP_FSTAT = 8; - public static final int SSH_FXP_SETSTAT = 9; - public static final int SSH_FXP_FSETSTAT = 10; - public static final int SSH_FXP_OPENDIR = 11; - public static final int SSH_FXP_READDIR = 12; - public static final int SSH_FXP_REMOVE = 13; - public static final int SSH_FXP_MKDIR = 14; - public static final int SSH_FXP_RMDIR = 15; - public static final int SSH_FXP_REALPATH = 16; - public static final int SSH_FXP_STAT = 17; - public static final int SSH_FXP_RENAME = 18; - public static final int SSH_FXP_READLINK = 19; - public static final int SSH_FXP_SYMLINK = 20; - - public static final int SSH_FXP_STATUS = 101; - public static final int SSH_FXP_HANDLE = 102; - public static final int SSH_FXP_DATA = 103; - public static final int SSH_FXP_NAME = 104; - public static final int SSH_FXP_ATTRS = 105; - - public static final int SSH_FXP_EXTENDED = 200; - public static final int SSH_FXP_EXTENDED_REPLY = 201; +public class Packet { + public static final int SSH_FXP_INIT = 1; + public static final int SSH_FXP_VERSION = 2; + public static final int SSH_FXP_OPEN = 3; + public static final int SSH_FXP_CLOSE = 4; + public static final int SSH_FXP_READ = 5; + public static final int SSH_FXP_WRITE = 6; + public static final int SSH_FXP_LSTAT = 7; + public static final int SSH_FXP_FSTAT = 8; + public static final int SSH_FXP_SETSTAT = 9; + public static final int SSH_FXP_FSETSTAT = 10; + public static final int SSH_FXP_OPENDIR = 11; + public static final int SSH_FXP_READDIR = 12; + public static final int SSH_FXP_REMOVE = 13; + public static final int SSH_FXP_MKDIR = 14; + public static final int SSH_FXP_RMDIR = 15; + public static final int SSH_FXP_REALPATH = 16; + public static final int SSH_FXP_STAT = 17; + public static final int SSH_FXP_RENAME = 18; + public static final int SSH_FXP_READLINK = 19; + public static final int SSH_FXP_SYMLINK = 20; + + public static final int SSH_FXP_STATUS = 101; + public static final int SSH_FXP_HANDLE = 102; + public static final int SSH_FXP_DATA = 103; + public static final int SSH_FXP_NAME = 104; + public static final int SSH_FXP_ATTRS = 105; + + public static final int SSH_FXP_EXTENDED = 200; + public static final int SSH_FXP_EXTENDED_REPLY = 201; } diff --git a/src/ch/ethz/ssh2/signature/DSAPrivateKey.java b/src/ch/ethz/ssh2/signature/DSAPrivateKey.java index 296ec2b..bd2e9a0 100644 --- a/src/ch/ethz/ssh2/signature/DSAPrivateKey.java +++ b/src/ch/ethz/ssh2/signature/DSAPrivateKey.java @@ -4,55 +4,47 @@ /** * DSAPrivateKey. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DSAPrivateKey.java,v 1.1 2005/05/26 14:53:30 cplattne Exp $ */ -public class DSAPrivateKey -{ - private BigInteger p; - private BigInteger q; - private BigInteger g; - private BigInteger x; - private BigInteger y; - - public DSAPrivateKey(BigInteger p, BigInteger q, BigInteger g, - BigInteger y, BigInteger x) - { - this.p = p; - this.q = q; - this.g = g; - this.y = y; - this.x = x; - } - - public BigInteger getP() - { - return p; - } - - public BigInteger getQ() - { - return q; - } - - public BigInteger getG() - { - return g; - } - - public BigInteger getY() - { - return y; - } - - public BigInteger getX() - { - return x; - } - - public DSAPublicKey getPublicKey() - { - return new DSAPublicKey(p, q, g, y); - } +public class DSAPrivateKey { + private BigInteger p; + private BigInteger q; + private BigInteger g; + private BigInteger x; + private BigInteger y; + + public DSAPrivateKey(BigInteger p, BigInteger q, BigInteger g, + BigInteger y, BigInteger x) { + this.p = p; + this.q = q; + this.g = g; + this.y = y; + this.x = x; + } + + public BigInteger getP() { + return p; + } + + public BigInteger getQ() { + return q; + } + + public BigInteger getG() { + return g; + } + + public BigInteger getY() { + return y; + } + + public BigInteger getX() { + return x; + } + + public DSAPublicKey getPublicKey() { + return new DSAPublicKey(p, q, g, y); + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/signature/DSAPublicKey.java b/src/ch/ethz/ssh2/signature/DSAPublicKey.java index 45c5858..7b8efa6 100644 --- a/src/ch/ethz/ssh2/signature/DSAPublicKey.java +++ b/src/ch/ethz/ssh2/signature/DSAPublicKey.java @@ -4,42 +4,36 @@ /** * DSAPublicKey. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DSAPublicKey.java,v 1.1 2005/05/26 14:53:30 cplattne Exp $ */ -public class DSAPublicKey -{ - private BigInteger p; - private BigInteger q; - private BigInteger g; - private BigInteger y; +public class DSAPublicKey { + private BigInteger p; + private BigInteger q; + private BigInteger g; + private BigInteger y; - public DSAPublicKey(BigInteger p, BigInteger q, BigInteger g, BigInteger y) - { - this.p = p; - this.q = q; - this.g = g; - this.y = y; - } + public DSAPublicKey(BigInteger p, BigInteger q, BigInteger g, BigInteger y) { + this.p = p; + this.q = q; + this.g = g; + this.y = y; + } - public BigInteger getP() - { - return p; - } + public BigInteger getP() { + return p; + } - public BigInteger getQ() - { - return q; - } + public BigInteger getQ() { + return q; + } - public BigInteger getG() - { - return g; - } + public BigInteger getG() { + return g; + } - public BigInteger getY() - { - return y; - } + public BigInteger getY() { + return y; + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/signature/DSASHA1Verify.java b/src/ch/ethz/ssh2/signature/DSASHA1Verify.java index 05641a9..4c24c6b 100644 --- a/src/ch/ethz/ssh2/signature/DSASHA1Verify.java +++ b/src/ch/ethz/ssh2/signature/DSASHA1Verify.java @@ -1,198 +1,185 @@ - package ch.ethz.ssh2.signature; -import java.io.IOException; -import java.math.BigInteger; -import java.security.SecureRandom; - import ch.ethz.ssh2.crypto.digest.SHA1; import ch.ethz.ssh2.log.Logger; import ch.ethz.ssh2.packets.TypesReader; import ch.ethz.ssh2.packets.TypesWriter; +import java.io.IOException; +import java.math.BigInteger; +import java.security.SecureRandom; + /** * DSASHA1Verify. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DSASHA1Verify.java,v 1.5 2006/02/14 19:43:16 cplattne Exp $ */ -public class DSASHA1Verify -{ - private static final Logger log = Logger.getLogger(DSASHA1Verify.class); +public class DSASHA1Verify { + private static final Logger log = Logger.getLogger(DSASHA1Verify.class); - public static DSAPublicKey decodeSSHDSAPublicKey(byte[] key) throws IOException - { - TypesReader tr = new TypesReader(key); + public static DSAPublicKey decodeSSHDSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); - String key_format = tr.readString(); + String key_format = tr.readString(); - if (key_format.equals("ssh-dss") == false) - throw new IllegalArgumentException("This is not a ssh-dss public key!"); + if (key_format.equals("ssh-dss") == false) + throw new IllegalArgumentException("This is not a ssh-dss public key!"); - BigInteger p = tr.readMPINT(); - BigInteger q = tr.readMPINT(); - BigInteger g = tr.readMPINT(); - BigInteger y = tr.readMPINT(); + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); - if (tr.remain() != 0) - throw new IOException("Padding in DSA public key!"); + if (tr.remain() != 0) + throw new IOException("Padding in DSA public key!"); - return new DSAPublicKey(p, q, g, y); - } + return new DSAPublicKey(p, q, g, y); + } - public static byte[] encodeSSHDSAPublicKey(DSAPublicKey pk) throws IOException - { - TypesWriter tw = new TypesWriter(); + public static byte[] encodeSSHDSAPublicKey(DSAPublicKey pk) throws IOException { + TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-dss"); - tw.writeMPInt(pk.getP()); - tw.writeMPInt(pk.getQ()); - tw.writeMPInt(pk.getG()); - tw.writeMPInt(pk.getY()); + tw.writeString("ssh-dss"); + tw.writeMPInt(pk.getP()); + tw.writeMPInt(pk.getQ()); + tw.writeMPInt(pk.getG()); + tw.writeMPInt(pk.getY()); - return tw.getBytes(); - } + return tw.getBytes(); + } - public static byte[] encodeSSHDSASignature(DSASignature ds) - { - TypesWriter tw = new TypesWriter(); + public static byte[] encodeSSHDSASignature(DSASignature ds) { + TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-dss"); + tw.writeString("ssh-dss"); - byte[] r = ds.getR().toByteArray(); - byte[] s = ds.getS().toByteArray(); + byte[] r = ds.getR().toByteArray(); + byte[] s = ds.getS().toByteArray(); - byte[] a40 = new byte[40]; + byte[] a40 = new byte[40]; /* Patch (unsigned) r and s into the target array. */ - int r_copylen = (r.length < 20) ? r.length : 20; - int s_copylen = (s.length < 20) ? s.length : 20; + int r_copylen = (r.length < 20) ? r.length : 20; + int s_copylen = (s.length < 20) ? s.length : 20; - System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); - System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); + System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); + System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); - tw.writeString(a40, 0, 40); + tw.writeString(a40, 0, 40); - return tw.getBytes(); - } + return tw.getBytes(); + } - public static DSASignature decodeSSHDSASignature(byte[] sig) throws IOException - { - TypesReader tr = new TypesReader(sig); + public static DSASignature decodeSSHDSASignature(byte[] sig) throws IOException { + TypesReader tr = new TypesReader(sig); - String sig_format = tr.readString(); + String sig_format = tr.readString(); - if (sig_format.equals("ssh-dss") == false) - throw new IOException("Peer sent wrong signature format"); + if (sig_format.equals("ssh-dss") == false) + throw new IOException("Peer sent wrong signature format"); - byte[] rsArray = tr.readByteString(); + byte[] rsArray = tr.readByteString(); - if (rsArray.length != 40) - throw new IOException("Peer sent corrupt signature"); + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); - if (tr.remain() != 0) - throw new IOException("Padding in DSA signature!"); + if (tr.remain() != 0) + throw new IOException("Padding in DSA signature!"); /* Remember, s and r are unsigned ints. */ - byte[] tmp = new byte[20]; + byte[] tmp = new byte[20]; - System.arraycopy(rsArray, 0, tmp, 0, 20); - BigInteger r = new BigInteger(1, tmp); + System.arraycopy(rsArray, 0, tmp, 0, 20); + BigInteger r = new BigInteger(1, tmp); - System.arraycopy(rsArray, 20, tmp, 0, 20); - BigInteger s = new BigInteger(1, tmp); + System.arraycopy(rsArray, 20, tmp, 0, 20); + BigInteger s = new BigInteger(1, tmp); - if (log.isEnabled()) - { - log.log(30, "decoded ssh-dss signature: first bytes r(" + ((rsArray[0]) & 0xff) + "), s(" - + ((rsArray[20]) & 0xff) + ")"); - } + if (log.isEnabled()) { + log.log(30, "decoded ssh-dss signature: first bytes r(" + ((rsArray[0]) & 0xff) + "), s(" + + ((rsArray[20]) & 0xff) + ")"); + } - return new DSASignature(r, s); - } + return new DSASignature(r, s); + } - public static boolean verifySignature(byte[] message, DSASignature ds, DSAPublicKey dpk) throws IOException - { - /* Inspired by Bouncycastle's DSASigner class */ + public static boolean verifySignature(byte[] message, DSASignature ds, DSAPublicKey dpk) throws IOException { + /* Inspired by Bouncycastle's DSASigner class */ - SHA1 md = new SHA1(); - md.update(message); - byte[] sha_message = new byte[md.getDigestLength()]; - md.digest(sha_message); + SHA1 md = new SHA1(); + md.update(message); + byte[] sha_message = new byte[md.getDigestLength()]; + md.digest(sha_message); - BigInteger m = new BigInteger(1, sha_message); + BigInteger m = new BigInteger(1, sha_message); - BigInteger r = ds.getR(); - BigInteger s = ds.getS(); + BigInteger r = ds.getR(); + BigInteger s = ds.getS(); - BigInteger g = dpk.getG(); - BigInteger p = dpk.getP(); - BigInteger q = dpk.getQ(); - BigInteger y = dpk.getY(); + BigInteger g = dpk.getG(); + BigInteger p = dpk.getP(); + BigInteger q = dpk.getQ(); + BigInteger y = dpk.getY(); - BigInteger zero = BigInteger.ZERO; + BigInteger zero = BigInteger.ZERO; - if (log.isEnabled()) - { - log.log(60, "ssh-dss signature: m: " + m.toString(16)); - log.log(60, "ssh-dss signature: r: " + r.toString(16)); - log.log(60, "ssh-dss signature: s: " + s.toString(16)); - log.log(60, "ssh-dss signature: g: " + g.toString(16)); - log.log(60, "ssh-dss signature: p: " + p.toString(16)); - log.log(60, "ssh-dss signature: q: " + q.toString(16)); - log.log(60, "ssh-dss signature: y: " + y.toString(16)); - } + if (log.isEnabled()) { + log.log(60, "ssh-dss signature: m: " + m.toString(16)); + log.log(60, "ssh-dss signature: r: " + r.toString(16)); + log.log(60, "ssh-dss signature: s: " + s.toString(16)); + log.log(60, "ssh-dss signature: g: " + g.toString(16)); + log.log(60, "ssh-dss signature: p: " + p.toString(16)); + log.log(60, "ssh-dss signature: q: " + q.toString(16)); + log.log(60, "ssh-dss signature: y: " + y.toString(16)); + } - if (zero.compareTo(r) >= 0 || q.compareTo(r) <= 0) - { - log.log(20, "ssh-dss signature: zero.compareTo(r) >= 0 || q.compareTo(r) <= 0"); - return false; - } + if (zero.compareTo(r) >= 0 || q.compareTo(r) <= 0) { + log.log(20, "ssh-dss signature: zero.compareTo(r) >= 0 || q.compareTo(r) <= 0"); + return false; + } - if (zero.compareTo(s) >= 0 || q.compareTo(s) <= 0) - { - log.log(20, "ssh-dss signature: zero.compareTo(s) >= 0 || q.compareTo(s) <= 0"); - return false; - } + if (zero.compareTo(s) >= 0 || q.compareTo(s) <= 0) { + log.log(20, "ssh-dss signature: zero.compareTo(s) >= 0 || q.compareTo(s) <= 0"); + return false; + } - BigInteger w = s.modInverse(q); + BigInteger w = s.modInverse(q); - BigInteger u1 = m.multiply(w).mod(q); - BigInteger u2 = r.multiply(w).mod(q); + BigInteger u1 = m.multiply(w).mod(q); + BigInteger u2 = r.multiply(w).mod(q); - u1 = g.modPow(u1, p); - u2 = y.modPow(u2, p); + u1 = g.modPow(u1, p); + u2 = y.modPow(u2, p); - BigInteger v = u1.multiply(u2).mod(p).mod(q); + BigInteger v = u1.multiply(u2).mod(p).mod(q); - return v.equals(r); - } + return v.equals(r); + } - public static DSASignature generateSignature(byte[] message, DSAPrivateKey pk, SecureRandom rnd) - { - SHA1 md = new SHA1(); - md.update(message); - byte[] sha_message = new byte[md.getDigestLength()]; - md.digest(sha_message); + public static DSASignature generateSignature(byte[] message, DSAPrivateKey pk, SecureRandom rnd) { + SHA1 md = new SHA1(); + md.update(message); + byte[] sha_message = new byte[md.getDigestLength()]; + md.digest(sha_message); - BigInteger m = new BigInteger(1, sha_message); - BigInteger k; - int qBitLength = pk.getQ().bitLength(); + BigInteger m = new BigInteger(1, sha_message); + BigInteger k; + int qBitLength = pk.getQ().bitLength(); - do - { - k = new BigInteger(qBitLength, rnd); - } - while (k.compareTo(pk.getQ()) >= 0); + do { + k = new BigInteger(qBitLength, rnd); + } + while (k.compareTo(pk.getQ()) >= 0); - BigInteger r = pk.getG().modPow(k, pk.getP()).mod(pk.getQ()); + BigInteger r = pk.getG().modPow(k, pk.getP()).mod(pk.getQ()); - k = k.modInverse(pk.getQ()).multiply(m.add((pk).getX().multiply(r))); + k = k.modInverse(pk.getQ()).multiply(m.add((pk).getX().multiply(r))); - BigInteger s = k.mod(pk.getQ()); + BigInteger s = k.mod(pk.getQ()); - return new DSASignature(r, s); - } + return new DSASignature(r, s); + } } diff --git a/src/ch/ethz/ssh2/signature/DSASignature.java b/src/ch/ethz/ssh2/signature/DSASignature.java index 54a3788..0eceff0 100644 --- a/src/ch/ethz/ssh2/signature/DSASignature.java +++ b/src/ch/ethz/ssh2/signature/DSASignature.java @@ -4,28 +4,24 @@ /** * DSASignature. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: DSASignature.java,v 1.1 2005/05/26 14:53:30 cplattne Exp $ */ -public class DSASignature -{ - private BigInteger r; - private BigInteger s; +public class DSASignature { + private BigInteger r; + private BigInteger s; - public DSASignature(BigInteger r, BigInteger s) - { - this.r = r; - this.s = s; - } + public DSASignature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } - public BigInteger getR() - { - return r; - } + public BigInteger getR() { + return r; + } - public BigInteger getS() - { - return s; - } + public BigInteger getS() { + return s; + } } diff --git a/src/ch/ethz/ssh2/signature/RSAPrivateKey.java b/src/ch/ethz/ssh2/signature/RSAPrivateKey.java index 99b7924..60f18f9 100644 --- a/src/ch/ethz/ssh2/signature/RSAPrivateKey.java +++ b/src/ch/ethz/ssh2/signature/RSAPrivateKey.java @@ -4,40 +4,34 @@ /** * RSAPrivateKey. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RSAPrivateKey.java,v 1.1 2005/08/11 12:47:29 cplattne Exp $ */ -public class RSAPrivateKey -{ - private BigInteger d; - private BigInteger e; - private BigInteger n; +public class RSAPrivateKey { + private BigInteger d; + private BigInteger e; + private BigInteger n; - public RSAPrivateKey(BigInteger d, BigInteger e, BigInteger n) - { - this.d = d; - this.e = e; - this.n = n; - } + public RSAPrivateKey(BigInteger d, BigInteger e, BigInteger n) { + this.d = d; + this.e = e; + this.n = n; + } - public BigInteger getD() - { - return d; - } - - public BigInteger getE() - { - return e; - } + public BigInteger getD() { + return d; + } - public BigInteger getN() - { - return n; - } - - public RSAPublicKey getPublicKey() - { - return new RSAPublicKey(e, n); - } + public BigInteger getE() { + return e; + } + + public BigInteger getN() { + return n; + } + + public RSAPublicKey getPublicKey() { + return new RSAPublicKey(e, n); + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/signature/RSAPublicKey.java b/src/ch/ethz/ssh2/signature/RSAPublicKey.java index bb03bb4..42e1fa8 100644 --- a/src/ch/ethz/ssh2/signature/RSAPublicKey.java +++ b/src/ch/ethz/ssh2/signature/RSAPublicKey.java @@ -4,28 +4,24 @@ /** * RSAPublicKey. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RSAPublicKey.java,v 1.2 2005/08/11 12:47:29 cplattne Exp $ */ -public class RSAPublicKey -{ - BigInteger e; - BigInteger n; +public class RSAPublicKey { + BigInteger e; + BigInteger n; - public RSAPublicKey(BigInteger e, BigInteger n) - { - this.e = e; - this.n = n; - } + public RSAPublicKey(BigInteger e, BigInteger n) { + this.e = e; + this.n = n; + } - public BigInteger getE() - { - return e; - } + public BigInteger getE() { + return e; + } - public BigInteger getN() - { - return n; - } + public BigInteger getN() { + return n; + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/signature/RSASHA1Verify.java b/src/ch/ethz/ssh2/signature/RSASHA1Verify.java index ae52cc1..eb1f019 100644 --- a/src/ch/ethz/ssh2/signature/RSASHA1Verify.java +++ b/src/ch/ethz/ssh2/signature/RSASHA1Verify.java @@ -1,284 +1,257 @@ - package ch.ethz.ssh2.signature; -import java.io.IOException; -import java.math.BigInteger; - import ch.ethz.ssh2.crypto.SimpleDERReader; import ch.ethz.ssh2.crypto.digest.SHA1; import ch.ethz.ssh2.log.Logger; import ch.ethz.ssh2.packets.TypesReader; import ch.ethz.ssh2.packets.TypesWriter; +import java.io.IOException; +import java.math.BigInteger; + /** * RSASHA1Verify. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RSASHA1Verify.java,v 1.4 2005/12/07 10:25:49 cplattne Exp $ */ -public class RSASHA1Verify -{ - private static final Logger log = Logger.getLogger(RSASHA1Verify.class); +public class RSASHA1Verify { + private static final Logger log = Logger.getLogger(RSASHA1Verify.class); - public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException - { - TypesReader tr = new TypesReader(key); + public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); - String key_format = tr.readString(); + String key_format = tr.readString(); - if (key_format.equals("ssh-rsa") == false) - throw new IllegalArgumentException("This is not a ssh-rsa public key"); + if (key_format.equals("ssh-rsa") == false) + throw new IllegalArgumentException("This is not a ssh-rsa public key"); - BigInteger e = tr.readMPINT(); - BigInteger n = tr.readMPINT(); + BigInteger e = tr.readMPINT(); + BigInteger n = tr.readMPINT(); - if (tr.remain() != 0) - throw new IOException("Padding in RSA public key!"); + if (tr.remain() != 0) + throw new IOException("Padding in RSA public key!"); - return new RSAPublicKey(e, n); - } + return new RSAPublicKey(e, n); + } - public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException - { - TypesWriter tw = new TypesWriter(); + public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException { + TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-rsa"); - tw.writeMPInt(pk.getE()); - tw.writeMPInt(pk.getN()); + tw.writeString("ssh-rsa"); + tw.writeMPInt(pk.getE()); + tw.writeMPInt(pk.getN()); - return tw.getBytes(); - } + return tw.getBytes(); + } - public static RSASignature decodeSSHRSASignature(byte[] sig) throws IOException - { - TypesReader tr = new TypesReader(sig); + public static RSASignature decodeSSHRSASignature(byte[] sig) throws IOException { + TypesReader tr = new TypesReader(sig); - String sig_format = tr.readString(); + String sig_format = tr.readString(); - if (sig_format.equals("ssh-rsa") == false) - throw new IOException("Peer sent wrong signature format"); + if (sig_format.equals("ssh-rsa") == false) + throw new IOException("Peer sent wrong signature format"); /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string - * containing s (which is an integer, without lengths or padding, unsigned and in + * containing s (which is an integer, without lengths or padding, unsigned and in * network byte order)." See also below. */ - byte[] s = tr.readByteString(); + byte[] s = tr.readByteString(); - if (s.length == 0) - throw new IOException("Error in RSA signature, S is empty."); + if (s.length == 0) + throw new IOException("Error in RSA signature, S is empty."); - if (log.isEnabled()) - { - log.log(80, "Decoding ssh-rsa signature string (length: " + s.length + ")"); - } + if (log.isEnabled()) { + log.log(80, "Decoding ssh-rsa signature string (length: " + s.length + ")"); + } - if (tr.remain() != 0) - throw new IOException("Padding in RSA signature!"); + if (tr.remain() != 0) + throw new IOException("Padding in RSA signature!"); - return new RSASignature(new BigInteger(1, s)); - } + return new RSASignature(new BigInteger(1, s)); + } - public static byte[] encodeSSHRSASignature(RSASignature sig) throws IOException - { - TypesWriter tw = new TypesWriter(); + public static byte[] encodeSSHRSASignature(RSASignature sig) throws IOException { + TypesWriter tw = new TypesWriter(); - tw.writeString("ssh-rsa"); + tw.writeString("ssh-rsa"); /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string * containing s (which is an integer, without lengths or padding, unsigned and in * network byte order)." */ - byte[] s = sig.getS().toByteArray(); + byte[] s = sig.getS().toByteArray(); /* Remove first zero sign byte, if present */ - if ((s.length > 1) && (s[0] == 0x00)) - tw.writeString(s, 1, s.length - 1); - else - tw.writeString(s, 0, s.length); + if ((s.length > 1) && (s[0] == 0x00)) + tw.writeString(s, 1, s.length - 1); + else + tw.writeString(s, 0, s.length); - return tw.getBytes(); - } + return tw.getBytes(); + } - public static RSASignature generateSignature(byte[] message, RSAPrivateKey pk) throws IOException - { - SHA1 md = new SHA1(); - md.update(message); - byte[] sha_message = new byte[md.getDigestLength()]; - md.digest(sha_message); + public static RSASignature generateSignature(byte[] message, RSAPrivateKey pk) throws IOException { + SHA1 md = new SHA1(); + md.update(message); + byte[] sha_message = new byte[md.getDigestLength()]; + md.digest(sha_message); - byte[] der_header = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, - 0x04, 0x14 }; + byte[] der_header = new byte[]{0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, + 0x04, 0x14}; - int rsa_block_len = (pk.getN().bitLength() + 7) / 8; + int rsa_block_len = (pk.getN().bitLength() + 7) / 8; - int num_pad = rsa_block_len - (2 + der_header.length + sha_message.length) - 1; + int num_pad = rsa_block_len - (2 + der_header.length + sha_message.length) - 1; - if (num_pad < 8) - throw new IOException("Cannot sign with RSA, message too long"); + if (num_pad < 8) + throw new IOException("Cannot sign with RSA, message too long"); - byte[] sig = new byte[der_header.length + sha_message.length + 2 + num_pad]; + byte[] sig = new byte[der_header.length + sha_message.length + 2 + num_pad]; - sig[0] = 0x01; + sig[0] = 0x01; - for (int i = 0; i < num_pad; i++) - { - sig[i + 1] = (byte) 0xff; - } + for (int i = 0; i < num_pad; i++) { + sig[i + 1] = (byte) 0xff; + } - sig[num_pad + 1] = 0x00; + sig[num_pad + 1] = 0x00; - System.arraycopy(der_header, 0, sig, 2 + num_pad, der_header.length); - System.arraycopy(sha_message, 0, sig, 2 + num_pad + der_header.length, sha_message.length); + System.arraycopy(der_header, 0, sig, 2 + num_pad, der_header.length); + System.arraycopy(sha_message, 0, sig, 2 + num_pad + der_header.length, sha_message.length); - BigInteger m = new BigInteger(1, sig); + BigInteger m = new BigInteger(1, sig); - BigInteger s = m.modPow(pk.getD(), pk.getN()); + BigInteger s = m.modPow(pk.getD(), pk.getN()); - return new RSASignature(s); - } + return new RSASignature(s); + } - public static boolean verifySignature(byte[] message, RSASignature ds, RSAPublicKey dpk) throws IOException - { - SHA1 md = new SHA1(); - md.update(message); - byte[] sha_message = new byte[md.getDigestLength()]; - md.digest(sha_message); + public static boolean verifySignature(byte[] message, RSASignature ds, RSAPublicKey dpk) throws IOException { + SHA1 md = new SHA1(); + md.update(message); + byte[] sha_message = new byte[md.getDigestLength()]; + md.digest(sha_message); - BigInteger n = dpk.getN(); - BigInteger e = dpk.getE(); - BigInteger s = ds.getS(); + BigInteger n = dpk.getN(); + BigInteger e = dpk.getE(); + BigInteger s = ds.getS(); - if (n.compareTo(s) <= 0) - { - log.log(20, "ssh-rsa signature: n.compareTo(s) <= 0"); - return false; - } + if (n.compareTo(s) <= 0) { + log.log(20, "ssh-rsa signature: n.compareTo(s) <= 0"); + return false; + } - int rsa_block_len = (n.bitLength() + 7) / 8; + int rsa_block_len = (n.bitLength() + 7) / 8; /* And now the show begins */ - if (rsa_block_len < 1) - { - log.log(20, "ssh-rsa signature: rsa_block_len < 1"); - return false; - } - - byte[] v = s.modPow(e, n).toByteArray(); - - int startpos = 0; - - if ((v.length > 0) && (v[0] == 0x00)) - startpos++; - - if ((v.length - startpos) != (rsa_block_len - 1)) - { - log.log(20, "ssh-rsa signature: (v.length - startpos) != (rsa_block_len - 1)"); - return false; - } - - if (v[startpos] != 0x01) - { - log.log(20, "ssh-rsa signature: v[startpos] != 0x01"); - return false; - } - - int pos = startpos + 1; - - while (true) - { - if (pos >= v.length) - { - log.log(20, "ssh-rsa signature: pos >= v.length"); - return false; - } - if (v[pos] == 0x00) - break; - if (v[pos] != (byte) 0xff) - { - log.log(20, "ssh-rsa signature: v[pos] != (byte) 0xff"); - return false; - } - pos++; - } - - int num_pad = pos - (startpos + 1); - - if (num_pad < 8) - { - log.log(20, "ssh-rsa signature: num_pad < 8"); - return false; - } - - pos++; - - if (pos >= v.length) - { - log.log(20, "ssh-rsa signature: pos >= v.length"); - return false; - } - - SimpleDERReader dr = new SimpleDERReader(v, pos, v.length - pos); - - byte[] seq = dr.readSequenceAsByteArray(); - - if (dr.available() != 0) - { - log.log(20, "ssh-rsa signature: dr.available() != 0"); - return false; - } - - dr.resetInput(seq); + if (rsa_block_len < 1) { + log.log(20, "ssh-rsa signature: rsa_block_len < 1"); + return false; + } + + byte[] v = s.modPow(e, n).toByteArray(); + + int startpos = 0; + + if ((v.length > 0) && (v[0] == 0x00)) + startpos++; + + if ((v.length - startpos) != (rsa_block_len - 1)) { + log.log(20, "ssh-rsa signature: (v.length - startpos) != (rsa_block_len - 1)"); + return false; + } + + if (v[startpos] != 0x01) { + log.log(20, "ssh-rsa signature: v[startpos] != 0x01"); + return false; + } + + int pos = startpos + 1; + + while (true) { + if (pos >= v.length) { + log.log(20, "ssh-rsa signature: pos >= v.length"); + return false; + } + if (v[pos] == 0x00) + break; + if (v[pos] != (byte) 0xff) { + log.log(20, "ssh-rsa signature: v[pos] != (byte) 0xff"); + return false; + } + pos++; + } + + int num_pad = pos - (startpos + 1); + + if (num_pad < 8) { + log.log(20, "ssh-rsa signature: num_pad < 8"); + return false; + } + + pos++; + + if (pos >= v.length) { + log.log(20, "ssh-rsa signature: pos >= v.length"); + return false; + } + + SimpleDERReader dr = new SimpleDERReader(v, pos, v.length - pos); + + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) { + log.log(20, "ssh-rsa signature: dr.available() != 0"); + return false; + } + + dr.resetInput(seq); /* Read digestAlgorithm */ - byte digestAlgorithm[] = dr.readSequenceAsByteArray(); + byte digestAlgorithm[] = dr.readSequenceAsByteArray(); /* Inspired by RFC 3347, however, ignoring the comment regarding old BER based implementations */ - if ((digestAlgorithm.length < 8) || (digestAlgorithm.length > 9)) - { - log.log(20, "ssh-rsa signature: (digestAlgorithm.length < 8) || (digestAlgorithm.length > 9)"); - return false; - } - - byte[] digestAlgorithm_sha1 = new byte[] { 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00 }; - - for (int i = 0; i < digestAlgorithm.length; i++) - { - if (digestAlgorithm[i] != digestAlgorithm_sha1[i]) - { - log.log(20, "ssh-rsa signature: digestAlgorithm[i] != digestAlgorithm_sha1[i]"); - return false; - } - } - - byte[] digest = dr.readOctetString(); - - if (dr.available() != 0) - { - log.log(20, "ssh-rsa signature: dr.available() != 0 (II)"); - return false; - } - - if (digest.length != sha_message.length) - { - log.log(20, "ssh-rsa signature: digest.length != sha_message.length"); - return false; - } - - for (int i = 0; i < sha_message.length; i++) - { - if (sha_message[i] != digest[i]) - { - log.log(20, "ssh-rsa signature: sha_message[i] != digest[i]"); - return false; - } - } - - return true; - } + if ((digestAlgorithm.length < 8) || (digestAlgorithm.length > 9)) { + log.log(20, "ssh-rsa signature: (digestAlgorithm.length < 8) || (digestAlgorithm.length > 9)"); + return false; + } + + byte[] digestAlgorithm_sha1 = new byte[]{0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00}; + + for (int i = 0; i < digestAlgorithm.length; i++) { + if (digestAlgorithm[i] != digestAlgorithm_sha1[i]) { + log.log(20, "ssh-rsa signature: digestAlgorithm[i] != digestAlgorithm_sha1[i]"); + return false; + } + } + + byte[] digest = dr.readOctetString(); + + if (dr.available() != 0) { + log.log(20, "ssh-rsa signature: dr.available() != 0 (II)"); + return false; + } + + if (digest.length != sha_message.length) { + log.log(20, "ssh-rsa signature: digest.length != sha_message.length"); + return false; + } + + for (int i = 0; i < sha_message.length; i++) { + if (sha_message[i] != digest[i]) { + log.log(20, "ssh-rsa signature: sha_message[i] != digest[i]"); + return false; + } + } + + return true; + } } diff --git a/src/ch/ethz/ssh2/signature/RSASignature.java b/src/ch/ethz/ssh2/signature/RSASignature.java index 6df2e17..78187f3 100644 --- a/src/ch/ethz/ssh2/signature/RSASignature.java +++ b/src/ch/ethz/ssh2/signature/RSASignature.java @@ -1,4 +1,3 @@ - package ch.ethz.ssh2.signature; import java.math.BigInteger; @@ -6,22 +5,19 @@ /** * RSASignature. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: RSASignature.java,v 1.1 2005/08/11 12:47:29 cplattne Exp $ */ -public class RSASignature -{ - BigInteger s; +public class RSASignature { + BigInteger s; - public BigInteger getS() - { - return s; - } + public RSASignature(BigInteger s) { + this.s = s; + } - public RSASignature(BigInteger s) - { - this.s = s; - } + public BigInteger getS() { + return s; + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/transport/ClientServerHello.java b/src/ch/ethz/ssh2/transport/ClientServerHello.java index 3ed985c..22e0238 100644 --- a/src/ch/ethz/ssh2/transport/ClientServerHello.java +++ b/src/ch/ethz/ssh2/transport/ClientServerHello.java @@ -1,102 +1,93 @@ - package ch.ethz.ssh2.transport; +import ch.ethz.ssh2.Connection; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import ch.ethz.ssh2.Connection; - /** * ClientServerHello. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: ClientServerHello.java,v 1.8 2006/08/02 11:57:12 cplattne Exp $ */ -public class ClientServerHello -{ - String server_line; - String client_line; - - String server_versioncomment; - - public final static int readLineRN(InputStream is, byte[] buffer) throws IOException - { - int pos = 0; - boolean need10 = false; - int len = 0; - while (true) - { - int c = is.read(); - if (c == -1) - throw new IOException("Premature connection close"); - - buffer[pos++] = (byte) c; - - if (c == 13) - { - need10 = true; - continue; - } - - if (c == 10) - break; - - if (need10 == true) - throw new IOException("Malformed line sent by the server, the line does not end correctly."); - - len++; - if (pos >= buffer.length) - throw new IOException("The server sent a too long line."); - } - - return len; - } - - public ClientServerHello(InputStream bi, OutputStream bo) throws IOException - { - client_line = "SSH-2.0-" + Connection.identification; - - bo.write((client_line + "\r\n").getBytes()); - bo.flush(); - - byte[] serverVersion = new byte[512]; - - for (int i = 0; i < 50; i++) - { - int len = readLineRN(bi, serverVersion); - - server_line = new String(serverVersion, 0, len); - - if (server_line.startsWith("SSH-")) - break; - } - - if (server_line.startsWith("SSH-") == false) - throw new IOException( - "Malformed server identification string. There was no line starting with 'SSH-' amongst the first 50 lines."); - - if (server_line.startsWith("SSH-1.99-")) - server_versioncomment = server_line.substring(9); - else if (server_line.startsWith("SSH-2.0-")) - server_versioncomment = server_line.substring(8); - else - throw new IOException("Server uses incompatible protocol, it is not SSH-2 compatible."); - } - - /** - * @return Returns the client_versioncomment. - */ - public byte[] getClientString() - { - return client_line.getBytes(); - } - - /** - * @return Returns the server_versioncomment. - */ - public byte[] getServerString() - { - return server_line.getBytes(); - } +public class ClientServerHello { + String server_line; + String client_line; + + String server_versioncomment; + + public ClientServerHello(InputStream bi, OutputStream bo) throws IOException { + client_line = "SSH-2.0-" + Connection.identification; + + bo.write((client_line + "\r\n").getBytes()); + bo.flush(); + + byte[] serverVersion = new byte[512]; + + for (int i = 0; i < 50; i++) { + int len = readLineRN(bi, serverVersion); + + server_line = new String(serverVersion, 0, len); + + if (server_line.startsWith("SSH-")) + break; + } + + if (server_line.startsWith("SSH-") == false) + throw new IOException( + "Malformed server identification string. There was no line starting with 'SSH-' amongst the first 50 lines."); + + if (server_line.startsWith("SSH-1.99-")) + server_versioncomment = server_line.substring(9); + else if (server_line.startsWith("SSH-2.0-")) + server_versioncomment = server_line.substring(8); + else + throw new IOException("Server uses incompatible protocol, it is not SSH-2 compatible."); + } + + public final static int readLineRN(InputStream is, byte[] buffer) throws IOException { + int pos = 0; + boolean need10 = false; + int len = 0; + while (true) { + int c = is.read(); + if (c == -1) + throw new IOException("Premature connection close"); + + buffer[pos++] = (byte) c; + + if (c == 13) { + need10 = true; + continue; + } + + if (c == 10) + break; + + if (need10 == true) + throw new IOException("Malformed line sent by the server, the line does not end correctly."); + + len++; + if (pos >= buffer.length) + throw new IOException("The server sent a too long line."); + } + + return len; + } + + /** + * @return Returns the client_versioncomment. + */ + public byte[] getClientString() { + return client_line.getBytes(); + } + + /** + * @return Returns the server_versioncomment. + */ + public byte[] getServerString() { + return server_line.getBytes(); + } } diff --git a/src/ch/ethz/ssh2/transport/KexManager.java b/src/ch/ethz/ssh2/transport/KexManager.java index 9fecee0..e9d1e23 100644 --- a/src/ch/ethz/ssh2/transport/KexManager.java +++ b/src/ch/ethz/ssh2/transport/KexManager.java @@ -1,9 +1,5 @@ - package ch.ethz.ssh2.transport; -import java.io.IOException; -import java.security.SecureRandom; - import ch.ethz.ssh2.ConnectionInfo; import ch.ethz.ssh2.DHGexParameters; import ch.ethz.ssh2.ServerHostKeyVerifier; @@ -15,614 +11,516 @@ import ch.ethz.ssh2.crypto.dh.DhGroupExchange; import ch.ethz.ssh2.crypto.digest.MAC; import ch.ethz.ssh2.log.Logger; -import ch.ethz.ssh2.packets.PacketKexDHInit; -import ch.ethz.ssh2.packets.PacketKexDHReply; -import ch.ethz.ssh2.packets.PacketKexDhGexGroup; -import ch.ethz.ssh2.packets.PacketKexDhGexInit; -import ch.ethz.ssh2.packets.PacketKexDhGexReply; -import ch.ethz.ssh2.packets.PacketKexDhGexRequest; -import ch.ethz.ssh2.packets.PacketKexDhGexRequestOld; -import ch.ethz.ssh2.packets.PacketKexInit; -import ch.ethz.ssh2.packets.PacketNewKeys; -import ch.ethz.ssh2.packets.Packets; -import ch.ethz.ssh2.signature.DSAPublicKey; -import ch.ethz.ssh2.signature.DSASHA1Verify; -import ch.ethz.ssh2.signature.DSASignature; -import ch.ethz.ssh2.signature.RSAPublicKey; -import ch.ethz.ssh2.signature.RSASHA1Verify; -import ch.ethz.ssh2.signature.RSASignature; +import ch.ethz.ssh2.packets.*; +import ch.ethz.ssh2.signature.*; + +import java.io.IOException; +import java.security.SecureRandom; /** * KexManager. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: KexManager.java,v 1.11 2006/09/20 12:51:37 cplattne Exp $ */ -public class KexManager -{ - private static final Logger log = Logger.getLogger(KexManager.class); - - KexState kxs; - int kexCount = 0; - KeyMaterial km; - byte[] sessionId; - ClientServerHello csh; - - final Object accessLock = new Object(); - ConnectionInfo lastConnInfo = null; - - boolean connectionClosed = false; - - boolean ignore_next_kex_packet = false; - - final TransportManager tm; - - CryptoWishList nextKEXcryptoWishList; - DHGexParameters nextKEXdhgexParameters; - - ServerHostKeyVerifier verifier; - final String hostname; - final int port; - final SecureRandom rnd; - - public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port, - ServerHostKeyVerifier keyVerifier, SecureRandom rnd) - { - this.tm = tm; - this.csh = csh; - this.nextKEXcryptoWishList = initialCwl; - this.nextKEXdhgexParameters = new DHGexParameters(); - this.hostname = hostname; - this.port = port; - this.verifier = keyVerifier; - this.rnd = rnd; - } - - public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException - { - synchronized (accessLock) - { - while (true) - { - if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount)) - return lastConnInfo; - - if (connectionClosed) - throw (IOException) new IOException("Key exchange was not finished, connection is closed.") - .initCause(tm.getReasonClosedCause()); - - try - { - accessLock.wait(); - } - catch (InterruptedException e) - { - } - } - } - } - - private String getFirstMatch(String[] client, String[] server) throws NegotiateException - { - if (client == null || server == null) - throw new IllegalArgumentException(); - - if (client.length == 0) - return null; - - for (int i = 0; i < client.length; i++) - { - for (int j = 0; j < server.length; j++) - { - if (client[i].equals(server[j])) - return client[i]; - } - } - throw new NegotiateException(); - } - - private boolean compareFirstOfNameList(String[] a, String[] b) - { - if (a == null || b == null) - throw new IllegalArgumentException(); - - if ((a.length == 0) && (b.length == 0)) - return true; - - if ((a.length == 0) || (b.length == 0)) - return false; - - return (a[0].equals(b[0])); - } - - private boolean isGuessOK(KexParameters cpar, KexParameters spar) - { - if (cpar == null || spar == null) - throw new IllegalArgumentException(); - - if (compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms) == false) - { - return false; - } - - if (compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms) == false) - { - return false; - } +public class KexManager { + private static final Logger log = Logger.getLogger(KexManager.class); + final Object accessLock = new Object(); + final TransportManager tm; + final String hostname; + final int port; + final SecureRandom rnd; + KexState kxs; + int kexCount = 0; + KeyMaterial km; + byte[] sessionId; + ClientServerHello csh; + ConnectionInfo lastConnInfo = null; + boolean connectionClosed = false; + boolean ignore_next_kex_packet = false; + CryptoWishList nextKEXcryptoWishList; + DHGexParameters nextKEXdhgexParameters; + ServerHostKeyVerifier verifier; + + public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port, + ServerHostKeyVerifier keyVerifier, SecureRandom rnd) { + this.tm = tm; + this.csh = csh; + this.nextKEXcryptoWishList = initialCwl; + this.nextKEXdhgexParameters = new DHGexParameters(); + this.hostname = hostname; + this.port = port; + this.verifier = keyVerifier; + this.rnd = rnd; + } + + public static final String[] getDefaultServerHostkeyAlgorithmList() { + return new String[]{"ssh-rsa", "ssh-dss"}; + } + + public static final void checkServerHostkeyAlgorithmsList(String[] algos) { + for (int i = 0; i < algos.length; i++) { + if (("ssh-rsa".equals(algos[i]) == false) && ("ssh-dss".equals(algos[i]) == false)) + throw new IllegalArgumentException("Unknown server host key algorithm '" + algos[i] + "'"); + } + } + + public static final String[] getDefaultKexAlgorithmList() { + return new String[]{"diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", + "diffie-hellman-group1-sha1"}; + } + + public static final void checkKexAlgorithmList(String[] algos) { + for (int i = 0; i < algos.length; i++) { + if ("diffie-hellman-group-exchange-sha1".equals(algos[i])) + continue; + + if ("diffie-hellman-group14-sha1".equals(algos[i])) + continue; + + if ("diffie-hellman-group1-sha1".equals(algos[i])) + continue; + + throw new IllegalArgumentException("Unknown kex algorithm '" + algos[i] + "'"); + } + } + + public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException { + synchronized (accessLock) { + while (true) { + if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount)) + return lastConnInfo; + + if (connectionClosed) + throw (IOException) new IOException("Key exchange was not finished, connection is closed.") + .initCause(tm.getReasonClosedCause()); + + try { + accessLock.wait(); + } catch (InterruptedException e) { + } + } + } + } + + private String getFirstMatch(String[] client, String[] server) throws NegotiateException { + if (client == null || server == null) + throw new IllegalArgumentException(); + + if (client.length == 0) + return null; + + for (int i = 0; i < client.length; i++) { + for (int j = 0; j < server.length; j++) { + if (client[i].equals(server[j])) + return client[i]; + } + } + throw new NegotiateException(); + } + + private boolean compareFirstOfNameList(String[] a, String[] b) { + if (a == null || b == null) + throw new IllegalArgumentException(); + + if ((a.length == 0) && (b.length == 0)) + return true; + + if ((a.length == 0) || (b.length == 0)) + return false; + + return (a[0].equals(b[0])); + } + + private boolean isGuessOK(KexParameters cpar, KexParameters spar) { + if (cpar == null || spar == null) + throw new IllegalArgumentException(); + + if (compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms) == false) { + return false; + } + + if (compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms) == false) { + return false; + } /* - * We do NOT check here if the other algorithms can be agreed on, this + * We do NOT check here if the other algorithms can be agreed on, this * is just a check if kex_algorithms and server_host_key_algorithms were * guessed right! */ - return true; - } - - private NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server) - { - NegotiatedParameters np = new NegotiatedParameters(); - - try - { - np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms); - - log.log(20, "kex_algo=" + np.kex_algo); - - np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms, - server.server_host_key_algorithms); - - log.log(20, "server_host_key_algo=" + np.server_host_key_algo); - - np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server, - server.encryption_algorithms_client_to_server); - np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client, - server.encryption_algorithms_server_to_client); - - log.log(20, "enc_algo_client_to_server=" + np.enc_algo_client_to_server); - log.log(20, "enc_algo_server_to_client=" + np.enc_algo_server_to_client); - - np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server, - server.mac_algorithms_client_to_server); - np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client, - server.mac_algorithms_server_to_client); - - log.log(20, "mac_algo_client_to_server=" + np.mac_algo_client_to_server); - log.log(20, "mac_algo_server_to_client=" + np.mac_algo_server_to_client); - - np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server, - server.compression_algorithms_client_to_server); - np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client, - server.compression_algorithms_server_to_client); - - log.log(20, "comp_algo_client_to_server=" + np.comp_algo_client_to_server); - log.log(20, "comp_algo_server_to_client=" + np.comp_algo_server_to_client); - - } - catch (NegotiateException e) - { - return null; - } - - try - { - np.lang_client_to_server = getFirstMatch(client.languages_client_to_server, - server.languages_client_to_server); - } - catch (NegotiateException e1) - { - np.lang_client_to_server = null; - } - - try - { - np.lang_server_to_client = getFirstMatch(client.languages_server_to_client, - server.languages_server_to_client); - } - catch (NegotiateException e2) - { - np.lang_server_to_client = null; - } - - if (isGuessOK(client, server)) - np.guessOK = true; - - return np; - } - - public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex) throws IOException - { - nextKEXcryptoWishList = cwl; - nextKEXdhgexParameters = dhgex; - - if (kxs == null) - { - kxs = new KexState(); - - kxs.dhgexParameters = nextKEXdhgexParameters; - PacketKexInit kp = new PacketKexInit(nextKEXcryptoWishList, rnd); - kxs.localKEX = kp; - tm.sendKexMessage(kp.getPayload()); - } - } - - private boolean establishKeyMaterial() - { - try - { - int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server); - int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server); - int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server); - - int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client); - int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client); - int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client); - - km = KeyMaterial.create("SHA1", kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len, - enc_sc_key_len, enc_sc_block_len, mac_sc_key_len); - } - catch (IllegalArgumentException e) - { - return false; - } - return true; - } - - private void finishKex() throws IOException - { - if (sessionId == null) - sessionId = kxs.H; - - establishKeyMaterial(); + return true; + } + + private NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server) { + NegotiatedParameters np = new NegotiatedParameters(); + + try { + np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms); + + log.log(20, "kex_algo=" + np.kex_algo); + + np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms, + server.server_host_key_algorithms); + + log.log(20, "server_host_key_algo=" + np.server_host_key_algo); + + np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server, + server.encryption_algorithms_client_to_server); + np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client, + server.encryption_algorithms_server_to_client); + + log.log(20, "enc_algo_client_to_server=" + np.enc_algo_client_to_server); + log.log(20, "enc_algo_server_to_client=" + np.enc_algo_server_to_client); + + np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server, + server.mac_algorithms_client_to_server); + np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client, + server.mac_algorithms_server_to_client); + + log.log(20, "mac_algo_client_to_server=" + np.mac_algo_client_to_server); + log.log(20, "mac_algo_server_to_client=" + np.mac_algo_server_to_client); + + np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server, + server.compression_algorithms_client_to_server); + np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client, + server.compression_algorithms_server_to_client); + + log.log(20, "comp_algo_client_to_server=" + np.comp_algo_client_to_server); + log.log(20, "comp_algo_server_to_client=" + np.comp_algo_server_to_client); + + } catch (NegotiateException e) { + return null; + } + + try { + np.lang_client_to_server = getFirstMatch(client.languages_client_to_server, + server.languages_client_to_server); + } catch (NegotiateException e1) { + np.lang_client_to_server = null; + } + + try { + np.lang_server_to_client = getFirstMatch(client.languages_server_to_client, + server.languages_server_to_client); + } catch (NegotiateException e2) { + np.lang_server_to_client = null; + } + + if (isGuessOK(client, server)) + np.guessOK = true; + + return np; + } + + public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex) throws IOException { + nextKEXcryptoWishList = cwl; + nextKEXdhgexParameters = dhgex; + + if (kxs == null) { + kxs = new KexState(); + + kxs.dhgexParameters = nextKEXdhgexParameters; + PacketKexInit kp = new PacketKexInit(nextKEXcryptoWishList, rnd); + kxs.localKEX = kp; + tm.sendKexMessage(kp.getPayload()); + } + } + + private boolean establishKeyMaterial() { + try { + int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server); + int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server); + int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server); + + int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client); + int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client); + int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client); + + km = KeyMaterial.create("SHA1", kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len, + enc_sc_key_len, enc_sc_block_len, mac_sc_key_len); + } catch (IllegalArgumentException e) { + return false; + } + return true; + } + + private void finishKex() throws IOException { + if (sessionId == null) + sessionId = kxs.H; + + establishKeyMaterial(); /* Tell the other side that we start using the new material */ - PacketNewKeys ign = new PacketNewKeys(); - tm.sendKexMessage(ign.getPayload()); - - BlockCipher cbc; - MAC mac; - - try - { - cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, true, km.enc_key_client_to_server, - km.initial_iv_client_to_server); - - mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); - - } - catch (IllegalArgumentException e1) - { - throw new IOException("Fatal error during MAC startup!"); - } - - tm.changeSendCipher(cbc, mac); - tm.kexFinished(); - } - - public static final String[] getDefaultServerHostkeyAlgorithmList() - { - return new String[] { "ssh-rsa", "ssh-dss" }; - } - - public static final void checkServerHostkeyAlgorithmsList(String[] algos) - { - for (int i = 0; i < algos.length; i++) - { - if (("ssh-rsa".equals(algos[i]) == false) && ("ssh-dss".equals(algos[i]) == false)) - throw new IllegalArgumentException("Unknown server host key algorithm '" + algos[i] + "'"); - } - } - - public static final String[] getDefaultKexAlgorithmList() - { - return new String[] { "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", - "diffie-hellman-group1-sha1" }; - } - - public static final void checkKexAlgorithmList(String[] algos) - { - for (int i = 0; i < algos.length; i++) - { - if ("diffie-hellman-group-exchange-sha1".equals(algos[i])) - continue; - - if ("diffie-hellman-group14-sha1".equals(algos[i])) - continue; - - if ("diffie-hellman-group1-sha1".equals(algos[i])) - continue; - - throw new IllegalArgumentException("Unknown kex algorithm '" + algos[i] + "'"); - } - } - - private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException - { - if (kxs.np.server_host_key_algo.equals("ssh-rsa")) - { - RSASignature rs = RSASHA1Verify.decodeSSHRSASignature(sig); - RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey); - - log.log(50, "Verifying ssh-rsa signature"); - - return RSASHA1Verify.verifySignature(kxs.H, rs, rpk); - } - - if (kxs.np.server_host_key_algo.equals("ssh-dss")) - { - DSASignature ds = DSASHA1Verify.decodeSSHDSASignature(sig); - DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey); - - log.log(50, "Verifying ssh-dss signature"); - - return DSASHA1Verify.verifySignature(kxs.H, ds, dpk); - } - - throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'"); - } - - public synchronized void handleMessage(byte[] msg, int msglen) throws IOException - { - PacketKexInit kip; - - if (msg == null) - { - synchronized (accessLock) - { - connectionClosed = true; - accessLock.notifyAll(); - return; - } - } - - if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) - throw new IOException("Unexpected KEX message (type " + msg[0] + ")"); - - if (ignore_next_kex_packet) - { - ignore_next_kex_packet = false; - return; - } - - if (msg[0] == Packets.SSH_MSG_KEXINIT) - { - if ((kxs != null) && (kxs.state != 0)) - throw new IOException("Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!"); - - if (kxs == null) - { + PacketNewKeys ign = new PacketNewKeys(); + tm.sendKexMessage(ign.getPayload()); + + BlockCipher cbc; + MAC mac; + + try { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, true, km.enc_key_client_to_server, + km.initial_iv_client_to_server); + + mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); + + } catch (IllegalArgumentException e1) { + throw new IOException("Fatal error during MAC startup!"); + } + + tm.changeSendCipher(cbc, mac); + tm.kexFinished(); + } + + private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException { + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + RSASignature rs = RSASHA1Verify.decodeSSHRSASignature(sig); + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey); + + log.log(50, "Verifying ssh-rsa signature"); + + return RSASHA1Verify.verifySignature(kxs.H, rs, rpk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + DSASignature ds = DSASHA1Verify.decodeSSHDSASignature(sig); + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey); + + log.log(50, "Verifying ssh-dss signature"); + + return DSASHA1Verify.verifySignature(kxs.H, ds, dpk); + } + + throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'"); + } + + public synchronized void handleMessage(byte[] msg, int msglen) throws IOException { + PacketKexInit kip; + + if (msg == null) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + return; + } + } + + if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) + throw new IOException("Unexpected KEX message (type " + msg[0] + ")"); + + if (ignore_next_kex_packet) { + ignore_next_kex_packet = false; + return; + } + + if (msg[0] == Packets.SSH_MSG_KEXINIT) { + if ((kxs != null) && (kxs.state != 0)) + throw new IOException("Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!"); + + if (kxs == null) { /* * Ah, OK, peer wants to do KEX. Let's be nice and play * together. */ - kxs = new KexState(); - kxs.dhgexParameters = nextKEXdhgexParameters; - kip = new PacketKexInit(nextKEXcryptoWishList, rnd); - kxs.localKEX = kip; - tm.sendKexMessage(kip.getPayload()); - } + kxs = new KexState(); + kxs.dhgexParameters = nextKEXdhgexParameters; + kip = new PacketKexInit(nextKEXcryptoWishList, rnd); + kxs.localKEX = kip; + tm.sendKexMessage(kip.getPayload()); + } - kip = new PacketKexInit(msg, 0, msglen); - kxs.remoteKEX = kip; + kip = new PacketKexInit(msg, 0, msglen); + kxs.remoteKEX = kip; - kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters()); + kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters()); - if (kxs.np == null) - throw new IOException("Cannot negotiate, proposals do not match."); + if (kxs.np == null) + throw new IOException("Cannot negotiate, proposals do not match."); - if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) - { + if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) { /* * Guess was wrong, we need to ignore the next kex packet. */ - ignore_next_kex_packet = true; - } - - if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1")) - { - if (kxs.dhgexParameters.getMin_group_len() == 0) - { - PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters); - tm.sendKexMessage(dhgexreq.getPayload()); - - } - else - { - PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters); - tm.sendKexMessage(dhgexreq.getPayload()); - } - kxs.state = 1; - return; - } - - if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") - || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")) - { - kxs.dhx = new DhExchange(); - - if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")) - kxs.dhx.init(1, rnd); - else - kxs.dhx.init(14, rnd); - - PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE()); - tm.sendKexMessage(kp.getPayload()); - kxs.state = 1; - return; - } - - throw new IllegalStateException("Unkown KEX method!"); - } - - if (msg[0] == Packets.SSH_MSG_NEWKEYS) - { - if (km == null) - throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); - - BlockCipher cbc; - MAC mac; - - try - { - cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false, - km.enc_key_server_to_client, km.initial_iv_server_to_client); - - mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); - - } - catch (IllegalArgumentException e1) - { - throw new IOException("Fatal error during MAC startup!"); - } - - tm.changeRecvCipher(cbc, mac); - - ConnectionInfo sci = new ConnectionInfo(); - - kexCount++; - - sci.keyExchangeAlgorithm = kxs.np.kex_algo; - sci.keyExchangeCounter = kexCount; - sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; - sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; - sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; - sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; - sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; - sci.serverHostKey = kxs.hostkey; - - synchronized (accessLock) - { - lastConnInfo = sci; - accessLock.notifyAll(); - } - - kxs = null; - return; - } - - if ((kxs == null) || (kxs.state == 0)) - throw new IOException("Unexpected Kex submessage!"); - - if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1")) - { - if (kxs.state == 1) - { - PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg, 0, msglen); - kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG()); - kxs.dhgx.init(rnd); - PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE()); - tm.sendKexMessage(dhgexinit.getPayload()); - kxs.state = 2; - return; - } - - if (kxs.state == 2) - { - PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg, 0, msglen); - - kxs.hostkey = dhgexrpl.getHostKey(); - - if (verifier != null) - { - boolean vres = false; - - try - { - vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); - } - catch (Exception e) - { - throw (IOException) new IOException( - "The server hostkey was not accepted by the verifier callback.").initCause(e); - } - - if (vres == false) - throw new IOException("The server hostkey was not accepted by the verifier callback"); - } - - kxs.dhgx.setF(dhgexrpl.getF()); - - try - { - kxs.H = kxs.dhgx.calculateH(csh.getClientString(), csh.getServerString(), - kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), dhgexrpl.getHostKey(), - kxs.dhgexParameters); - } - catch (IllegalArgumentException e) - { - throw (IOException) new IOException("KEX error.").initCause(e); - } - - boolean res = verifySignature(dhgexrpl.getSignature(), kxs.hostkey); - - if (res == false) - throw new IOException("Hostkey signature sent by remote is wrong!"); - - kxs.K = kxs.dhgx.getK(); - - finishKex(); - kxs.state = -1; - return; - } - - throw new IllegalStateException("Illegal State in KEX Exchange!"); - } - - if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") - || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")) - { - if (kxs.state == 1) - { - - PacketKexDHReply dhr = new PacketKexDHReply(msg, 0, msglen); - - kxs.hostkey = dhr.getHostKey(); - - if (verifier != null) - { - boolean vres = false; - - try - { - vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); - } - catch (Exception e) - { - throw (IOException) new IOException( - "The server hostkey was not accepted by the verifier callback.").initCause(e); - } - - if (vres == false) - throw new IOException("The server hostkey was not accepted by the verifier callback"); - } - - kxs.dhx.setF(dhr.getF()); - - try - { - kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(), - kxs.remoteKEX.getPayload(), dhr.getHostKey()); - } - catch (IllegalArgumentException e) - { - throw (IOException) new IOException("KEX error.").initCause(e); - } - - boolean res = verifySignature(dhr.getSignature(), kxs.hostkey); - - if (res == false) - throw new IOException("Hostkey signature sent by remote is wrong!"); - - kxs.K = kxs.dhx.getK(); - - finishKex(); - kxs.state = -1; - return; - } - } - - throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")"); - } + ignore_next_kex_packet = true; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1")) { + if (kxs.dhgexParameters.getMin_group_len() == 0) { + PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + + } else { + PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + kxs.state = 1; + return; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") + || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")) { + kxs.dhx = new DhExchange(); + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")) + kxs.dhx.init(1, rnd); + else + kxs.dhx.init(14, rnd); + + PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE()); + tm.sendKexMessage(kp.getPayload()); + kxs.state = 1; + return; + } + + throw new IllegalStateException("Unkown KEX method!"); + } + + if (msg[0] == Packets.SSH_MSG_NEWKEYS) { + if (km == null) + throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); + + BlockCipher cbc; + MAC mac; + + try { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false, + km.enc_key_server_to_client, km.initial_iv_server_to_client); + + mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); + + } catch (IllegalArgumentException e1) { + throw new IOException("Fatal error during MAC startup!"); + } + + tm.changeRecvCipher(cbc, mac); + + ConnectionInfo sci = new ConnectionInfo(); + + kexCount++; + + sci.keyExchangeAlgorithm = kxs.np.kex_algo; + sci.keyExchangeCounter = kexCount; + sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; + sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; + sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; + sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; + sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; + sci.serverHostKey = kxs.hostkey; + + synchronized (accessLock) { + lastConnInfo = sci; + accessLock.notifyAll(); + } + + kxs = null; + return; + } + + if ((kxs == null) || (kxs.state == 0)) + throw new IOException("Unexpected Kex submessage!"); + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1")) { + if (kxs.state == 1) { + PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg, 0, msglen); + kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG()); + kxs.dhgx.init(rnd); + PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE()); + tm.sendKexMessage(dhgexinit.getPayload()); + kxs.state = 2; + return; + } + + if (kxs.state == 2) { + PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg, 0, msglen); + + kxs.hostkey = dhgexrpl.getHostKey(); + + if (verifier != null) { + boolean vres = false; + + try { + vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); + } catch (Exception e) { + throw (IOException) new IOException( + "The server hostkey was not accepted by the verifier callback.").initCause(e); + } + + if (vres == false) + throw new IOException("The server hostkey was not accepted by the verifier callback"); + } + + kxs.dhgx.setF(dhgexrpl.getF()); + + try { + kxs.H = kxs.dhgx.calculateH(csh.getClientString(), csh.getServerString(), + kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), dhgexrpl.getHostKey(), + kxs.dhgexParameters); + } catch (IllegalArgumentException e) { + throw (IOException) new IOException("KEX error.").initCause(e); + } + + boolean res = verifySignature(dhgexrpl.getSignature(), kxs.hostkey); + + if (res == false) + throw new IOException("Hostkey signature sent by remote is wrong!"); + + kxs.K = kxs.dhgx.getK(); + + finishKex(); + kxs.state = -1; + return; + } + + throw new IllegalStateException("Illegal State in KEX Exchange!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") + || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")) { + if (kxs.state == 1) { + + PacketKexDHReply dhr = new PacketKexDHReply(msg, 0, msglen); + + kxs.hostkey = dhr.getHostKey(); + + if (verifier != null) { + boolean vres = false; + + try { + vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey); + } catch (Exception e) { + throw (IOException) new IOException( + "The server hostkey was not accepted by the verifier callback.").initCause(e); + } + + if (vres == false) + throw new IOException("The server hostkey was not accepted by the verifier callback"); + } + + kxs.dhx.setF(dhr.getF()); + + try { + kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(), + kxs.remoteKEX.getPayload(), dhr.getHostKey()); + } catch (IllegalArgumentException e) { + throw (IOException) new IOException("KEX error.").initCause(e); + } + + boolean res = verifySignature(dhr.getSignature(), kxs.hostkey); + + if (res == false) + throw new IOException("Hostkey signature sent by remote is wrong!"); + + kxs.K = kxs.dhx.getK(); + + finishKex(); + kxs.state = -1; + return; + } + } + + throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")"); + } } diff --git a/src/ch/ethz/ssh2/transport/KexParameters.java b/src/ch/ethz/ssh2/transport/KexParameters.java index 4485de9..c8554b6 100644 --- a/src/ch/ethz/ssh2/transport/KexParameters.java +++ b/src/ch/ethz/ssh2/transport/KexParameters.java @@ -2,23 +2,22 @@ /** * KexParameters. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: KexParameters.java,v 1.1 2005/05/26 14:53:28 cplattne Exp $ */ -public class KexParameters -{ - public byte[] cookie; - public String[] kex_algorithms; - public String[] server_host_key_algorithms; - public String[] encryption_algorithms_client_to_server; - public String[] encryption_algorithms_server_to_client; - public String[] mac_algorithms_client_to_server; - public String[] mac_algorithms_server_to_client; - public String[] compression_algorithms_client_to_server; - public String[] compression_algorithms_server_to_client; - public String[] languages_client_to_server; - public String[] languages_server_to_client; - public boolean first_kex_packet_follows; - public int reserved_field1; +public class KexParameters { + public byte[] cookie; + public String[] kex_algorithms; + public String[] server_host_key_algorithms; + public String[] encryption_algorithms_client_to_server; + public String[] encryption_algorithms_server_to_client; + public String[] mac_algorithms_client_to_server; + public String[] mac_algorithms_server_to_client; + public String[] compression_algorithms_client_to_server; + public String[] compression_algorithms_server_to_client; + public String[] languages_client_to_server; + public String[] languages_server_to_client; + public boolean first_kex_packet_follows; + public int reserved_field1; } diff --git a/src/ch/ethz/ssh2/transport/KexState.java b/src/ch/ethz/ssh2/transport/KexState.java index a694a49..0ff28f5 100644 --- a/src/ch/ethz/ssh2/transport/KexState.java +++ b/src/ch/ethz/ssh2/transport/KexState.java @@ -1,32 +1,31 @@ package ch.ethz.ssh2.transport; -import java.math.BigInteger; - import ch.ethz.ssh2.DHGexParameters; import ch.ethz.ssh2.crypto.dh.DhExchange; import ch.ethz.ssh2.crypto.dh.DhGroupExchange; import ch.ethz.ssh2.packets.PacketKexInit; +import java.math.BigInteger; + /** * KexState. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: KexState.java,v 1.3 2005/06/06 12:44:23 cplattne Exp $ */ -public class KexState -{ - public PacketKexInit localKEX; - public PacketKexInit remoteKEX; - public NegotiatedParameters np; - public int state = 0; +public class KexState { + public PacketKexInit localKEX; + public PacketKexInit remoteKEX; + public NegotiatedParameters np; + public int state = 0; + + public BigInteger K; + public byte[] H; + + public byte[] hostkey; - public BigInteger K; - public byte[] H; - - public byte[] hostkey; - - public DhExchange dhx; - public DhGroupExchange dhgx; - public DHGexParameters dhgexParameters; + public DhExchange dhx; + public DhGroupExchange dhgx; + public DHGexParameters dhgexParameters; } diff --git a/src/ch/ethz/ssh2/transport/MessageHandler.java b/src/ch/ethz/ssh2/transport/MessageHandler.java index 91fe36e..064cf19 100644 --- a/src/ch/ethz/ssh2/transport/MessageHandler.java +++ b/src/ch/ethz/ssh2/transport/MessageHandler.java @@ -4,11 +4,10 @@ /** * MessageHandler. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: MessageHandler.java,v 1.1 2005/05/26 14:53:29 cplattne Exp $ */ -public interface MessageHandler -{ - public void handleMessage(byte[] msg, int msglen) throws IOException; +public interface MessageHandler { + public void handleMessage(byte[] msg, int msglen) throws IOException; } diff --git a/src/ch/ethz/ssh2/transport/NegotiateException.java b/src/ch/ethz/ssh2/transport/NegotiateException.java index 58f3728..351cfaf 100644 --- a/src/ch/ethz/ssh2/transport/NegotiateException.java +++ b/src/ch/ethz/ssh2/transport/NegotiateException.java @@ -2,11 +2,10 @@ /** * NegotiateException. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: NegotiateException.java,v 1.1 2005/05/26 14:53:29 cplattne Exp $ */ -public class NegotiateException extends Exception -{ - private static final long serialVersionUID = 3689910669428143157L; +public class NegotiateException extends Exception { + private static final long serialVersionUID = 3689910669428143157L; } diff --git a/src/ch/ethz/ssh2/transport/NegotiatedParameters.java b/src/ch/ethz/ssh2/transport/NegotiatedParameters.java index 1cd5b62..da4cfd0 100644 --- a/src/ch/ethz/ssh2/transport/NegotiatedParameters.java +++ b/src/ch/ethz/ssh2/transport/NegotiatedParameters.java @@ -2,21 +2,20 @@ /** * NegotiatedParameters. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: NegotiatedParameters.java,v 1.1 2005/05/26 14:53:28 cplattne Exp $ */ -public class NegotiatedParameters -{ - public boolean guessOK; - public String kex_algo; - public String server_host_key_algo; - public String enc_algo_client_to_server; - public String enc_algo_server_to_client; - public String mac_algo_client_to_server; - public String mac_algo_server_to_client; - public String comp_algo_client_to_server; - public String comp_algo_server_to_client; - public String lang_client_to_server; - public String lang_server_to_client; +public class NegotiatedParameters { + public boolean guessOK; + public String kex_algo; + public String server_host_key_algo; + public String enc_algo_client_to_server; + public String enc_algo_server_to_client; + public String mac_algo_client_to_server; + public String mac_algo_server_to_client; + public String comp_algo_client_to_server; + public String comp_algo_server_to_client; + public String lang_client_to_server; + public String lang_server_to_client; } diff --git a/src/ch/ethz/ssh2/transport/TransportConnection.java b/src/ch/ethz/ssh2/transport/TransportConnection.java index 0f89d99..910d0de 100644 --- a/src/ch/ethz/ssh2/transport/TransportConnection.java +++ b/src/ch/ethz/ssh2/transport/TransportConnection.java @@ -1,11 +1,5 @@ - package ch.ethz.ssh2.transport; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.SecureRandom; - import ch.ethz.ssh2.crypto.cipher.BlockCipher; import ch.ethz.ssh2.crypto.cipher.CipherInputStream; import ch.ethz.ssh2.crypto.cipher.CipherOutputStream; @@ -14,134 +8,108 @@ import ch.ethz.ssh2.log.Logger; import ch.ethz.ssh2.packets.Packets; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + /** * TransportConnection. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: TransportConnection.java,v 1.8 2006/02/14 19:43:15 cplattne Exp $ */ -public class TransportConnection -{ - private static final Logger log = Logger.getLogger(TransportConnection.class); - - int send_seq_number = 0; - - int recv_seq_number = 0; - - CipherInputStream cis; - - CipherOutputStream cos; - - boolean useRandomPadding = false; +public class TransportConnection { + private static final Logger log = Logger.getLogger(TransportConnection.class); + final byte[] send_padding_buffer = new byte[256]; + final byte[] send_packet_header_buffer = new byte[5]; + final byte[] recv_padding_buffer = new byte[256]; + final byte[] recv_packet_header_buffer = new byte[5]; + final SecureRandom rnd; /* Depends on current MAC and CIPHER */ - - MAC send_mac; - - byte[] send_mac_buffer; - - int send_padd_blocksize = 8; - - MAC recv_mac; - - byte[] recv_mac_buffer; - - byte[] recv_mac_buffer_cmp; - - int recv_padd_blocksize = 8; + int send_seq_number = 0; + int recv_seq_number = 0; + CipherInputStream cis; + CipherOutputStream cos; + boolean useRandomPadding = false; + MAC send_mac; + byte[] send_mac_buffer; /* won't change */ + int send_padd_blocksize = 8; + MAC recv_mac; + byte[] recv_mac_buffer; + byte[] recv_mac_buffer_cmp; + int recv_padd_blocksize = 8; + boolean recv_packet_header_present = false; + ClientServerHello csh; + + public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) { + this.cis = new CipherInputStream(new NullCipher(), is); + this.cos = new CipherOutputStream(new NullCipher(), os); + this.rnd = rnd; + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) { + cis.changeCipher(bc); + recv_mac = mac; + recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; + recv_padd_blocksize = bc.getBlockSize(); + if (recv_padd_blocksize < 8) + recv_padd_blocksize = 8; + } + + public void changeSendCipher(BlockCipher bc, MAC mac) { + if ((bc instanceof NullCipher) == false) { + /* Only use zero byte padding for the first few packets */ + useRandomPadding = true; + /* Once we start encrypting, there is no way back */ + } - final byte[] send_padding_buffer = new byte[256]; + cos.changeCipher(bc); + send_mac = mac; + send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + send_padd_blocksize = bc.getBlockSize(); + if (send_padd_blocksize < 8) + send_padd_blocksize = 8; + } - final byte[] send_packet_header_buffer = new byte[5]; + public void sendMessage(byte[] message) throws IOException { + sendMessage(message, 0, message.length, 0); + } - final byte[] recv_padding_buffer = new byte[256]; + public void sendMessage(byte[] message, int off, int len) throws IOException { + sendMessage(message, off, len, 0); + } - final byte[] recv_packet_header_buffer = new byte[5]; + public int getPacketOverheadEstimate() { + // return an estimate for the paket overhead (for send operations) + return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; + } - boolean recv_packet_header_present = false; + public void sendMessage(byte[] message, int off, int len, int padd) throws IOException { + if (padd < 4) + padd = 4; + else if (padd > 64) + padd = 64; - ClientServerHello csh; + int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ - final SecureRandom rnd; + int slack = packet_len % send_padd_blocksize; - public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) - { - this.cis = new CipherInputStream(new NullCipher(), is); - this.cos = new CipherOutputStream(new NullCipher(), os); - this.rnd = rnd; - } + if (slack != 0) { + packet_len += (send_padd_blocksize - slack); + } - public void changeRecvCipher(BlockCipher bc, MAC mac) - { - cis.changeCipher(bc); - recv_mac = mac; - recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; - recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; - recv_padd_blocksize = bc.getBlockSize(); - if (recv_padd_blocksize < 8) - recv_padd_blocksize = 8; - } + if (packet_len < 16) + packet_len = 16; - public void changeSendCipher(BlockCipher bc, MAC mac) - { - if ((bc instanceof NullCipher) == false) - { - /* Only use zero byte padding for the first few packets */ - useRandomPadding = true; - /* Once we start encrypting, there is no way back */ - } - - cos.changeCipher(bc); - send_mac = mac; - send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; - send_padd_blocksize = bc.getBlockSize(); - if (send_padd_blocksize < 8) - send_padd_blocksize = 8; - } - - public void sendMessage(byte[] message) throws IOException - { - sendMessage(message, 0, message.length, 0); - } - - public void sendMessage(byte[] message, int off, int len) throws IOException - { - sendMessage(message, off, len, 0); - } - - public int getPacketOverheadEstimate() - { - // return an estimate for the paket overhead (for send operations) - return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; - } - - public void sendMessage(byte[] message, int off, int len, int padd) throws IOException - { - if (padd < 4) - padd = 4; - else if (padd > 64) - padd = 64; - - int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ - - int slack = packet_len % send_padd_blocksize; - - if (slack != 0) - { - packet_len += (send_padd_blocksize - slack); - } - - if (packet_len < 16) - packet_len = 16; - - int padd_len = packet_len - (5 + len); - - if (useRandomPadding) - { - for (int i = 0; i < padd_len; i = i + 4) - { + int padd_len = packet_len - (5 + len); + + if (useRandomPadding) { + for (int i = 0; i < padd_len; i = i + 4) { /* * don't waste calls to rnd.nextInt() (by using only 8bit of the * output). just believe me: even though we may write here up to 3 @@ -150,134 +118,122 @@ else if (padd > 64) * bytes, and that is bigger than any current cipher block size + 64). */ - int r = rnd.nextInt(); - send_padding_buffer[i] = (byte) r; - send_padding_buffer[i + 1] = (byte) (r >> 8); - send_padding_buffer[i + 2] = (byte) (r >> 16); - send_padding_buffer[i + 3] = (byte) (r >> 24); - } - } - else - { + int r = rnd.nextInt(); + send_padding_buffer[i] = (byte) r; + send_padding_buffer[i + 1] = (byte) (r >> 8); + send_padding_buffer[i + 2] = (byte) (r >> 16); + send_padding_buffer[i + 3] = (byte) (r >> 24); + } + } else { /* use zero padding for unencrypted traffic */ - for (int i = 0; i < padd_len; i++) - send_padding_buffer[i] = 0; + for (int i = 0; i < padd_len; i++) + send_padding_buffer[i] = 0; /* Actually this code is paranoid: we never filled any * bytes into the padding buffer so far, therefore it should * consist of zeros only. */ - } + } - send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24); - send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16); - send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8); - send_packet_header_buffer[3] = (byte) ((packet_len - 4)); - send_packet_header_buffer[4] = (byte) padd_len; + send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24); + send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16); + send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8); + send_packet_header_buffer[3] = (byte) ((packet_len - 4)); + send_packet_header_buffer[4] = (byte) padd_len; - cos.write(send_packet_header_buffer, 0, 5); - cos.write(message, off, len); - cos.write(send_padding_buffer, 0, padd_len); + cos.write(send_packet_header_buffer, 0, 5); + cos.write(message, off, len); + cos.write(send_padding_buffer, 0, padd_len); - if (send_mac != null) - { - send_mac.initMac(send_seq_number); - send_mac.update(send_packet_header_buffer, 0, 5); - send_mac.update(message, off, len); - send_mac.update(send_padding_buffer, 0, padd_len); + if (send_mac != null) { + send_mac.initMac(send_seq_number); + send_mac.update(send_packet_header_buffer, 0, 5); + send_mac.update(message, off, len); + send_mac.update(send_padding_buffer, 0, padd_len); - send_mac.getMac(send_mac_buffer, 0); - cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); - } + send_mac.getMac(send_mac_buffer, 0); + cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); + } - cos.flush(); + cos.flush(); - if (log.isEnabled()) - { - log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); - } + if (log.isEnabled()) { + log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); + } - send_seq_number++; - } + send_seq_number++; + } - public int peekNextMessageLength() throws IOException - { - if (recv_packet_header_present == false) - { - cis.read(recv_packet_header_buffer, 0, 5); - recv_packet_header_present = true; - } + public int peekNextMessageLength() throws IOException { + if (recv_packet_header_present == false) { + cis.read(recv_packet_header_buffer, 0, 5); + recv_packet_header_present = true; + } - int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) - | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) - | ((recv_packet_header_buffer[3] & 0xff)); + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) + | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) + | ((recv_packet_header_buffer[3] & 0xff)); - int padding_length = recv_packet_header_buffer[4] & 0xff; + int padding_length = recv_packet_header_buffer[4] & 0xff; - if (packet_length > 35000 || packet_length < 12) - throw new IOException("Illegal packet size! (" + packet_length + ")"); + if (packet_length > 35000 || packet_length < 12) + throw new IOException("Illegal packet size! (" + packet_length + ")"); - int payload_length = packet_length - padding_length - 1; + int payload_length = packet_length - padding_length - 1; - if (payload_length < 0) - throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); + if (payload_length < 0) + throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); - return payload_length; - } + return payload_length; + } - public int receiveMessage(byte buffer[], int off, int len) throws IOException - { - if (recv_packet_header_present == false) - { - cis.read(recv_packet_header_buffer, 0, 5); - } - else - recv_packet_header_present = false; + public int receiveMessage(byte buffer[], int off, int len) throws IOException { + if (recv_packet_header_present == false) { + cis.read(recv_packet_header_buffer, 0, 5); + } else + recv_packet_header_present = false; - int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) - | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) - | ((recv_packet_header_buffer[3] & 0xff)); + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) + | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) + | ((recv_packet_header_buffer[3] & 0xff)); - int padding_length = recv_packet_header_buffer[4] & 0xff; + int padding_length = recv_packet_header_buffer[4] & 0xff; - if (packet_length > 35000 || packet_length < 12) - throw new IOException("Illegal packet size! (" + packet_length + ")"); + if (packet_length > 35000 || packet_length < 12) + throw new IOException("Illegal packet size! (" + packet_length + ")"); - int payload_length = packet_length - padding_length - 1; + int payload_length = packet_length - padding_length - 1; - if (payload_length < 0) - throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); + if (payload_length < 0) + throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")"); - if (payload_length >= len) - throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); + if (payload_length >= len) + throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); - cis.read(buffer, off, payload_length); - cis.read(recv_padding_buffer, 0, padding_length); + cis.read(buffer, off, payload_length); + cis.read(recv_padding_buffer, 0, padding_length); - if (recv_mac != null) - { - cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); + if (recv_mac != null) { + cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); - recv_mac.initMac(recv_seq_number); - recv_mac.update(recv_packet_header_buffer, 0, 5); - recv_mac.update(buffer, off, payload_length); - recv_mac.update(recv_padding_buffer, 0, padding_length); - recv_mac.getMac(recv_mac_buffer_cmp, 0); + recv_mac.initMac(recv_seq_number); + recv_mac.update(recv_packet_header_buffer, 0, 5); + recv_mac.update(buffer, off, payload_length); + recv_mac.update(recv_padding_buffer, 0, padding_length); + recv_mac.getMac(recv_mac_buffer_cmp, 0); - for (int i = 0; i < recv_mac_buffer.length; i++) - { - if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) - throw new IOException("Remote sent corrupt MAC."); - } - } + for (int i = 0; i < recv_mac_buffer.length; i++) { + if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) + throw new IOException("Remote sent corrupt MAC."); + } + } - recv_seq_number++; + recv_seq_number++; - if (log.isEnabled()) - { - log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length - + " bytes payload"); - } + if (log.isEnabled()) { + log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + + " bytes payload"); + } - return payload_length; - } + return payload_length; + } } diff --git a/src/ch/ethz/ssh2/transport/TransportManager.java b/src/ch/ethz/ssh2/transport/TransportManager.java index 52568b3..ee8a108 100644 --- a/src/ch/ethz/ssh2/transport/TransportManager.java +++ b/src/ch/ethz/ssh2/transport/TransportManager.java @@ -1,23 +1,6 @@ - package ch.ethz.ssh2.transport; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.security.SecureRandom; -import java.util.Vector; - -import ch.ethz.ssh2.ConnectionInfo; -import ch.ethz.ssh2.ConnectionMonitor; -import ch.ethz.ssh2.DHGexParameters; -import ch.ethz.ssh2.HTTPProxyData; -import ch.ethz.ssh2.HTTPProxyException; -import ch.ethz.ssh2.ProxyData; -import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.*; import ch.ethz.ssh2.crypto.Base64; import ch.ethz.ssh2.crypto.CryptoWishList; import ch.ethz.ssh2.crypto.cipher.BlockCipher; @@ -28,6 +11,16 @@ import ch.ethz.ssh2.packets.TypesReader; import ch.ethz.ssh2.util.Tokenizer; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.Vector; + /* * Yes, the "standard" is a big mess. On one side, the say that arbitary channel * packets are allowed during kex exchange, on the other side we need to blindly @@ -46,550 +39,398 @@ /** * TransportManager. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: TransportManager.java,v 1.16 2006/08/11 12:24:00 cplattne Exp $ */ -public class TransportManager -{ - private static final Logger log = Logger.getLogger(TransportManager.class); - - class HandlerEntry - { - MessageHandler mh; - int low; - int high; - } - - private final Vector asynchronousQueue = new Vector(); - private Thread asynchronousThread = null; - - class AsynchronousWorker extends Thread - { - public void run() - { - while (true) - { - byte[] msg = null; - - synchronized (asynchronousQueue) - { - if (asynchronousQueue.size() == 0) - { - /* After the queue is empty for about 2 seconds, stop this thread */ - - try - { - asynchronousQueue.wait(2000); - } - catch (InterruptedException e) - { - /* OKOK, if somebody interrupts us, then we may die earlier. */ - } +public class TransportManager { + private static final Logger log = Logger.getLogger(TransportManager.class); + final Socket sock = new Socket(); + private final Vector asynchronousQueue = new Vector(); + String hostname; + int port; + Object connectionSemaphore = new Object(); + boolean flagKexOngoing = false; + boolean connectionClosed = false; + Throwable reasonClosedCause = null; + TransportConnection tc; + KexManager km; + Vector messageHandlers = new Vector(); + Thread receiveThread; + Vector connectionMonitors = new Vector(); + boolean monitorsWereInformed = false; + private Thread asynchronousThread = null; + + public TransportManager(String host, int port) throws IOException { + this.hostname = host; + this.port = port; + } + + /** + * There were reports that there are JDKs which use + * the resolver even though one supplies a dotted IP + * address in the Socket constructor. That is why we + * try to generate the InetAdress "by hand". + * + * @param host + * @return the InetAddress + * @throws UnknownHostException + */ + private InetAddress createInetAddress(String host) throws UnknownHostException { + /* Check if it is a dotted IP4 address */ - if (asynchronousQueue.size() == 0) - { - asynchronousThread = null; - return; - } - } + InetAddress addr = parseIPv4Address(host); - msg = (byte[]) asynchronousQueue.remove(0); - } + if (addr != null) + return addr; - /* The following invocation may throw an IOException. - * There is no point in handling it - it simply means - * that the connection has a problem and we should stop - * sending asynchronously messages. We do not need to signal that - * we have exited (asynchronousThread = null): further - * messages in the queue cannot be sent by this or any - * other thread. - * Other threads will sooner or later (when receiving or - * sending the next message) get the same IOException and - * get to the same conclusion. - */ + return InetAddress.getByName(host); + } - try - { - sendMessage(msg); - } - catch (IOException e) - { - return; - } - } - } - } - - String hostname; - int port; - final Socket sock = new Socket(); - - Object connectionSemaphore = new Object(); - - boolean flagKexOngoing = false; - boolean connectionClosed = false; - - Throwable reasonClosedCause = null; - - TransportConnection tc; - KexManager km; - - Vector messageHandlers = new Vector(); - - Thread receiveThread; - - Vector connectionMonitors = new Vector(); - boolean monitorsWereInformed = false; - - /** - * There were reports that there are JDKs which use - * the resolver even though one supplies a dotted IP - * address in the Socket constructor. That is why we - * try to generate the InetAdress "by hand". - * - * @param host - * @return the InetAddress - * @throws UnknownHostException - */ - private InetAddress createInetAddress(String host) throws UnknownHostException - { - /* Check if it is a dotted IP4 address */ + private InetAddress parseIPv4Address(String host) throws UnknownHostException { + if (host == null) + return null; - InetAddress addr = parseIPv4Address(host); + String[] quad = Tokenizer.parseTokens(host, '.'); - if (addr != null) - return addr; + if ((quad == null) || (quad.length != 4)) + return null; - return InetAddress.getByName(host); - } + byte[] addr = new byte[4]; - private InetAddress parseIPv4Address(String host) throws UnknownHostException - { - if (host == null) - return null; + for (int i = 0; i < 4; i++) { + int part = 0; - String[] quad = Tokenizer.parseTokens(host, '.'); + if ((quad[i].length() == 0) || (quad[i].length() > 3)) + return null; - if ((quad == null) || (quad.length != 4)) - return null; + for (int k = 0; k < quad[i].length(); k++) { + char c = quad[i].charAt(k); - byte[] addr = new byte[4]; + /* No, Character.isDigit is not the same */ + if ((c < '0') || (c > '9')) + return null; - for (int i = 0; i < 4; i++) - { - int part = 0; + part = part * 10 + (c - '0'); + } - if ((quad[i].length() == 0) || (quad[i].length() > 3)) - return null; + if (part > 255) /* 300.1.2.3 is invalid =) */ + return null; - for (int k = 0; k < quad[i].length(); k++) - { - char c = quad[i].charAt(k); + addr[i] = (byte) part; + } - /* No, Character.isDigit is not the same */ - if ((c < '0') || (c > '9')) - return null; - - part = part * 10 + (c - '0'); - } - - if (part > 255) /* 300.1.2.3 is invalid =) */ - return null; - - addr[i] = (byte) part; - } - - return InetAddress.getByAddress(host, addr); - } - - public TransportManager(String host, int port) throws IOException - { - this.hostname = host; - this.port = port; - } - - public int getPacketOverheadEstimate() - { - return tc.getPacketOverheadEstimate(); - } - - public void setTcpNoDelay(boolean state) throws IOException - { - sock.setTcpNoDelay(state); - } - - public void setSoTimeout(int timeout) throws IOException - { - sock.setSoTimeout(timeout); - } - - public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException - { - return km.getOrWaitForConnectionInfo(kexNumber); - } - - public Throwable getReasonClosedCause() - { - synchronized (connectionSemaphore) - { - return reasonClosedCause; - } - } - - public byte[] getSessionIdentifier() - { - return km.sessionId; - } - - public void close(Throwable cause, boolean useDisconnectPacket) - { - if (useDisconnectPacket == false) - { + return InetAddress.getByAddress(host, addr); + } + + public int getPacketOverheadEstimate() { + return tc.getPacketOverheadEstimate(); + } + + public void setTcpNoDelay(boolean state) throws IOException { + sock.setTcpNoDelay(state); + } + + public void setSoTimeout(int timeout) throws IOException { + sock.setSoTimeout(timeout); + } + + public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException { + return km.getOrWaitForConnectionInfo(kexNumber); + } + + public Throwable getReasonClosedCause() { + synchronized (connectionSemaphore) { + return reasonClosedCause; + } + } + + public byte[] getSessionIdentifier() { + return km.sessionId; + } + + public void close(Throwable cause, boolean useDisconnectPacket) { + if (useDisconnectPacket == false) { /* OK, hard shutdown - do not aquire the semaphore, * perhaps somebody is inside (and waits until the remote * side is ready to accept new data). */ - try - { - sock.close(); - } - catch (IOException ignore) - { - } + try { + sock.close(); + } catch (IOException ignore) { + } /* OK, whoever tried to send data, should now agree that * there is no point in further waiting =) * It is safe now to aquire the semaphore. */ - } - - synchronized (connectionSemaphore) - { - if (connectionClosed == false) - { - if (useDisconnectPacket == true) - { - try - { - byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "") - .getPayload(); - if (tc != null) - tc.sendMessage(msg); - } - catch (IOException ignore) - { - } - - try - { - sock.close(); - } - catch (IOException ignore) - { - } - } - - connectionClosed = true; - reasonClosedCause = cause; /* may be null */ - } - connectionSemaphore.notifyAll(); - } + } + + synchronized (connectionSemaphore) { + if (connectionClosed == false) { + if (useDisconnectPacket == true) { + try { + byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "") + .getPayload(); + if (tc != null) + tc.sendMessage(msg); + } catch (IOException ignore) { + } + + try { + sock.close(); + } catch (IOException ignore) { + } + } + + connectionClosed = true; + reasonClosedCause = cause; /* may be null */ + } + connectionSemaphore.notifyAll(); + } /* No check if we need to inform the monitors */ - Vector monitors = null; + Vector monitors = null; - synchronized (this) - { + synchronized (this) { /* Short term lock to protect "connectionMonitors" * and "monitorsWereInformed" * (they may be modified concurrently) */ - if (monitorsWereInformed == false) - { - monitorsWereInformed = true; - monitors = (Vector) connectionMonitors.clone(); - } - } - - if (monitors != null) - { - for (int i = 0; i < monitors.size(); i++) - { - try - { - ConnectionMonitor cmon = (ConnectionMonitor) monitors.elementAt(i); - cmon.connectionLost(reasonClosedCause); - } - catch (Exception ignore) - { - } - } - } - } - - private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException - { + if (monitorsWereInformed == false) { + monitorsWereInformed = true; + monitors = (Vector) connectionMonitors.clone(); + } + } + + if (monitors != null) { + for (int i = 0; i < monitors.size(); i++) { + try { + ConnectionMonitor cmon = (ConnectionMonitor) monitors.elementAt(i); + cmon.connectionLost(reasonClosedCause); + } catch (Exception ignore) { + } + } + } + } + + private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException { /* See the comment for createInetAddress() */ - if (proxyData == null) - { - InetAddress addr = createInetAddress(hostname); - sock.connect(new InetSocketAddress(addr, port), connectTimeout); - sock.setSoTimeout(0); - return; - } + if (proxyData == null) { + InetAddress addr = createInetAddress(hostname); + sock.connect(new InetSocketAddress(addr, port), connectTimeout); + sock.setSoTimeout(0); + return; + } - if (proxyData instanceof HTTPProxyData) - { - HTTPProxyData pd = (HTTPProxyData) proxyData; + if (proxyData instanceof HTTPProxyData) { + HTTPProxyData pd = (HTTPProxyData) proxyData; /* At the moment, we only support HTTP proxies */ - InetAddress addr = createInetAddress(pd.proxyHost); - sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout); - sock.setSoTimeout(0); + InetAddress addr = createInetAddress(pd.proxyHost); + sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout); + sock.setSoTimeout(0); /* OK, now tell the proxy where we actually want to connect to */ - StringBuffer sb = new StringBuffer(); - - sb.append("CONNECT "); - sb.append(hostname); - sb.append(':'); - sb.append(port); - sb.append(" HTTP/1.0\r\n"); - - if ((pd.proxyUser != null) && (pd.proxyPass != null)) - { - String credentials = pd.proxyUser + ":" + pd.proxyPass; - char[] encoded = Base64.encode(credentials.getBytes()); - sb.append("Proxy-Authorization: Basic "); - sb.append(encoded); - sb.append("\r\n"); - } - - if (pd.requestHeaderLines != null) - { - for (int i = 0; i < pd.requestHeaderLines.length; i++) - { - if (pd.requestHeaderLines[i] != null) - { - sb.append(pd.requestHeaderLines[i]); - sb.append("\r\n"); - } - } - } - - sb.append("\r\n"); - - OutputStream out = sock.getOutputStream(); - - out.write(sb.toString().getBytes()); - out.flush(); + StringBuffer sb = new StringBuffer(); + + sb.append("CONNECT "); + sb.append(hostname); + sb.append(':'); + sb.append(port); + sb.append(" HTTP/1.0\r\n"); + + if ((pd.proxyUser != null) && (pd.proxyPass != null)) { + String credentials = pd.proxyUser + ":" + pd.proxyPass; + char[] encoded = Base64.encode(credentials.getBytes()); + sb.append("Proxy-Authorization: Basic "); + sb.append(encoded); + sb.append("\r\n"); + } + + if (pd.requestHeaderLines != null) { + for (int i = 0; i < pd.requestHeaderLines.length; i++) { + if (pd.requestHeaderLines[i] != null) { + sb.append(pd.requestHeaderLines[i]); + sb.append("\r\n"); + } + } + } + + sb.append("\r\n"); + + OutputStream out = sock.getOutputStream(); + + out.write(sb.toString().getBytes()); + out.flush(); /* Now parse the HTTP response */ - byte[] buffer = new byte[1024]; - InputStream in = sock.getInputStream(); + byte[] buffer = new byte[1024]; + InputStream in = sock.getInputStream(); - int len = ClientServerHello.readLineRN(in, buffer); + int len = ClientServerHello.readLineRN(in, buffer); - String httpReponse = new String(buffer, 0, len); + String httpReponse = new String(buffer, 0, len); - if (httpReponse.startsWith("HTTP/") == false) - throw new IOException("The proxy did not send back a valid HTTP response."); + if (httpReponse.startsWith("HTTP/") == false) + throw new IOException("The proxy did not send back a valid HTTP response."); /* "HTTP/1.X XYZ X" => 14 characters minimum */ - if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) - throw new IOException("The proxy did not send back a valid HTTP response."); + if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) + throw new IOException("The proxy did not send back a valid HTTP response."); - int errorCode = 0; + int errorCode = 0; - try - { - errorCode = Integer.parseInt(httpReponse.substring(9, 12)); - } - catch (NumberFormatException ignore) - { - throw new IOException("The proxy did not send back a valid HTTP response."); - } + try { + errorCode = Integer.parseInt(httpReponse.substring(9, 12)); + } catch (NumberFormatException ignore) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } - if ((errorCode < 0) || (errorCode > 999)) - throw new IOException("The proxy did not send back a valid HTTP response."); + if ((errorCode < 0) || (errorCode > 999)) + throw new IOException("The proxy did not send back a valid HTTP response."); - if (errorCode != 200) - { - throw new HTTPProxyException(httpReponse.substring(13), errorCode); - } + if (errorCode != 200) { + throw new HTTPProxyException(httpReponse.substring(13), errorCode); + } /* OK, read until empty line */ - while (true) - { - len = ClientServerHello.readLineRN(in, buffer); - if (len == 0) - break; - } - return; - } - - throw new IOException("Unsupported ProxyData"); - } - - public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex, - int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException - { + while (true) { + len = ClientServerHello.readLineRN(in, buffer); + if (len == 0) + break; + } + return; + } + + throw new IOException("Unsupported ProxyData"); + } + + public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex, + int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException { /* First, establish the TCP connection to the SSH-2 server */ - establishConnection(proxyData, connectTimeout); + establishConnection(proxyData, connectTimeout); /* Parse the server line and say hello - important: this information is later needed for the * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object * for later use. */ - ClientServerHello csh = new ClientServerHello(sock.getInputStream(), sock.getOutputStream()); + ClientServerHello csh = new ClientServerHello(sock.getInputStream(), sock.getOutputStream()); - tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); + tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); - km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd); - km.initiateKEX(cwl, dhgex); + km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd); + km.initiateKEX(cwl, dhgex); - receiveThread = new Thread(new Runnable() - { - public void run() - { - try - { - receiveLoop(); - } - catch (IOException e) - { - close(e, false); + receiveThread = new Thread(new Runnable() { + public void run() { + try { + receiveLoop(); + } catch (IOException e) { + close(e, false); - if (log.isEnabled()) - log.log(10, "Receive thread: error in receiveLoop: " + e.getMessage()); - } + if (log.isEnabled()) + log.log(10, "Receive thread: error in receiveLoop: " + e.getMessage()); + } - if (log.isEnabled()) - log.log(50, "Receive thread: back from receiveLoop"); + if (log.isEnabled()) + log.log(50, "Receive thread: back from receiveLoop"); /* Tell all handlers that it is time to say goodbye */ - if (km != null) - { - try - { - km.handleMessage(null, 0); - } - catch (IOException e) - { - } - } - - for (int i = 0; i < messageHandlers.size(); i++) - { - HandlerEntry he = (HandlerEntry) messageHandlers.elementAt(i); - try - { - he.mh.handleMessage(null, 0); - } - catch (Exception ignore) - { - } - } - } - }); - - receiveThread.setDaemon(true); - receiveThread.start(); - } - - public void registerMessageHandler(MessageHandler mh, int low, int high) - { - HandlerEntry he = new HandlerEntry(); - he.mh = mh; - he.low = low; - he.high = high; - - synchronized (messageHandlers) - { - messageHandlers.addElement(he); - } - } - - public void removeMessageHandler(MessageHandler mh, int low, int high) - { - synchronized (messageHandlers) - { - for (int i = 0; i < messageHandlers.size(); i++) - { - HandlerEntry he = (HandlerEntry) messageHandlers.elementAt(i); - if ((he.mh == mh) && (he.low == low) && (he.high == high)) - { - messageHandlers.removeElementAt(i); - break; - } - } - } - } - - public void sendKexMessage(byte[] msg) throws IOException - { - synchronized (connectionSemaphore) - { - if (connectionClosed) - { - throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); - } - - flagKexOngoing = true; - - try - { - tc.sendMessage(msg); - } - catch (IOException e) - { - close(e, false); - throw e; - } - } - } - - public void kexFinished() throws IOException - { - synchronized (connectionSemaphore) - { - flagKexOngoing = false; - connectionSemaphore.notifyAll(); - } - } - - public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException - { - km.initiateKEX(cwl, dhgex); - } - - public void changeRecvCipher(BlockCipher bc, MAC mac) - { - tc.changeRecvCipher(bc, mac); - } - - public void changeSendCipher(BlockCipher bc, MAC mac) - { - tc.changeSendCipher(bc, mac); - } - - public void sendAsynchronousMessage(byte[] msg) throws IOException - { - synchronized (asynchronousQueue) - { - asynchronousQueue.addElement(msg); + if (km != null) { + try { + km.handleMessage(null, 0); + } catch (IOException e) { + } + } + + for (int i = 0; i < messageHandlers.size(); i++) { + HandlerEntry he = (HandlerEntry) messageHandlers.elementAt(i); + try { + he.mh.handleMessage(null, 0); + } catch (Exception ignore) { + } + } + } + }); + + receiveThread.setDaemon(true); + receiveThread.start(); + } + + public void registerMessageHandler(MessageHandler mh, int low, int high) { + HandlerEntry he = new HandlerEntry(); + he.mh = mh; + he.low = low; + he.high = high; + + synchronized (messageHandlers) { + messageHandlers.addElement(he); + } + } + + public void removeMessageHandler(MessageHandler mh, int low, int high) { + synchronized (messageHandlers) { + for (int i = 0; i < messageHandlers.size(); i++) { + HandlerEntry he = (HandlerEntry) messageHandlers.elementAt(i); + if ((he.mh == mh) && (he.low == low) && (he.high == high)) { + messageHandlers.removeElementAt(i); + break; + } + } + } + } + + public void sendKexMessage(byte[] msg) throws IOException { + synchronized (connectionSemaphore) { + if (connectionClosed) { + throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + + flagKexOngoing = true; + + try { + tc.sendMessage(msg); + } catch (IOException e) { + close(e, false); + throw e; + } + } + } + + public void kexFinished() throws IOException { + synchronized (connectionSemaphore) { + flagKexOngoing = false; + connectionSemaphore.notifyAll(); + } + } + + public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException { + km.initiateKEX(cwl, dhgex); + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) { + tc.changeRecvCipher(bc, mac); + } + + public void changeSendCipher(BlockCipher bc, MAC mac) { + tc.changeSendCipher(bc, mac); + } + + public void sendAsynchronousMessage(byte[] msg) throws IOException { + synchronized (asynchronousQueue) { + asynchronousQueue.addElement(msg); /* This limit should be flexible enough. We need this, otherwise the peer * can flood us with global requests (and other stuff where we have to reply @@ -597,131 +438,110 @@ public void sendAsynchronousMessage(byte[] msg) throws IOException * read what we send) this will probably put us in a low memory situation * (our send queue would grow and grow and...) */ - if (asynchronousQueue.size() > 100) - throw new IOException("Error: the peer is not consuming our asynchronous replies."); + if (asynchronousQueue.size() > 100) + throw new IOException("Error: the peer is not consuming our asynchronous replies."); /* Check if we have an asynchronous sending thread */ - if (asynchronousThread == null) - { - asynchronousThread = new AsynchronousWorker(); - asynchronousThread.setDaemon(true); - asynchronousThread.start(); + if (asynchronousThread == null) { + asynchronousThread = new AsynchronousWorker(); + asynchronousThread.setDaemon(true); + asynchronousThread.start(); /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */ - } - } - } - - public void setConnectionMonitors(Vector monitors) - { - synchronized (this) - { - connectionMonitors = (Vector) monitors.clone(); - } - } - - public void sendMessage(byte[] msg) throws IOException - { - if (Thread.currentThread() == receiveThread) - throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!"); - - synchronized (connectionSemaphore) - { - while (true) - { - if (connectionClosed) - { - throw (IOException) new IOException("Sorry, this connection is closed.") - .initCause(reasonClosedCause); - } - - if (flagKexOngoing == false) - break; - - try - { - connectionSemaphore.wait(); - } - catch (InterruptedException e) - { - } - } - - try - { - tc.sendMessage(msg); - } - catch (IOException e) - { - close(e, false); - throw e; - } - } - } - - public void receiveLoop() throws IOException - { - byte[] msg = new byte[35000]; - - while (true) - { - int msglen = tc.receiveMessage(msg, 0, msg.length); - - int type = msg[0] & 0xff; - - if (type == Packets.SSH_MSG_IGNORE) - continue; - - if (type == Packets.SSH_MSG_DEBUG) - { - if (log.isEnabled()) - { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); - tr.readBoolean(); - StringBuffer debugMessageBuffer = new StringBuffer(); - debugMessageBuffer.append(tr.readString("UTF-8")); - - for (int i = 0; i < debugMessageBuffer.length(); i++) - { - char c = debugMessageBuffer.charAt(i); - - if ((c >= 32) && (c <= 126)) - continue; - debugMessageBuffer.setCharAt(i, '\uFFFD'); - } - - log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); - } - continue; - } - - if (type == Packets.SSH_MSG_UNIMPLEMENTED) - { - throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); - } - - if (type == Packets.SSH_MSG_DISCONNECT) - { - TypesReader tr = new TypesReader(msg, 0, msglen); - tr.readByte(); - int reason_code = tr.readUINT32(); - StringBuffer reasonBuffer = new StringBuffer(); - reasonBuffer.append(tr.readString("UTF-8")); + } + } + } + + public void setConnectionMonitors(Vector monitors) { + synchronized (this) { + connectionMonitors = (Vector) monitors.clone(); + } + } + + public void sendMessage(byte[] msg) throws IOException { + if (Thread.currentThread() == receiveThread) + throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!"); + + synchronized (connectionSemaphore) { + while (true) { + if (connectionClosed) { + throw (IOException) new IOException("Sorry, this connection is closed.") + .initCause(reasonClosedCause); + } + + if (flagKexOngoing == false) + break; + + try { + connectionSemaphore.wait(); + } catch (InterruptedException e) { + } + } + + try { + tc.sendMessage(msg); + } catch (IOException e) { + close(e, false); + throw e; + } + } + } + + public void receiveLoop() throws IOException { + byte[] msg = new byte[35000]; + + while (true) { + int msglen = tc.receiveMessage(msg, 0, msg.length); + + int type = msg[0] & 0xff; + + if (type == Packets.SSH_MSG_IGNORE) + continue; + + if (type == Packets.SSH_MSG_DEBUG) { + if (log.isEnabled()) { + TypesReader tr = new TypesReader(msg, 0, msglen); + tr.readByte(); + tr.readBoolean(); + StringBuffer debugMessageBuffer = new StringBuffer(); + debugMessageBuffer.append(tr.readString("UTF-8")); + + for (int i = 0; i < debugMessageBuffer.length(); i++) { + char c = debugMessageBuffer.charAt(i); + + if ((c >= 32) && (c <= 126)) + continue; + debugMessageBuffer.setCharAt(i, '\uFFFD'); + } + + log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); + } + continue; + } + + if (type == Packets.SSH_MSG_UNIMPLEMENTED) { + throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); + } + + if (type == Packets.SSH_MSG_DISCONNECT) { + TypesReader tr = new TypesReader(msg, 0, msglen); + tr.readByte(); + int reason_code = tr.readUINT32(); + StringBuffer reasonBuffer = new StringBuffer(); + reasonBuffer.append(tr.readString("UTF-8")); /* * Do not get fooled by servers that send abnormal long error * messages */ - if (reasonBuffer.length() > 255) - { - reasonBuffer.setLength(255); - reasonBuffer.setCharAt(254, '.'); - reasonBuffer.setCharAt(253, '.'); - reasonBuffer.setCharAt(252, '.'); - } + if (reasonBuffer.length() > 255) { + reasonBuffer.setLength(255); + reasonBuffer.setCharAt(254, '.'); + reasonBuffer.setCharAt(253, '.'); + reasonBuffer.setCharAt(252, '.'); + } /* * Also, check that the server did not send charcaters that may @@ -730,46 +550,93 @@ public void receiveLoop() throws IOException * all others with 0xFFFD (UNICODE replacement character). */ - for (int i = 0; i < reasonBuffer.length(); i++) - { - char c = reasonBuffer.charAt(i); + for (int i = 0; i < reasonBuffer.length(); i++) { + char c = reasonBuffer.charAt(i); - if ((c >= 32) && (c <= 126)) - continue; - reasonBuffer.setCharAt(i, '\uFFFD'); - } + if ((c >= 32) && (c <= 126)) + continue; + reasonBuffer.setCharAt(i, '\uFFFD'); + } - throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): " - + reasonBuffer.toString()); - } + throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): " + + reasonBuffer.toString()); + } /* * Is it a KEX Packet? */ - if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) - || ((type >= 30) && (type <= 49))) - { - km.handleMessage(msg, msglen); - continue; - } - - MessageHandler mh = null; - - for (int i = 0; i < messageHandlers.size(); i++) - { - HandlerEntry he = (HandlerEntry) messageHandlers.elementAt(i); - if ((he.low <= type) && (type <= he.high)) - { - mh = he.mh; - break; - } - } - - if (mh == null) - throw new IOException("Unexpected SSH message (type " + type + ")"); - - mh.handleMessage(msg, msglen); - } - } + if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) + || ((type >= 30) && (type <= 49))) { + km.handleMessage(msg, msglen); + continue; + } + + MessageHandler mh = null; + + for (int i = 0; i < messageHandlers.size(); i++) { + HandlerEntry he = (HandlerEntry) messageHandlers.elementAt(i); + if ((he.low <= type) && (type <= he.high)) { + mh = he.mh; + break; + } + } + + if (mh == null) + throw new IOException("Unexpected SSH message (type " + type + ")"); + + mh.handleMessage(msg, msglen); + } + } + + class HandlerEntry { + MessageHandler mh; + int low; + int high; + } + + class AsynchronousWorker extends Thread { + public void run() { + while (true) { + byte[] msg = null; + + synchronized (asynchronousQueue) { + if (asynchronousQueue.size() == 0) { + /* After the queue is empty for about 2 seconds, stop this thread */ + + try { + asynchronousQueue.wait(2000); + } catch (InterruptedException e) { + /* OKOK, if somebody interrupts us, then we may die earlier. */ + } + + if (asynchronousQueue.size() == 0) { + asynchronousThread = null; + return; + } + } + + msg = (byte[]) asynchronousQueue.remove(0); + } + + /* The following invocation may throw an IOException. + * There is no point in handling it - it simply means + * that the connection has a problem and we should stop + * sending asynchronously messages. We do not need to signal that + * we have exited (asynchronousThread = null): further + * messages in the queue cannot be sent by this or any + * other thread. + * Other threads will sooner or later (when receiving or + * sending the next message) get the same IOException and + * get to the same conclusion. + */ + + try { + sendMessage(msg); + } catch (IOException e) { + return; + } + } + } + } } diff --git a/src/ch/ethz/ssh2/util/PasswordField.java b/src/ch/ethz/ssh2/util/PasswordField.java index e86b67a..82007f0 100644 --- a/src/ch/ethz/ssh2/util/PasswordField.java +++ b/src/ch/ethz/ssh2/util/PasswordField.java @@ -11,119 +11,117 @@ public class PasswordField { - /** - * @param input - * stream to be used (e.g. System.in) - * @param prompt - * The prompt to display to the user. - * @return The password as entered by the user. - */ - - public static final char[] getPassword(InputStream in, String prompt) throws IOException { - MaskingThread maskingthread = new MaskingThread(prompt); - Thread thread = new Thread(maskingthread); - thread.start(); - - char[] lineBuffer; - char[] buf; - int i; - - buf = lineBuffer = new char[128]; - - int room = buf.length; - int offset = 0; - int c; - - loop: while (true) { - switch (c = in.read()) { - case -1: - case '\n': - break loop; - - case '\r': - int c2 = in.read(); - if ((c2 != '\n') && (c2 != -1)) { - if (!(in instanceof PushbackInputStream)) { - in = new PushbackInputStream(in); - } - ((PushbackInputStream) in).unread(c2); - } else { - break loop; - } - - default: - if (--room < 0) { - buf = new char[offset + 128]; - room = buf.length - offset - 1; - System.arraycopy(lineBuffer, 0, buf, 0, offset); - Arrays.fill(lineBuffer, ' '); - lineBuffer = buf; - } - buf[offset++] = (char) c; - break; - } - } - maskingthread.stopMasking(); - if (offset == 0) { - return null; - } - char[] ret = new char[offset]; - System.arraycopy(buf, 0, ret, 0, offset); - Arrays.fill(buf, ' '); - return ret; - } - - /** - * This class attempts to erase characters echoed to the console. - */ - /** - * This class attempts to erase characters echoed to the console. - */ - static class MaskingThread extends Thread { - private volatile boolean stop = false; - - private int index; - - private String prompt; - - /** - * @param prompt - * The prompt displayed to the user - */ - public MaskingThread(String prompt) { - this.prompt = prompt; - } - - /** - * Begin masking until asked to stop. - */ - public void run() { - int priority = Thread.currentThread().getPriority(); - Thread.currentThread().setPriority(Thread.MAX_PRIORITY); - try { - while (!stop) { - try { - // attempt masking at this rate - Thread.sleep(2); - } catch (InterruptedException iex) { - iex.printStackTrace(); - } - - if (!stop) { - System.out.print("\r" + prompt + " \r" + prompt); - System.out.flush(); - } - } - } finally { // restore the original priority - Thread.currentThread().setPriority(priority); - } - } - - /** - * Instruct the thread to stop masking. - */ - public void stopMasking() { - this.stop = true; - } - } + /** + * @param in stream to be used (e.g. System.in) + * @param prompt The prompt to display to the user. + * @return The password as entered by the user. + */ + + public static final char[] getPassword(InputStream in, String prompt) throws IOException { + MaskingThread maskingthread = new MaskingThread(prompt); + Thread thread = new Thread(maskingthread); + thread.start(); + + char[] lineBuffer; + char[] buf; + int i; + + buf = lineBuffer = new char[128]; + + int room = buf.length; + int offset = 0; + int c; + + loop: + while (true) { + switch (c = in.read()) { + case -1: + case '\n': + break loop; + + case '\r': + int c2 = in.read(); + if ((c2 != '\n') && (c2 != -1)) { + if (!(in instanceof PushbackInputStream)) { + in = new PushbackInputStream(in); + } + ((PushbackInputStream) in).unread(c2); + } else { + break loop; + } + + default: + if (--room < 0) { + buf = new char[offset + 128]; + room = buf.length - offset - 1; + System.arraycopy(lineBuffer, 0, buf, 0, offset); + Arrays.fill(lineBuffer, ' '); + lineBuffer = buf; + } + buf[offset++] = (char) c; + break; + } + } + maskingthread.stopMasking(); + if (offset == 0) { + return null; + } + char[] ret = new char[offset]; + System.arraycopy(buf, 0, ret, 0, offset); + Arrays.fill(buf, ' '); + return ret; + } + + /** + * This class attempts to erase characters echoed to the console. + */ + /** + * This class attempts to erase characters echoed to the console. + */ + static class MaskingThread extends Thread { + private volatile boolean stop = false; + + private int index; + + private String prompt; + + /** + * @param prompt The prompt displayed to the user + */ + public MaskingThread(String prompt) { + this.prompt = prompt; + } + + /** + * Begin masking until asked to stop. + */ + public void run() { + int priority = Thread.currentThread().getPriority(); + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + try { + while (!stop) { + try { + // attempt masking at this rate + Thread.sleep(2); + } catch (InterruptedException iex) { + iex.printStackTrace(); + } + + if (!stop) { + System.out.print("\r" + prompt + " \r" + prompt); + System.out.flush(); + } + } + } finally { // restore the original priority + Thread.currentThread().setPriority(priority); + } + } + + /** + * Instruct the thread to stop masking. + */ + public void stopMasking() { + this.stop = true; + } + } } \ No newline at end of file diff --git a/src/ch/ethz/ssh2/util/PasswordReader.java b/src/ch/ethz/ssh2/util/PasswordReader.java index 82c1eba..f9fb658 100644 --- a/src/ch/ethz/ssh2/util/PasswordReader.java +++ b/src/ch/ethz/ssh2/util/PasswordReader.java @@ -5,18 +5,18 @@ import java.io.InputStreamReader; public class PasswordReader { - public static String readPassword(String prompt) { - System.out.print(prompt); - try { - return String.valueOf(System.console().readPassword()); - } catch (Throwable t) { - //java <1.6 - try { - BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); - return in.readLine(); - } catch (IOException e) { - return null; - } - } - } + public static String readPassword(String prompt) { + System.out.print(prompt); + try { + return String.valueOf(System.console().readPassword()); + } catch (Throwable t) { + //java <1.6 + try { + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + return in.readLine(); + } catch (IOException e) { + return null; + } + } + } } diff --git a/src/ch/ethz/ssh2/util/TimeoutService.java b/src/ch/ethz/ssh2/util/TimeoutService.java index 1cdaeb9..ec35588 100644 --- a/src/ch/ethz/ssh2/util/TimeoutService.java +++ b/src/ch/ethz/ssh2/util/TimeoutService.java @@ -1,13 +1,12 @@ - package ch.ethz.ssh2.util; +import ch.ethz.ssh2.log.Logger; + import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collections; import java.util.LinkedList; -import ch.ethz.ssh2.log.Logger; - /** * TimeoutService (beta). Here you can register a timeout. *

    @@ -15,134 +14,111 @@ * that rely on timeouts, then there will be only one timeout thread. Once all timeouts * have expired/are cancelled, the thread will (sooner or later) exit. * Only after new timeouts arrive a new thread (singleton) will be instantiated. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: TimeoutService.java,v 1.2 2006/07/30 21:59:29 cplattne Exp $ */ -public class TimeoutService -{ - private static final Logger log = Logger.getLogger(TimeoutService.class); - - public static class TimeoutToken implements Comparable - { - private long runTime; - private Runnable handler; - - private TimeoutToken(long runTime, Runnable handler) - { - this.runTime = runTime; - this.handler = handler; - } - - public int compareTo(Object o) - { - TimeoutToken t = (TimeoutToken) o; - if (runTime > t.runTime) - return 1; - if (runTime == t.runTime) - return 0; - return -1; - } - } - - private static class TimeoutThread extends Thread - { - public void run() - { - synchronized (todolist) - { - while (true) - { - if (todolist.size() == 0) - { - timeoutThread = null; - return; - } - - long now = System.currentTimeMillis(); - - TimeoutToken tt = (TimeoutToken) todolist.getFirst(); - - if (tt.runTime > now) - { - /* Not ready yet, sleep a little bit */ - - try - { - todolist.wait(tt.runTime - now); - } - catch (InterruptedException e) - { - } +public class TimeoutService { + private static final Logger log = Logger.getLogger(TimeoutService.class); + /* The list object is also used for locking purposes */ + private static final LinkedList todolist = new LinkedList(); + private static Thread timeoutThread = null; + + /** + * It is assumed that the passed handler will not execute for a long time. + * + * @param runTime + * @param handler + * @return a TimeoutToken that can be used to cancel the timeout. + */ + public static final TimeoutToken addTimeoutHandler(long runTime, Runnable handler) { + TimeoutToken token = new TimeoutToken(runTime, handler); + + synchronized (todolist) { + todolist.add(token); + Collections.sort(todolist); + + if (timeoutThread != null) + timeoutThread.interrupt(); + else { + timeoutThread = new TimeoutThread(); + timeoutThread.setDaemon(true); + timeoutThread.start(); + } + } + + return token; + } + + public static final void cancelTimeoutHandler(TimeoutToken token) { + synchronized (todolist) { + todolist.remove(token); + + if (timeoutThread != null) + timeoutThread.interrupt(); + } + } + + public static class TimeoutToken implements Comparable { + private long runTime; + private Runnable handler; + + private TimeoutToken(long runTime, Runnable handler) { + this.runTime = runTime; + this.handler = handler; + } + + public int compareTo(Object o) { + TimeoutToken t = (TimeoutToken) o; + if (runTime > t.runTime) + return 1; + if (runTime == t.runTime) + return 0; + return -1; + } + } + + private static class TimeoutThread extends Thread { + public void run() { + synchronized (todolist) { + while (true) { + if (todolist.size() == 0) { + timeoutThread = null; + return; + } + + long now = System.currentTimeMillis(); + + TimeoutToken tt = (TimeoutToken) todolist.getFirst(); + + if (tt.runTime > now) { + /* Not ready yet, sleep a little bit */ + + try { + todolist.wait(tt.runTime - now); + } catch (InterruptedException e) { + } /* We cannot simply go on, since it could be that the token * was removed (cancelled) or another one has been inserted in * the meantime. */ - continue; - } - - todolist.removeFirst(); - - try - { - tt.handler.run(); - } - catch (Exception e) - { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - log.log(20, "Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); - } - } - } - } - } - - /* The list object is also used for locking purposes */ - private static final LinkedList todolist = new LinkedList(); - - private static Thread timeoutThread = null; - - /** - * It is assumed that the passed handler will not execute for a long time. - * - * @param runTime - * @param handler - * @return a TimeoutToken that can be used to cancel the timeout. - */ - public static final TimeoutToken addTimeoutHandler(long runTime, Runnable handler) - { - TimeoutToken token = new TimeoutToken(runTime, handler); - - synchronized (todolist) - { - todolist.add(token); - Collections.sort(todolist); - - if (timeoutThread != null) - timeoutThread.interrupt(); - else - { - timeoutThread = new TimeoutThread(); - timeoutThread.setDaemon(true); - timeoutThread.start(); - } - } - - return token; - } - - public static final void cancelTimeoutHandler(TimeoutToken token) - { - synchronized (todolist) - { - todolist.remove(token); - - if (timeoutThread != null) - timeoutThread.interrupt(); - } - } + continue; + } + + todolist.removeFirst(); + + try { + tt.handler.run(); + } catch (Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + log.log(20, "Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); + } + } + } + } + } } diff --git a/src/ch/ethz/ssh2/util/Tokenizer.java b/src/ch/ethz/ssh2/util/Tokenizer.java index c4dd904..a986013 100644 --- a/src/ch/ethz/ssh2/util/Tokenizer.java +++ b/src/ch/ethz/ssh2/util/Tokenizer.java @@ -1,51 +1,43 @@ - package ch.ethz.ssh2.util; /** * Tokenizer. Why? Because StringTokenizer is not available in J2ME. - * + * * @author Christian Plattner, plattner@inf.ethz.ch * @version $Id: Tokenizer.java,v 1.1 2005/12/05 17:13:27 cplattne Exp $ */ -public class Tokenizer -{ - /** - * Exists because StringTokenizer is not available in J2ME. - * Returns an array with at least 1 entry. - * - * @param source must be non-null - * @param delimiter - * @return an array of Strings - */ - public static String[] parseTokens(String source, char delimiter) - { - int numtoken = 1; +public class Tokenizer { + /** + * Exists because StringTokenizer is not available in J2ME. + * Returns an array with at least 1 entry. + * + * @param source must be non-null + * @param delimiter + * @return an array of Strings + */ + public static String[] parseTokens(String source, char delimiter) { + int numtoken = 1; - for (int i = 0; i < source.length(); i++) - { - if (source.charAt(i) == delimiter) - numtoken++; - } + for (int i = 0; i < source.length(); i++) { + if (source.charAt(i) == delimiter) + numtoken++; + } - String list[] = new String[numtoken]; - int nextfield = 0; + String list[] = new String[numtoken]; + int nextfield = 0; - for (int i = 0; i < numtoken; i++) - { - if (nextfield >= source.length()) - { - list[i] = ""; - } - else - { - int idx = source.indexOf(delimiter, nextfield); - if (idx == -1) - idx = source.length(); - list[i] = source.substring(nextfield, idx); - nextfield = idx + 1; - } - } + for (int i = 0; i < numtoken; i++) { + if (nextfield >= source.length()) { + list[i] = ""; + } else { + int idx = source.indexOf(delimiter, nextfield); + if (idx == -1) + idx = source.length(); + list[i] = source.substring(nextfield, idx); + nextfield = idx + 1; + } + } - return list; - } + return list; + } } diff --git a/src/edu/caltech/hep/dcapj/Config.java b/src/edu/caltech/hep/dcapj/Config.java index b92784d..ab4d0e8 100644 --- a/src/edu/caltech/hep/dcapj/Config.java +++ b/src/edu/caltech/hep/dcapj/Config.java @@ -13,7 +13,7 @@ * This class contains configuration information for dCapJ. The default values * can be overridden by setting the environment variable * DCAPJ_CONFIG_FILE to the absolute path of the file. - * + * * @author Kamran Soomro * @author Faisal Khan */ @@ -35,7 +35,6 @@ public class Config { *

    * If the DCAPJ_CONFIG_FILE variable is defined, the values * defined in the file override the default values. - * */ public Config() { initialize(); @@ -92,7 +91,7 @@ private void initialize() { /** * Get the Pnfs ID of the file. - * + * * @return The Pnfs ID of the file. */ public String getPnfsDir() { @@ -101,7 +100,7 @@ public String getPnfsDir() { /** * Get the location of the dCap door. - * + * * @return The location of the dCap door of the form hostname:port */ public String getdCapDoor() { @@ -110,7 +109,7 @@ public String getdCapDoor() { /** * Get the interface to use for receiving callback connections from pools. - * + * * @return The IP address of the interface to use for callback connections. */ public String getInterface() { @@ -139,6 +138,7 @@ public String getInterface() { /** * Get the time to wait for a pool to respond. + * * @return The pool timeout. */ public int getPoolTimeout() { @@ -147,6 +147,7 @@ public int getPoolTimeout() { /** * Set the time to wait for a pool to respond. + * * @param timeout The pool timeout in seconds. */ public void setPoolTimeout(int timeout) { diff --git a/src/edu/caltech/hep/dcapj/PnfsUtil.java b/src/edu/caltech/hep/dcapj/PnfsUtil.java index 2f67d13..0933025 100644 --- a/src/edu/caltech/hep/dcapj/PnfsUtil.java +++ b/src/edu/caltech/hep/dcapj/PnfsUtil.java @@ -1,7 +1,7 @@ /** * A utility class for extracting useful information * from the pnfs filesystem by reading pnfs files. - * + * * @author Faisal Khan * @version 0.1 */ @@ -9,48 +9,47 @@ package edu.caltech.hep.dcapj; import java.io.*; -import java.util.*; +import java.util.Vector; public class PnfsUtil { - private static String PATH_SEPARATOR = "/"; - /** Enables/disables debug mode. */ public static boolean DEBUG = false; + private static String PATH_SEPARATOR = "/"; /** * PNFS ID is extracted by reading .(id)(filename) under the parent directory of * the given path. */ public static String getPnfsID(String path) throws FileNotFoundException, - IOException { + IOException { File pnfsPath = new File(path); - File dir = pnfsPath.getParentFile(); + File dir = pnfsPath.getParentFile(); - if (!dir.isDirectory()) - throw new FileNotFoundException( - "Invalid pnfs path: Unable to extract parent directory"); + if (!dir.isDirectory()) + throw new FileNotFoundException( + "Invalid pnfs path: Unable to extract parent directory"); - //fomat = /path-to-pnfs file-parents directory/.(id)(pnfs-file-name) - String pnfslayer = dir.getPath() + "" + PATH_SEPARATOR + ".(id)(" - + pnfsPath.getName() + ")"; + //fomat = /path-to-pnfs file-parents directory/.(id)(pnfs-file-name) + String pnfslayer = dir.getPath() + "" + PATH_SEPARATOR + ".(id)(" + + pnfsPath.getName() + ")"; - if (DEBUG) - System.err.println("Reading pnfslayer's file: " + pnfslayer); + if (DEBUG) + System.err.println("Reading pnfslayer's file: " + pnfslayer); - FileInputStream fis = new FileInputStream(pnfslayer); - byte idbytes[] = new byte[1024]; - int rcount = fis.read(idbytes); + FileInputStream fis = new FileInputStream(pnfslayer); + byte idbytes[] = new byte[1024]; + int rcount = fis.read(idbytes); - if (DEBUG) - System.out.println("Number of bytes read = " + rcount); + if (DEBUG) + System.out.println("Number of bytes read = " + rcount); - if (rcount < 0) - throw new IOException("Couldn't read pnfslayer file " + pnfslayer); + if (rcount < 0) + throw new IOException("Couldn't read pnfslayer file " + pnfslayer); - return new String(idbytes).trim(); - } + return new String(idbytes).trim(); + } /** * Check whether a given path is a valid PNFS path. @@ -58,38 +57,38 @@ public static String getPnfsID(String path) throws FileNotFoundException, * @return true if the path is a valid PNFS path, false otherwise. */ public static boolean isPnfs(String path) { - File pnfsPath = new File(path); - File dir = pnfsPath.getParentFile(); - - //file/path should exist as the remaining test are based on directory. - if (!pnfsPath.exists()) { - if (DEBUG) - System.out.println("Requested path or file doesn't exist. " + pnfsPath); - - return false; - } - - if (!dir.isDirectory()) { - if (DEBUG) - System.out - .println("Invalid pnfs path: Unable to extract parent directory"); - return false; - } - - //format = /path-to-pnfs-file's parent direcoty/.(get)(cursor) - String pnfslayer = dir.getPath() + "" + PATH_SEPARATOR - + ".(get)(cursor)"; - - if (DEBUG) - System.err.println("Checking pnfslayer's file: " + pnfslayer); - - File fpnfslayer = new File(pnfslayer); - //if the pnfs layer file exists and we can read it - //it means it is pnfs file system. - if (fpnfslayer.exists() && fpnfslayer.canRead()) - return true; - else - return false; + File pnfsPath = new File(path); + File dir = pnfsPath.getParentFile(); + + //file/path should exist as the remaining test are based on directory. + if (!pnfsPath.exists()) { + if (DEBUG) + System.out.println("Requested path or file doesn't exist. " + pnfsPath); + + return false; + } + + if (!dir.isDirectory()) { + if (DEBUG) + System.out + .println("Invalid pnfs path: Unable to extract parent directory"); + return false; + } + + //format = /path-to-pnfs-file's parent direcoty/.(get)(cursor) + String pnfslayer = dir.getPath() + "" + PATH_SEPARATOR + + ".(get)(cursor)"; + + if (DEBUG) + System.err.println("Checking pnfslayer's file: " + pnfslayer); + + File fpnfslayer = new File(pnfslayer); + //if the pnfs layer file exists and we can read it + //it means it is pnfs file system. + if (fpnfslayer.exists() && fpnfslayer.canRead()) + return true; + else + return false; } /** @@ -100,56 +99,56 @@ public static boolean isPnfs(String path) { * @throws IOException If there was an error getting the dCap doors. */ public static String[] getdCapDoors(String path) - throws FileNotFoundException, IOException { - - File pnfsfile = new File(path); - File dir = pnfsfile.getParentFile(); + throws FileNotFoundException, IOException { + + File pnfsfile = new File(path); + File dir = pnfsfile.getParentFile(); - if (DEBUG) { - System.out.println("Querying dCache's door for pnfs path " + path); - System.out.println("Checking if it is pnfs path"); + if (DEBUG) { + System.out.println("Querying dCache's door for pnfs path " + path); + System.out.println("Checking if it is pnfs path"); - } + } - //path should exist and it should belong to pnfs layer. - if (!isPnfs(path)) { + //path should exist and it should belong to pnfs layer. + if (!isPnfs(path)) { - if (DEBUG) - System.out.println("Not a valid pnfs path : " + path); + if (DEBUG) + System.out.println("Not a valid pnfs path : " + path); - return null; - } + return null; + } - //It's a pnfs path, going to read the conf file for getting list of - //dcache's file. - String pnfslayer = dir.getPath() + PATH_SEPARATOR - + ".(config)(dCache)/dcache.conf"; + //It's a pnfs path, going to read the conf file for getting list of + //dcache's file. + String pnfslayer = dir.getPath() + PATH_SEPARATOR + + ".(config)(dCache)/dcache.conf"; - if (DEBUG) - System.out.println("Reading pnfs layer file " + pnfslayer); + if (DEBUG) + System.out.println("Reading pnfs layer file " + pnfslayer); - File fpnfslayer = new File(pnfslayer); - if (!fpnfslayer.exists() || !fpnfslayer.canRead()) { - throw new IOException("Unable to read pnfs layer file " + pnfslayer); - } + File fpnfslayer = new File(pnfslayer); + if (!fpnfslayer.exists() || !fpnfslayer.canRead()) { + throw new IOException("Unable to read pnfs layer file " + pnfslayer); + } - BufferedReader buffreader = new BufferedReader( - new FileReader(pnfslayer)); + BufferedReader buffreader = new BufferedReader( + new FileReader(pnfslayer)); - String line = null; - Vector hosts = new Vector(); - while ((line = buffreader.readLine()) != null) { - hosts.add(line); + String line = null; + Vector hosts = new Vector(); + while ((line = buffreader.readLine()) != null) { + hosts.add(line); - if (DEBUG) - System.out.println("Host = " + line); - } + if (DEBUG) + System.out.println("Host = " + line); + } - if (DEBUG) - System.out - .println("Number of doors available are " + hosts.size()); + if (DEBUG) + System.out + .println("Number of doors available are " + hosts.size()); - return hosts.toArray(new String[0]); + return hosts.toArray(new String[0]); } /** @@ -160,31 +159,31 @@ public static String[] getdCapDoors(String path) * @throws IOException If there was an error getting the dCap doors. */ public static String getdCapDoor(String pnfspath) - throws FileNotFoundException, IOException { - - String selectedHost = null; - - String[] hosts = getdCapDoors(pnfspath); - //First get the list of all available doors and then - //pick one at random. Almost the same mechanism as adopted - //by dcap. - if (hosts.length == 1) - selectedHost = hosts[0]; - else if (hosts.length > 1) { - //more than 1 hosts. Let's pick one at random - int random = (int) Math.random() * hosts.length; - if (random > 0 && random < hosts.length) { - if (DEBUG) - System.out.println("Picking " + hosts[random] - + " at random "); - selectedHost = hosts[random]; - - } else { - if (DEBUG) //this shouldn't happen - System.out.println("dCap host selection failed; unable to precicesly calculate" + - " random number."); - } - } - return selectedHost; + throws FileNotFoundException, IOException { + + String selectedHost = null; + + String[] hosts = getdCapDoors(pnfspath); + //First get the list of all available doors and then + //pick one at random. Almost the same mechanism as adopted + //by dcap. + if (hosts.length == 1) + selectedHost = hosts[0]; + else if (hosts.length > 1) { + //more than 1 hosts. Let's pick one at random + int random = (int) Math.random() * hosts.length; + if (random > 0 && random < hosts.length) { + if (DEBUG) + System.out.println("Picking " + hosts[random] + + " at random "); + selectedHost = hosts[random]; + + } else { + if (DEBUG) //this shouldn't happen + System.out.println("dCap host selection failed; unable to precicesly calculate" + + " random number."); + } + } + return selectedHost; } } diff --git a/src/edu/caltech/hep/dcapj/dCacheFile.java b/src/edu/caltech/hep/dcapj/dCacheFile.java index 7bd18e4..a13acfe 100644 --- a/src/edu/caltech/hep/dcapj/dCacheFile.java +++ b/src/edu/caltech/hep/dcapj/dCacheFile.java @@ -1,27 +1,16 @@ package edu.caltech.hep.dcapj; -import java.io.BufferedReader; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; +import edu.caltech.hep.dcapj.util.*; + +import java.io.*; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.Arrays; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; import java.util.logging.Level; import java.util.logging.Logger; -import edu.caltech.hep.dcapj.util.ControlCommandCallback; -import edu.caltech.hep.dcapj.util.ControlConnection; -import edu.caltech.hep.dcapj.util.DataConnectionCallback; -import edu.caltech.hep.dcapj.util.IOCallback; -import edu.caltech.hep.dcapj.util.InvalidConfigurationException; - /** * Main class that implements all the file-like behaviour for dCache files. *

    @@ -30,45 +19,34 @@ * {@link edu.caltech.hep.dcapj.io.dCacheFileOutputStream} and * {@link edu.caltech.hep.dcapj.io.dCacheFileInputStream}. To achieve NIO like * functionality, use {@link edu.caltech.hep.dcapj.nio.dCacheFileChannel}. - * + * * @author Kamran Soomro * @author Faisal Khan */ public class dCacheFile extends File implements DataConnectionCallback, ControlCommandCallback { + protected SocketChannel _clientChannel = null; private Logger _logger = Logger.getLogger(dCacheFile.class.getName()); - private boolean _connected = false; private boolean _poolReplied = false; private boolean _doorReplied = false; private ByteBuffer _commandBuffer = null; - private IOCallback _poolCallback = null; private ControlConnection _controlConnection = null; - - protected SocketChannel _clientChannel = null; - private String _pnfsID; private int _sessionID = -1; private int _commandID = -1; - - // / Represents the mode in which dCache files can be opened. - public enum Mode { - READ_ONLY, WRITE_ONLY - }; - private Mode _openMode; + ; private long _max_bytes = Long.MAX_VALUE; private boolean _writable = false; - private long _filesize; - private int _poolID = 0; private int _mode; - private SelectionKey _selectionKey; + private String[] _doorReply = null; /* * private int _uid; private int _guid; @@ -76,37 +54,30 @@ public enum Mode { * private long _atime; private long _mtime; private long _ctime; */ - private String[] _doorReply = null; - public dCacheFile(String name, String openMode) throws FileNotFoundException, - IOException, InvalidConfigurationException { - this(name, openMode.equals("w")?Mode.WRITE_ONLY:Mode.READ_ONLY); + IOException, InvalidConfigurationException { + this(name, openMode.equals("w") ? Mode.WRITE_ONLY : Mode.READ_ONLY); } - + /** * Create a dCacheFile. - * - * @param name - * Path of the file to open. The path should be absolute. - * @param openMode - * Open the file in read or write mode. - * @throws FileNotFoundException - * If the file is not found - * @throws IOException - * If there was an error opening/creating the file - * @throws InvalidConfigurationException - * If the $DCAPJ_CONFIG_FILE is in an invalid - * format + * + * @param name Path of the file to open. The path should be absolute. + * @param openMode Open the file in read or write mode. + * @throws FileNotFoundException If the file is not found + * @throws IOException If there was an error opening/creating the file + * @throws InvalidConfigurationException If the $DCAPJ_CONFIG_FILE is in an invalid + * format */ public dCacheFile(String name, Mode openMode) throws FileNotFoundException, - IOException, InvalidConfigurationException { + IOException, InvalidConfigurationException { super(name); // if dcap layer is not initialized, don't do anything if (!dCapLayer.isInitialized()) { throw new InvalidConfigurationException( "dCap layer should be initialized by " - + " making a call to dCapLayer.initialze()"); + + " making a call to dCapLayer.initialze()"); } // We can only handle files that are part of dCache. @@ -144,9 +115,8 @@ public dCacheFile(String name, Mode openMode) throws FileNotFoundException, } // File exists and we want to read _pnfsID = PnfsUtil.getPnfsID(super.getAbsolutePath()); // getPnfsID(); - if (_openMode == Mode.READ_ONLY) - { - this.open(); + if (_openMode == Mode.READ_ONLY) { + this.open(); this.tell(); _connected = true; getPoolID(); @@ -162,8 +132,8 @@ private void open() throws IOException { if (!result) { String emsg = "Open failed on file " + getName() - + "; Unable to register " + " pool call back for session " - + _sessionID; + + "; Unable to register " + " pool call back for session " + + _sessionID; _logger.severe(emsg); throw new IOException(emsg); } @@ -173,7 +143,7 @@ private void open() throws IOException { String ip = dCapLayer.getConfig().getInterface(); if (ip == null) { IOException ex = new IOException( - "Cannot get ip address for system."); + "Cannot get ip address for system."); _logger.severe(ex.getMessage()); _logger.throwing("dCacheFile", "open", ex); throw ex; @@ -181,8 +151,8 @@ private void open() throws IOException { _doorReplied = false; String openCommand = _sessionID + " " + (++_commandID) - + " client open " + _pnfsID + " " + mode + " " + ip + " " - + _poolCallback.getPort(); + + " client open " + _pnfsID + " " + mode + " " + ip + " " + + _poolCallback.getPort(); _controlConnection.sendCommand(openCommand); long t0 = System.currentTimeMillis(); @@ -207,9 +177,9 @@ private void open() throws IOException { } if (_doorReplied) { - String reply = ""; - for (String replyPart : _doorReply) - reply += replyPart + " "; + String reply = ""; + for (String replyPart : _doorReply) + reply += replyPart + " "; String errormsg = "dCapJ " + reply; _logger.severe(errormsg); throw new IOException(errormsg); @@ -219,7 +189,7 @@ private void open() throws IOException { _logger.fine("Waiting for a data call back connection"); long t1 = System.currentTimeMillis(); - while (!_poolReplied) { + while (!_poolReplied) { try { Thread.sleep(500); @@ -242,7 +212,7 @@ private void open() throws IOException { // Receive Mover HELLO BLOCK ByteBuffer commandBuffer = ByteBuffer.allocate(20); commandBuffer.limit(4); - + _clientChannel.read(commandBuffer); commandBuffer.rewind(); @@ -263,49 +233,23 @@ private void open() throws IOException { + ", nBytes = " + nBytes); } - // TODO: Let's see what we can do for stat - /* - * private void stat() throws IOException { _poolOut.writeInt(4); - * _poolOut.writeInt(10); // Send STATUS command - * - * int nBytes = _poolIn.readInt() - 12; // Should be 12 int ack = - * _poolIn.readInt(); int cmdCode = _poolIn.readInt(); int success = - * _poolIn.readInt(); - * - * if (success != 0) // Means command failed { String detail = - * _poolIn.readUTF(); _logger.severe("Cannot get STATUS information for file - * [nBytes = " + nBytes + ", ack = " + ack + ", cmdCode = " + cmdCode + ", - * success = " + success + "]\n" + "Pool replied: " + detail); - * - * IOException ex = new IOException(detail); _logger.throwing("dCacheFile", - * "stat", ex); throw ex; } // Else if command successful _mode = - * _poolIn.readInt(); int nLinks = _poolIn.readInt(); - * - * _uid = _poolIn.readInt(); _guid = _poolIn.readInt(); _filesize = - * _poolIn.readLong(); _atime = _poolIn.readLong(); _mtime = - * _poolIn.readLong(); _ctime = _poolIn.readLong(); } - */ - /** * Set the cursor to the specified offset. This method is only available if * the file is opened for reading. - * * - * @param offset - * The offset within the file to set the cursor to - * @param relative - * If true, the offset is calculated from the current position - * within the file, otherwise it is calculated as absolute + * * + * + * @param offset The offset within the file to set the cursor to + * @param relative If true, the offset is calculated from the current position + * within the file, otherwise it is calculated as absolute * @return The new position - * @throws IOException - * If there was an error setting the cursor + * @throws IOException If there was an error setting the cursor */ public long seek(long offset, boolean relative) throws IOException { - if (_openMode == Mode.WRITE_ONLY) - { - IOException ex = new IOException("Cannot seek in WRITE mode."); - _logger.throwing("dCacheFile", "seek", ex); - } - + if (_openMode == Mode.WRITE_ONLY) { + IOException ex = new IOException("Cannot seek in WRITE mode."); + _logger.throwing("dCacheFile", "seek", ex); + } + _logger.fine("Sending SEEK command to pool [offset = " + offset + ", relative = " + relative + "]"); @@ -367,23 +311,44 @@ else if (offset < 0) // From end of file return _commandBuffer.getLong(); } + // TODO: Let's see what we can do for stat + /* + * private void stat() throws IOException { _poolOut.writeInt(4); + * _poolOut.writeInt(10); // Send STATUS command + * + * int nBytes = _poolIn.readInt() - 12; // Should be 12 int ack = + * _poolIn.readInt(); int cmdCode = _poolIn.readInt(); int success = + * _poolIn.readInt(); + * + * if (success != 0) // Means command failed { String detail = + * _poolIn.readUTF(); _logger.severe("Cannot get STATUS information for file + * [nBytes = " + nBytes + ", ack = " + ack + ", cmdCode = " + cmdCode + ", + * success = " + success + "]\n" + "Pool replied: " + detail); + * + * IOException ex = new IOException(detail); _logger.throwing("dCacheFile", + * "stat", ex); throw ex; } // Else if command successful _mode = + * _poolIn.readInt(); int nLinks = _poolIn.readInt(); + * + * _uid = _poolIn.readInt(); _guid = _poolIn.readInt(); _filesize = + * _poolIn.readLong(); _atime = _poolIn.readLong(); _mtime = + * _poolIn.readLong(); _ctime = _poolIn.readLong(); } + */ + /** * Get the current position of the cursor. *

    - * Only available if the file is opened for reading. - * + * Only available if the file is opened for reading. + * * @return The current position of the cursor in the file - * @throws IOException - * If there was an error getting the cursor + * @throws IOException If there was an error getting the cursor */ public long tell() throws IOException { - if (_openMode == Mode.WRITE_ONLY) - { - IOException ex = new IOException("Cannot get cursor position in WRITE mode"); - _logger.throwing("dCacheFiles", "tell", ex); - throw ex; - } - + if (_openMode == Mode.WRITE_ONLY) { + IOException ex = new IOException("Cannot get cursor position in WRITE mode"); + _logger.throwing("dCacheFiles", "tell", ex); + throw ex; + } + _logger.fine("Sending LOCATE command to pool"); ByteBuffer _commandBuffer = ByteBuffer.allocate(30); @@ -437,14 +402,11 @@ public long tell() throws IOException { /** * Read from the file. - * - * @param bytes - * Fill the buffer bytes with bytes from the file - * @param off - * The offset within the file to read from + * + * @param bytes Fill the buffer bytes with bytes from the file + * @param off The offset within the file to read from * @return The number of bytes successfully read - * @throws IOException - * If there was an error reading the file + * @throws IOException If there was an error reading the file */ public int read(ByteBuffer bytes, long off) throws IOException { if (_openMode != Mode.READ_ONLY) { @@ -480,7 +442,7 @@ public int read(ByteBuffer bytes, long off) throws IOException { _commandBuffer.rewind(); int num_bytes = 0; while (num_bytes < 12) - num_bytes += _commandBuffer.getInt(); // Should be 12 + num_bytes += _commandBuffer.getInt(); // Should be 12 _commandBuffer.rewind(); _commandBuffer.limit(num_bytes); @@ -547,13 +509,13 @@ public int read(ByteBuffer bytes, long off) throws IOException { while (restPacket > 0) { int block = restPacket; - for (int rest = block; rest > 0;) { + for (int rest = block; rest > 0; ) { bytes.position(position); bytes.limit(position + rest); int rc = _clientChannel.read(bytes); if (rc < 0) throw new IOException( - "Read operation terminted prematurely"); + "Read operation terminted prematurely"); rest -= rc; position += rc; @@ -591,7 +553,6 @@ public int read(ByteBuffer bytes, long off) throws IOException { _logger.fine("Read " + bytes_read + " bytes from " + super.getName()); return (bytes_read == 0 ? -1 : bytes_read); } - /** * Not implemented @@ -600,52 +561,47 @@ public int read(byte bytes[]) throws IOException { // return read(bytes, 0); throw new IOException("Not Implemented Yet"); } - + /** * Get the pool ID of the file that the pool is coming from. - * + * * @return The pool ID */ public int getPoolID() { - if (!_connected) - { - try - { - open(); - tell(); - _connected = true; - } - catch(Exception ex) - { - ex.printStackTrace(); - } - } - - _logger.info("PoolID: " + _poolID); + if (!_connected) { + try { + open(); + tell(); + _connected = true; + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + _logger.info("PoolID: " + _poolID); return _poolID; } - - + /** * Prepare the file for writing. - * + * * @param position The position to start writing from * @return The remaining number of bytes that can be written, if limited by the pool. * @throws IOException If the writing fails */ private long makeWritable(long position) throws IOException { - _commandBuffer.clear(); + _commandBuffer.clear(); if (position == -1) { - _commandBuffer.putInt(4); - _commandBuffer.putInt(1); + _commandBuffer.putInt(4); + _commandBuffer.putInt(1); } else { - _commandBuffer.putInt(16); - _commandBuffer.putInt(12); - _commandBuffer.putLong(position); - _commandBuffer.putInt(0); + _commandBuffer.putInt(16); + _commandBuffer.putInt(12); + _commandBuffer.putLong(position); + _commandBuffer.putInt(0); } _commandBuffer.flip(); @@ -657,7 +613,7 @@ private long makeWritable(long position) throws IOException { _commandBuffer.clear(); int bytes_read = 0; while (bytes_read < 12) - bytes_read += _clientChannel.read(_commandBuffer); + bytes_read += _clientChannel.read(_commandBuffer); _commandBuffer.flip(); int num_bytes = _commandBuffer.getInt(); @@ -683,7 +639,7 @@ private long makeWritable(long position) throws IOException { if (num_bytes != 12) { // Server sends extra parameters if (num_bytes == 24) { - _commandBuffer.position(_commandBuffer.position() + 4); + _commandBuffer.position(_commandBuffer.position() + 4); _max_bytes = _commandBuffer.getLong(); _logger.fine("WRITE request succeeded for " + super.getName() + ". Server limited write size. [" + "num_bytes = " @@ -691,7 +647,7 @@ private long makeWritable(long position) throws IOException { + cmdCode + ", success = " + success + ", max_bytes = " + _max_bytes + "]"); } else if (num_bytes == 16) { - _commandBuffer.position(_commandBuffer.position() + 4); + _commandBuffer.position(_commandBuffer.position() + 4); _logger.fine("WRITE request succeeded for " + super.getName() + ". Server replied EXPECT_NEW_CONNECTION. [" @@ -701,42 +657,38 @@ private long makeWritable(long position) throws IOException { } } else { _logger - .fine("WRITE request succeeded for " + super.getName() - + " [" + "num_bytes = " + num_bytes + ", ack = " - + ack + ", cmdCode = " + cmdCode + ", success = " - + success + "]"); + .fine("WRITE request succeeded for " + super.getName() + + " [" + "num_bytes = " + num_bytes + ", ack = " + + ack + ", cmdCode = " + cmdCode + ", success = " + + success + "]"); } _writable = true; return _max_bytes; } - /** * Write to the file. - * - * @param buffer - * Write buffer to the file - * @param position Start writing at position in the file + * + * @param buffer Write buffer to the file + * @param position Start writing at position in the file * @return The number of bytes written - * - * @throws IOException If the write operation failed + * @throws IOException If the write operation failed */ public int write(ByteBuffer buffer, long position) - throws IOException { - if (!_connected) - { - open(); - tell(); - _connected = true; - _logger.fine("PoolID: " + getPoolID()); - } - + throws IOException { + if (!_connected) { + open(); + tell(); + _connected = true; + _logger.fine("PoolID: " + getPoolID()); + } + try { if (_commandBuffer == null) - _commandBuffer = ByteBuffer.allocate(128); + _commandBuffer = ByteBuffer.allocate(128); else - _commandBuffer.clear(); + _commandBuffer.clear(); if (_openMode != Mode.WRITE_ONLY) { IOException ex = new IOException("File not opened for writing"); @@ -745,7 +697,7 @@ public int write(ByteBuffer buffer, long position) throw ex; } - if ( _max_bytes < buffer.remaining() ) { + if (_max_bytes < buffer.remaining()) { IOException ex = new IOException("Cannot write more than " + _max_bytes + " to " + super.getName()); _logger.throwing("dCacheFile", "write(byte[], int, int, long)", @@ -762,20 +714,20 @@ public int write(ByteBuffer buffer, long position) _commandBuffer.clear(); _commandBuffer.putInt(4); _commandBuffer.putInt(8); - - _commandBuffer.putInt(buffer.remaining()); - _logger.finest("Writing " + buffer.remaining() + " bytes to " + super.getName() + " starting..."); - _commandBuffer.flip(); - + _commandBuffer.putInt(buffer.remaining()); + _logger.finest("Writing " + buffer.remaining() + " bytes to " + super.getName() + " starting..."); + + _commandBuffer.flip(); + while (_commandBuffer.hasRemaining()) - _clientChannel.write(_commandBuffer); + _clientChannel.write(_commandBuffer); int bytes_written = 0; while (buffer.hasRemaining()) { bytes_written += _clientChannel.write(buffer); } - + _logger.finest("File was opened write only, telling pool transfer is complete"); finishedWriting(); @@ -790,16 +742,16 @@ public int write(ByteBuffer buffer, long position) } private void finishedWriting() throws IOException { - _commandBuffer.clear(); - - _commandBuffer.putInt(-1); - _commandBuffer.flip(); - _clientChannel.write(_commandBuffer); - _commandBuffer.clear(); - - int num_bytes = 0; - while (num_bytes < 12) - num_bytes += _clientChannel.read(_commandBuffer); + _commandBuffer.clear(); + + _commandBuffer.putInt(-1); + _commandBuffer.flip(); + _clientChannel.write(_commandBuffer); + _commandBuffer.clear(); + + int num_bytes = 0; + while (num_bytes < 12) + num_bytes += _clientChannel.read(_commandBuffer); _logger.finest("Bytes read: " + num_bytes); _commandBuffer.flip(); num_bytes = _commandBuffer.getInt(); @@ -827,16 +779,11 @@ private void finishedWriting() throws IOException { } } - -// public int write(byte b[]) throws IOException { -// return write(b, 0, 0, -1); -// } - -/** - * Close the file - * - * @throws IOException If there was an error closing the file - */ + /** + * Close the file + * + * @throws IOException If there was an error closing the file + */ public void close() throws IOException { ByteBuffer _commandBuffer = ByteBuffer.allocate(30); _logger.info("Close on " + super.getName()); @@ -857,7 +804,7 @@ public void close() throws IOException { _commandBuffer.limit(16); int numread = 0; while (numread < 16) - numread += _clientChannel.read(_commandBuffer); + numread += _clientChannel.read(_commandBuffer); if (numread >= 16) { _commandBuffer.rewind(); @@ -904,15 +851,19 @@ public void close() throws IOException { } +// public int write(byte b[]) throws IOException { +// return write(b, 0, 0, -1); +// } + /** * Get length of the file - * + * * @return The length of the file in bytes */ public long length() { return _filesize; } - + /** * Not Implemented. */ @@ -920,35 +871,31 @@ public long length() { public int available() throws IOException { return Integer.MAX_VALUE; } - + /** * Get mode in which file was opened + * * @return The file mode */ public Mode mode() { return _openMode; } - protected void finalize() throws Throwable { close(); super.finalize(); } - public void handleStreams(DataInputStream dataIn, DataOutputStream dataOut, - String host, SocketChannel client) { + String host, SocketChannel client) { _clientChannel = client; - try - { - _clientChannel.socket().setSendBufferSize(800000); - } - catch (java.net.SocketException ex) - { - + try { + _clientChannel.socket().setSendBufferSize(800000); + } catch (java.net.SocketException ex) { + } - + _logger.info("Data connection callback for " + super.getAbsolutePath()); if (host != null) @@ -961,4 +908,9 @@ public void handleDoorCommand(String input[]) { _doorReply = input; _doorReplied = true; } + + // / Represents the mode in which dCache files can be opened. + public enum Mode { + READ_ONLY, WRITE_ONLY + } } diff --git a/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java b/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java index af2d33c..ecf8399 100644 --- a/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java +++ b/src/edu/caltech/hep/dcapj/dCacheFileChannelProviderFactory.java @@ -3,22 +3,22 @@ */ package edu.caltech.hep.dcapj; -import java.io.File; -import java.io.IOException; -import java.nio.channels.FileChannel; - import edu.caltech.hep.dcapj.util.InvalidConfigurationException; import lia.util.net.common.FDTCloseable; import lia.util.net.common.FileChannelProvider; import lia.util.net.common.FileChannelProviderFactory; -//import lia.util.net.copy.FDTCoordinatorSession; import lia.util.net.copy.FDTReaderSession; import lia.util.net.copy.FDTWriterSession; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; + +//import lia.util.net.copy.FDTCoordinatorSession; + /** - * * Created to remove dependencies inside FDT core - * + * * @author ramiro */ public class dCacheFileChannelProviderFactory implements FileChannelProviderFactory, FDTCloseable { @@ -34,7 +34,7 @@ public dCacheFileChannelProviderFactory() throws Exception { this.writerFileChannelProvider = new dCacheWriterFileChannelProvider(); this.coordinatorChannelProvider = new dCacheCoordinatorChannelProvider(); } - + public FileChannelProvider newReaderFileChannelProvider(FDTReaderSession readerSession) { return readerFileChannelProvider; } @@ -51,7 +51,7 @@ public FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerS public boolean close(String downMessage, Throwable downCause) { try { dCapLayer.close(); - }catch(Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } return true; @@ -66,83 +66,83 @@ private static final class dCacheReaderFileChannelProvider implements FileChanne public File getFile(String fName) throws IOException { try { return new edu.caltech.hep.dcapj.dCacheFile(fName, edu.caltech.hep.dcapj.dCacheFile.Mode.READ_ONLY); - }catch(InvalidConfigurationException ice) { + } catch (InvalidConfigurationException ice) { throw new IOException(ice); } - } + } public int getPartitionID(File dCacheFile) throws IOException { - if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { - return ((edu.caltech.hep.dcapj.dCacheFile)dCacheFile).getPoolID(); + if (dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { + return ((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getPoolID(); } throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); } public FileChannel getFileChannel(File dCacheFile, String openMode) throws IOException { - if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile ) { + if (dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { try { return new edu.caltech.hep.dcapj.io.dCacheFileInputStream((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getChannel(); - }catch(Exception ex) { + } catch (Exception ex) { throw new IOException(ex); } } throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); } - } + } private static final class dCacheWriterFileChannelProvider implements FileChannelProvider { public File getFile(String fName) throws IOException { try { return new edu.caltech.hep.dcapj.dCacheFile(fName, edu.caltech.hep.dcapj.dCacheFile.Mode.WRITE_ONLY); - }catch(InvalidConfigurationException ice) { + } catch (InvalidConfigurationException ice) { throw new IOException(ice); } - } + } public int getPartitionID(File dCacheFile) throws IOException { - if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { - return ((edu.caltech.hep.dcapj.dCacheFile)dCacheFile).getPoolID(); + if (dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { + return ((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getPoolID(); } throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); } public FileChannel getFileChannel(File dCacheFile, String openMode) throws IOException { - if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile ) { + if (dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { try { return new edu.caltech.hep.dcapj.io.dCacheFileOutputStream((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getChannel(); - }catch(Exception ex) { + } catch (Exception ex) { throw new IOException(ex); } } throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); } - } + } private static final class dCacheCoordinatorChannelProvider implements FileChannelProvider { public File getFile(String fName) throws IOException { try { return new edu.caltech.hep.dcapj.dCacheFile(fName, edu.caltech.hep.dcapj.dCacheFile.Mode.WRITE_ONLY); - }catch(InvalidConfigurationException ice) { + } catch (InvalidConfigurationException ice) { throw new IOException(ice); } } public int getPartitionID(File dCacheFile) throws IOException { - if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { - return ((edu.caltech.hep.dcapj.dCacheFile)dCacheFile).getPoolID(); + if (dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { + return ((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getPoolID(); } throw new IOException("File: " + dCacheFile + " is not an edu.caltech.hep.dcapj.dCacheFile object"); } public FileChannel getFileChannel(File dCacheFile, String openMode) throws IOException { - if(dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile ) { + if (dCacheFile instanceof edu.caltech.hep.dcapj.dCacheFile) { try { return new edu.caltech.hep.dcapj.io.dCacheFileOutputStream((edu.caltech.hep.dcapj.dCacheFile) dCacheFile).getChannel(); - }catch(Exception ex) { + } catch (Exception ex) { throw new IOException(ex); } } diff --git a/src/edu/caltech/hep/dcapj/dCapLayer.java b/src/edu/caltech/hep/dcapj/dCapLayer.java index 8cc7e10..ac5f18a 100644 --- a/src/edu/caltech/hep/dcapj/dCapLayer.java +++ b/src/edu/caltech/hep/dcapj/dCapLayer.java @@ -1,42 +1,47 @@ package edu.caltech.hep.dcapj; -import java.util.concurrent.atomic.AtomicBoolean; - -import edu.caltech.hep.dcapj.util.IOCallback; import edu.caltech.hep.dcapj.util.ControlConnection; +import edu.caltech.hep.dcapj.util.IOCallback; /** * This class should be called by the application before starting any IO * operation using dcapJ protocol - * + * * @author Faisal Khan */ public class dCapLayer { - /** guards _initialized flag */ + /** + * guards _initialized flag + */ private static final Object initializedLock = new Object(); - /** The IOCallback object that handles the mapping between session IDs and sessions. */ + /** + * The IOCallback object that handles the mapping between session IDs and sessions. + */ static IOCallback _dataConnectionCallback = null; - /** The ControlConnection object. */ + /** + * The ControlConnection object. + */ static ControlConnection _controlConnection = null; - /** The Config object. */ + /** + * The Config object. + */ static Config _conf = null; - + private static volatile boolean _isInitialized = false; - + /** * Initialize the library. - * - * @throws Exception - * If an error occurred + * + * @throws Exception If an error occurred */ public static void initialize() throws Exception { - synchronized(initializedLock) { + synchronized (initializedLock) { if (!_isInitialized) { _conf = new Config(); _dataConnectionCallback = new IOCallback(); @@ -47,13 +52,14 @@ public static void initialize() throws Exception { } public static final boolean isInitialized() { - synchronized(initializedLock) { + synchronized (initializedLock) { return _isInitialized; } } - + /** * Get the IOCallback object. + * * @return The IOCallback object. */ public static IOCallback getDataConnectionCallback() { @@ -62,6 +68,7 @@ public static IOCallback getDataConnectionCallback() { /** * Gets the ControlConnection object. + * * @return The ControlConnection object. */ public static ControlConnection getControlConnection() { @@ -70,6 +77,7 @@ public static ControlConnection getControlConnection() { /** * Gets the Config object. + * * @return The Config object. */ public static Config getConfig() { @@ -78,7 +86,6 @@ public static Config getConfig() { /** * Close the library. - * */ public static void close() { _controlConnection.stop(); diff --git a/src/edu/caltech/hep/dcapj/io/dCacheFileInputStream.java b/src/edu/caltech/hep/dcapj/io/dCacheFileInputStream.java index e285199..3e53ca4 100644 --- a/src/edu/caltech/hep/dcapj/io/dCacheFileInputStream.java +++ b/src/edu/caltech/hep/dcapj/io/dCacheFileInputStream.java @@ -1,18 +1,16 @@ package edu.caltech.hep.dcapj.io; -import java.io.*; -import edu.caltech.hep.dcapj.*; -import edu.caltech.hep.dcapj.nio.*; import edu.caltech.hep.dcapj.dCacheFile; import edu.caltech.hep.dcapj.nio.dCacheFileChannel; +import java.io.FileInputStream; +import java.io.IOException; import java.nio.channels.FileChannel; /** * Use this class to read from a dCache file. - * + * * @author Kamran Soomro - * */ public class dCacheFileInputStream extends FileInputStream { private dCacheFile _file = null; @@ -20,11 +18,9 @@ public class dCacheFileInputStream extends FileInputStream { /** * Create a dCacheFileInputStream object from the specified dCacheFile * object. - * - * @param file - * The dCacheFile to use as the underlying file - * @throws java.lang.Exception - * If an error occurred + * + * @param file The dCacheFile to use as the underlying file + * @throws java.lang.Exception If an error occurred */ public dCacheFileInputStream(dCacheFile file) throws java.lang.Exception { super(file); @@ -34,11 +30,9 @@ public dCacheFileInputStream(dCacheFile file) throws java.lang.Exception { /** * Open a dCache file for reading. - * - * @param file - * The Pnfs path of the file to open - * @throws Exception - * If an occurred + * + * @param file The Pnfs path of the file to open + * @throws Exception If an occurred */ public dCacheFileInputStream(String file) throws Exception { this(new dCacheFile(file, dCacheFile.Mode.READ_ONLY)); @@ -46,7 +40,7 @@ public dCacheFileInputStream(String file) throws Exception { /** * Return an estimate of the number of bytes available. - * + * * @return The estimated number of bytes available for reading */ public int available() throws IOException { @@ -57,9 +51,8 @@ public int available() throws IOException { * Reads up to bytes.length bytes of data from this input * stream into an array of bytes. This method blocks until some input is * available. - * - * @throws IOException - * If an error reading the file occurs + * + * @throws IOException If an error reading the file occurs */ public int read(byte bytes[]) throws IOException { return _file.read(bytes); @@ -68,7 +61,6 @@ public int read(byte bytes[]) throws IOException { /** * Reads up to bytes.length bytes of data from this input * stream into an array of bytes. len is ignored. - * */ public int read(byte bytes[], int off, int len) throws IOException { // return _file.read(bytes, off); diff --git a/src/edu/caltech/hep/dcapj/io/dCacheFileOutputStream.java b/src/edu/caltech/hep/dcapj/io/dCacheFileOutputStream.java index 2f1491d..53a24c0 100644 --- a/src/edu/caltech/hep/dcapj/io/dCacheFileOutputStream.java +++ b/src/edu/caltech/hep/dcapj/io/dCacheFileOutputStream.java @@ -1,16 +1,16 @@ package edu.caltech.hep.dcapj.io; -import java.io.*; -import java.nio.*; -import java.nio.channels.*; -import edu.caltech.hep.dcapj.*; -import edu.caltech.hep.dcapj.nio.*; import edu.caltech.hep.dcapj.dCacheFile; import edu.caltech.hep.dcapj.nio.dCacheFileChannel; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + /** * Use this file to open a dCache file for writing. - * + * * @author Kamran Soomro */ public class dCacheFileOutputStream extends FileOutputStream { @@ -19,9 +19,8 @@ public class dCacheFileOutputStream extends FileOutputStream { /** * Create a dCacheFileOutputStream object using the specified file as the * underlying {@link dCacheFile}. - * - * @throws Exception - * If the file already exists or an error occurred + * + * @throws Exception If the file already exists or an error occurred */ public dCacheFileOutputStream(dCacheFile file) throws Exception { super(file); @@ -31,9 +30,8 @@ public dCacheFileOutputStream(dCacheFile file) throws Exception { /** * Open the specified file for writing. - * - * @throws Exception - * If the file already exists or an error occurred + * + * @throws Exception If the file already exists or an error occurred */ public dCacheFileOutputStream(String file) throws Exception { this(new dCacheFile(file, dCacheFile.Mode.WRITE_ONLY)); @@ -42,32 +40,29 @@ public dCacheFileOutputStream(String file) throws Exception { /** * Writes bytes.length bytes from the specified byte array to * this file output stream. - * - * @throws IOException - * If an error occurred + * + * @throws IOException If an error occurred */ public void write(byte bytes[]) throws IOException { - ByteBuffer buffer = ByteBuffer.wrap(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes); _file.write(buffer, -1); } /** * Writes len bytes from the specified byte array starting at * offset off to this file output stream. - * - * @throws IOException - * If an error occurred + * + * @throws IOException If an error occurred */ public void write(byte bytes[], int off, int len) throws IOException { - ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len); + ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len); _file.write(buffer, -1); } /** * Close this output stream. - * - * @throws IOException - * If an error occurred + * + * @throws IOException If an error occurred */ public void close() throws IOException { _file.close(); diff --git a/src/edu/caltech/hep/dcapj/nio/dCacheFileChannel.java b/src/edu/caltech/hep/dcapj/nio/dCacheFileChannel.java index 684d7cd..f7b9839 100644 --- a/src/edu/caltech/hep/dcapj/nio/dCacheFileChannel.java +++ b/src/edu/caltech/hep/dcapj/nio/dCacheFileChannel.java @@ -1,17 +1,22 @@ package edu.caltech.hep.dcapj.nio; -import java.io.*; -import java.nio.*; -import java.nio.channels.*; -import edu.caltech.hep.dcapj.*; +import edu.caltech.hep.dcapj.dCacheFile; import edu.caltech.hep.dcapj.io.dCacheFileInputStream; import edu.caltech.hep.dcapj.io.dCacheFileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + /** * This class implements the {@link java.nio.channels.FileChannel} API for a * {@link dCacheFile}. It is recommended to use this class to open a * {@link dCacheFile} instead of directly creating one. - * + * * @author kamran * @see java.nio.channels.FileChannel */ @@ -25,10 +30,9 @@ public class dCacheFileChannel extends FileChannel { /** * Create a new dCacheFileChannel object using a pre-exisiting * {@link dCacheFile}. - * - * @param file - * The dCacheFile object that will be used to create this - * dCacheFileChannel + * + * @param file The dCacheFile object that will be used to create this + * dCacheFileChannel */ public dCacheFileChannel(dCacheFile file, dCacheFileInputStream fileIn) { _file = file; @@ -87,16 +91,14 @@ public FileChannel position(long newPosition) throws IOException { /** * Read from the file. - * - * @param dst - * The ByteBuffer into which the read bytes will be stored. dCapJ + * + * @param dst The ByteBuffer into which the read bytes will be stored. dCapJ * will try to fill the remaining number of bytes in dst. If EOF * is encountered, the limit will be set to the number of bytes * successfully read. * @return The number of bytes successfully read. If EOF is encountered, -1 - * will be returned. - * @throws IOException - * If read operation was not successful + * will be returned. + * @throws IOException If read operation was not successful */ public int read(ByteBuffer dst) throws IOException { if (_file.mode() == dCacheFile.Mode.WRITE_ONLY) @@ -117,15 +119,12 @@ public int read(ByteBuffer dst) throws IOException { /** * Read from a specified position in the file without changing the location * of the file's cursor. - * - * @param dst - * The ByteBuffer into which the read bytes will be stored - * @param position - * The position in the file to read from + * + * @param dst The ByteBuffer into which the read bytes will be stored + * @param position The position in the file to read from * @return The number of bytes successfully read. -1 of EOF is encountered - * and no bytes are read - * @throws IOException - * If the read operation was unsuccessful + * and no bytes are read + * @throws IOException If the read operation was unsuccessful */ public int read(ByteBuffer dst, long position) throws IOException { if (_file.mode() == dCacheFile.Mode.WRITE_ONLY) @@ -142,15 +141,11 @@ public int read(ByteBuffer dst, long position) throws IOException { /** * Fill the range given by [i]offset[/i] and [i]length[/i] with bytes from * the file. - * - * @param dsts - * The array into which the bytes will be stored - * @param offset - * The offset of the ByteBuffer from which to start filling - * @param length - * The total number of ByteBuffers to fill - * @throws IOException - * If the read operation was unsuccessful + * + * @param dsts The array into which the bytes will be stored + * @param offset The offset of the ByteBuffer from which to start filling + * @param length The total number of ByteBuffers to fill + * @throws IOException If the read operation was unsuccessful */ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { @@ -165,10 +160,9 @@ public long read(ByteBuffer[] dsts, int offset, int length) /** * Get the file size. - * + * * @return The size of the file in bytes - * @throws IOException - * If the operation was unsuccessful + * @throws IOException If the operation was unsuccessful */ public long size() throws IOException { if (_file.mode() == dCacheFile.Mode.WRITE_ONLY) @@ -180,15 +174,11 @@ public long size() throws IOException { /** * Write to the file file from the ReadableByteChannel. - * - * @param src - * The channel to read from - * @param position - * The position within the file to write to - * @param count - * The number of bytes to read - * @throws IOException - * If the operation was unsuccessful + * + * @param src The channel to read from + * @param position The position within the file to write to + * @param count The number of bytes to read + * @throws IOException If the operation was unsuccessful */ public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { @@ -200,15 +190,11 @@ public long transferFrom(ReadableByteChannel src, long position, long count) /** * Read from the file into the WritableByteChannel. - * - * @param position - * The position in the file to read from - * @param count - * The number of bytes to be read from the file - * @param target - * The WritableByteChannel to write to - * @throws IOException - * If the operation was unsuccessful + * + * @param position The position in the file to read from + * @param count The number of bytes to be read from the file + * @param target The WritableByteChannel to write to + * @throws IOException If the operation was unsuccessful */ @Override public long transferTo(long position, long count, WritableByteChannel target) @@ -240,33 +226,28 @@ public FileLock tryLock(long position, long size, boolean shared) /** * Write to the file. - * - * @param src - * The ByteBuffer to write to the file + * + * @param src The ByteBuffer to write to the file * @return The number of bytes successfully written - * @throws IOException - * If the operation fails + * @throws IOException If the operation fails */ @Override public int write(ByteBuffer src) throws IOException { if (_file.mode() == dCacheFile.Mode.READ_ONLY) throw new IOException("Not in write mode"); - return _file.write(src, -1); + return _file.write(src, -1); } /** * Write to the file. *

    * position is ignored. The writing is performed sequentially. - * - * @param src - * The ByteBuffer to write - * @param position - * Ignored + * + * @param src The ByteBuffer to write + * @param position Ignored * @return The number of bytes successfully written - * @throws IOException - * If the operation fails + * @throws IOException If the operation fails * @see #write(ByteBuffer) */ @Override @@ -279,16 +260,12 @@ public int write(ByteBuffer src, long position) throws IOException { /** * Write from the array to the file. - * - * @param srcs - * The ByteBuffer array to write from - * @param offset - * The offset of the ByteBuffer in src to start writing from - * @param length - * The number ByteBuffers to write to the file + * + * @param srcs The ByteBuffer array to write from + * @param offset The offset of the ByteBuffer in src to start writing from + * @param length The number ByteBuffers to write to the file * @return The number of bytes successfully written to the file - * @throws IOException - * If the operation fails + * @throws IOException If the operation fails */ @Override public long write(ByteBuffer[] srcs, int offset, int length) @@ -307,6 +284,7 @@ public long write(ByteBuffer[] srcs, int offset, int length) /** * Close this channel and the underlying {@link dCacheFile}. + * * @throws IOException If there was an error trying to close the file */ protected void implCloseChannel() throws IOException { diff --git a/src/edu/caltech/hep/dcapj/test/Main.java b/src/edu/caltech/hep/dcapj/test/Main.java index 2e7424a..cc363b1 100644 --- a/src/edu/caltech/hep/dcapj/test/Main.java +++ b/src/edu/caltech/hep/dcapj/test/Main.java @@ -1,10 +1,10 @@ package edu.caltech.hep.dcapj.test; -import edu.caltech.hep.dcapj.io.*; -import edu.caltech.hep.dcapj.nio.*; -import edu.caltech.hep.dcapj.*; +import edu.caltech.hep.dcapj.dCapLayer; +import edu.caltech.hep.dcapj.io.dCacheFileOutputStream; +import edu.caltech.hep.dcapj.nio.dCacheFileChannel; -import java.nio.*; +import java.nio.ByteBuffer; public class Main { diff --git a/src/edu/caltech/hep/dcapj/test/Main2.java b/src/edu/caltech/hep/dcapj/test/Main2.java index d192f46..86a23a5 100644 --- a/src/edu/caltech/hep/dcapj/test/Main2.java +++ b/src/edu/caltech/hep/dcapj/test/Main2.java @@ -1,13 +1,11 @@ package edu.caltech.hep.dcapj.test; -import edu.caltech.hep.dcapj.io.*; -import edu.caltech.hep.dcapj.nio.*; -import edu.caltech.hep.dcapj.dCacheFile; import edu.caltech.hep.dcapj.dCapLayer; +import edu.caltech.hep.dcapj.io.dCacheFileInputStream; -import java.io.*; -import java.nio.*; -import java.nio.channels.*; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; public class Main2 { diff --git a/src/edu/caltech/hep/dcapj/test/Main3.java b/src/edu/caltech/hep/dcapj/test/Main3.java index 12f4bbf..49a720c 100644 --- a/src/edu/caltech/hep/dcapj/test/Main3.java +++ b/src/edu/caltech/hep/dcapj/test/Main3.java @@ -2,12 +2,13 @@ import edu.caltech.hep.dcapj.PnfsUtil; import edu.caltech.hep.dcapj.dCapLayer; -import edu.caltech.hep.dcapj.io.*; -import edu.caltech.hep.dcapj.nio.*; +import edu.caltech.hep.dcapj.io.dCacheFileInputStream; +import edu.caltech.hep.dcapj.io.dCacheFileOutputStream; -import java.io.*; -import java.nio.*; -import java.nio.channels.*; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; /** * Copy from dcache to filesystem or from filesystem to dache.. no other option @@ -30,6 +31,44 @@ public Main3(String src, String dest) { this.destination = dest; } + public static void main(String args[]) { + + // initialize the dcap layer + try { + dCapLayer.initialize(); + } catch (Exception e) { + e.printStackTrace(); + } + + int ioPairs = args.length / 2; // based on soruce, dest pair + + if (ioPairs == 0 || (ioPairs != 1 && ioPairs % 2 != 0)) { + System.out.println("Not enough source/dest pair! " + ioPairs); + dCapLayer.close(); + return; + } + + Main3 main3[] = new Main3[ioPairs]; + Thread ioThreads[] = new Thread[ioPairs]; + int count = 0; + + for (int i = 0; i < ioPairs; i++) { + main3[i] = new Main3(args[count++], args[count++]); + ioThreads[i] = new Thread(main3[i]); + ioThreads[i].start(); + } + + try { + + for (int i = 0; i < ioPairs; i++) + ioThreads[i].join(); + } catch (Exception e) { + e.printStackTrace(); + } + + dCapLayer.close(); + } + public void run() { doIO(); } @@ -111,42 +150,4 @@ private FileChannel[] getFileChannels() throws Exception { return fc; } - - public static void main(String args[]) { - - // initialize the dcap layer - try { - dCapLayer.initialize(); - } catch (Exception e) { - e.printStackTrace(); - } - - int ioPairs = args.length / 2; // based on soruce, dest pair - - if (ioPairs == 0 || (ioPairs != 1 && ioPairs % 2 != 0)) { - System.out.println("Not enough source/dest pair! " + ioPairs); - dCapLayer.close(); - return; - } - - Main3 main3[] = new Main3[ioPairs]; - Thread ioThreads[] = new Thread[ioPairs]; - int count = 0; - - for (int i = 0; i < ioPairs; i++) { - main3[i] = new Main3(args[count++], args[count++]); - ioThreads[i] = new Thread(main3[i]); - ioThreads[i].start(); - } - - try { - - for (int i = 0; i < ioPairs; i++) - ioThreads[i].join(); - } catch (Exception e) { - e.printStackTrace(); - } - - dCapLayer.close(); - } } diff --git a/src/edu/caltech/hep/dcapj/util/ControlConnection.java b/src/edu/caltech/hep/dcapj/util/ControlConnection.java index 4c3d4a8..e22a40a 100644 --- a/src/edu/caltech/hep/dcapj/util/ControlConnection.java +++ b/src/edu/caltech/hep/dcapj/util/ControlConnection.java @@ -1,26 +1,22 @@ package edu.caltech.hep.dcapj.util; import edu.caltech.hep.dcapj.Config; -import edu.caltech.hep.dcapj.dCapLayer; import edu.caltech.hep.dcapj.PnfsUtil; +import edu.caltech.hep.dcapj.dCapLayer; -import java.util.Hashtable; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; -import java.io.IOException; import java.net.Socket; +import java.util.Hashtable; import java.util.logging.Logger; public class ControlConnection implements Runnable { - protected Hashtable callbacks; - private static final Logger _logger = Logger .getLogger(ControlConnection.class.getName()); - + protected Hashtable callbacks; private Socket _client = null; private PrintWriter _clientOut = null; @@ -75,7 +71,7 @@ private void initialize() throws IOException, InvalidConfigurationException { if (tmp.length > 1) { ip = tmp[0]; try { - System.out.println(tmp[1]); + System.out.println(tmp[1]); port = Integer.parseInt(tmp[1]); } catch (NumberFormatException nfe) { _logger.severe("Illegal port number in dCap host address"); diff --git a/src/edu/caltech/hep/dcapj/util/DataConnectionCallback.java b/src/edu/caltech/hep/dcapj/util/DataConnectionCallback.java index f548f83..7fa4cff 100644 --- a/src/edu/caltech/hep/dcapj/util/DataConnectionCallback.java +++ b/src/edu/caltech/hep/dcapj/util/DataConnectionCallback.java @@ -1,10 +1,10 @@ package edu.caltech.hep.dcapj.util; -import java.nio.channels.SocketChannel; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.nio.channels.SocketChannel; public interface DataConnectionCallback { public void handleStreams(DataInputStream dataIn, DataOutputStream dataOut, - String host, SocketChannel client); + String host, SocketChannel client); } diff --git a/src/edu/caltech/hep/dcapj/util/IOCallback.java b/src/edu/caltech/hep/dcapj/util/IOCallback.java index 96280d0..2f28ef0 100644 --- a/src/edu/caltech/hep/dcapj/util/IOCallback.java +++ b/src/edu/caltech/hep/dcapj/util/IOCallback.java @@ -1,20 +1,18 @@ package edu.caltech.hep.dcapj.util; -import java.util.Hashtable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.net.Socket; +import java.util.Hashtable; import java.util.logging.Logger; public class IOCallback extends ServerNIO { - protected Hashtable callbacks; - private static final Logger logger = Logger.getLogger(IOCallback.class .getName()); + protected Hashtable callbacks; public IOCallback() throws IOException { callbacks = new Hashtable(); @@ -69,7 +67,7 @@ public void handleConnection(SocketChannel client) { * in-comming pool connection */ public boolean registerCallback(int sessionID, - DataConnectionCallback connection) { + DataConnectionCallback connection) { if (!callbacks.contains(sessionID)) { callbacks.put(sessionID, connection); logger.fine("Registered data call back receiver for sessionID " @@ -83,7 +81,7 @@ public boolean registerCallback(int sessionID, public boolean unregisterCallback(int sessionID) { if (callbacks.contains(sessionID)) { logger.info("Removing callback receiver for sessionID " - + sessionID); + + sessionID); callbacks.remove(sessionID); return true; } diff --git a/src/edu/caltech/hep/dcapj/util/Server.java b/src/edu/caltech/hep/dcapj/util/Server.java index ab1932f..69945ce 100644 --- a/src/edu/caltech/hep/dcapj/util/Server.java +++ b/src/edu/caltech/hep/dcapj/util/Server.java @@ -1,15 +1,10 @@ package edu.caltech.hep.dcapj.util; import java.io.IOException; -import java.net.InetAddress; -import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.Socket; -import java.net.Inet4Address; -import java.net.NetworkInterface; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.Enumeration; public abstract class Server implements Runnable { @@ -19,13 +14,10 @@ public abstract class Server implements Runnable { protected boolean accept; protected ServerSocket _server = null; - + protected int timeout = 5 * 60 * 1000; private Thread serverThread = null; - private boolean secure = true; - protected int timeout = 5 * 60 * 1000; - public Server() throws IOException { this(0); } @@ -41,14 +33,14 @@ protected void init(final int port) throws IOException { + _server.getLocalPort()); } - public void setTimeout(final int timeout) { - this.timeout = timeout; - } - public int getTimeout() { return this.timeout; } + public void setTimeout(final int timeout) { + this.timeout = timeout; + } + public void shutdown() { accept = false; try { diff --git a/src/edu/caltech/hep/dcapj/util/ServerNIO.java b/src/edu/caltech/hep/dcapj/util/ServerNIO.java index 7394902..6468f72 100644 --- a/src/edu/caltech/hep/dcapj/util/ServerNIO.java +++ b/src/edu/caltech/hep/dcapj/util/ServerNIO.java @@ -2,16 +2,11 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.net.MalformedURLException; import java.net.ServerSocket; -import java.net.Socket; -import java.net.Inet4Address; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.net.NetworkInterface; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.Enumeration; public abstract class ServerNIO implements Runnable { @@ -23,13 +18,10 @@ public abstract class ServerNIO implements Runnable { protected ServerSocketChannel _serverChannel = null; protected ServerSocket _server = null; - + protected int timeout = 5 * 60 * 1000; private Thread serverThread = null; - private boolean secure = true; - protected int timeout = 5 * 60 * 1000; - public ServerNIO() throws IOException { this(0); } @@ -50,14 +42,14 @@ protected void init(final int port) throws IOException { + _server.getLocalPort()); } - public void setTimeout(final int timeout) { - this.timeout = timeout; - } - public int getTimeout() { return this.timeout; } + public void setTimeout(final int timeout) { + this.timeout = timeout; + } + public void shutdown() { accept = false; try { diff --git a/src/lia/gsi/ClientTest.java b/src/lia/gsi/ClientTest.java index 3097244..4783d4f 100644 --- a/src/lia/gsi/ClientTest.java +++ b/src/lia/gsi/ClientTest.java @@ -3,34 +3,27 @@ */ package lia.gsi; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; +import lia.gsi.net.GSIGssSocketFactory; + +import java.io.*; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; -import lia.gsi.net.GSIGssSocketFactory; - /** - * * @author Adrian Muraru - * */ public class ClientTest { - public static void main(String[] args) throws UnknownHostException, IOException { - GSIGssSocketFactory factory = new GSIGssSocketFactory(); - Socket socket = factory.createSocket(InetAddress.getByName(args[0]), 54320, false, false); - OutputStream outputStream = socket.getOutputStream(); - InputStream inputStream = socket.getInputStream(); - - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); - System.out.println("Received:"+br.readLine()); - PrintWriter pw = new PrintWriter(outputStream,true); - pw.println("Hello"); - } + public static void main(String[] args) throws UnknownHostException, IOException { + GSIGssSocketFactory factory = new GSIGssSocketFactory(); + Socket socket = factory.createSocket(InetAddress.getByName(args[0]), 54320, false, false); + OutputStream outputStream = socket.getOutputStream(); + InputStream inputStream = socket.getInputStream(); + + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); + System.out.println("Received:" + br.readLine()); + PrintWriter pw = new PrintWriter(outputStream, true); + pw.println("Hello"); + } } diff --git a/src/lia/gsi/FDTGSIServer.java b/src/lia/gsi/FDTGSIServer.java index 352c7fa..177bb46 100644 --- a/src/lia/gsi/FDTGSIServer.java +++ b/src/lia/gsi/FDTGSIServer.java @@ -3,22 +3,20 @@ */ package lia.gsi; -import java.net.Socket; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.security.auth.Subject; - import lia.util.net.copy.FDTSessionManager; import lia.util.net.copy.transport.ControlChannel; - import org.ietf.jgss.GSSCredential; +import javax.security.auth.Subject; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * This class will handle all the FDT GSI requests + * This class will handle all the FDT GSI requests * It overrides handleConversation from the base class. - * + * * @author ramiro */ public class FDTGSIServer extends GSIServer { @@ -33,7 +31,7 @@ public FDTGSIServer() throws Exception { public FDTGSIServer(int port) throws Exception { this((String) null, (String) null, port); } - + public FDTGSIServer(GSSCredential cred, int port) throws Exception { super(cred, port); } @@ -45,10 +43,10 @@ public FDTGSIServer(String serverKey, String serverCert, int port) throws Except public void start() { super.start(); } - + protected void handleConversation(GSIServer parent, Socket client, Subject peerSubject) { ControlChannel ct = null; - + try { ct = new ControlChannel(parent, client, peerSubject, fdtSessionManager); fdtSessionManager.addFDTClientSession(ct); diff --git a/src/lia/gsi/GSIServer.java b/src/lia/gsi/GSIServer.java index f1dd61d..86ffb58 100644 --- a/src/lia/gsi/GSIServer.java +++ b/src/lia/gsi/GSIServer.java @@ -3,18 +3,8 @@ */ package lia.gsi; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.Socket; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.security.auth.Subject; - import lia.gsi.net.GSIBaseServer; import lia.gsi.net.Peer; - import org.globus.gsi.CredentialException; import org.globus.gsi.GSIConstants; import org.globus.gsi.GlobusCredentialException; @@ -24,222 +14,222 @@ import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; +import javax.security.auth.Subject; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * Generic GSI Server - * - * @author Adrian Muraru + * + * @author Adrian Muraru */ public class GSIServer extends GSIBaseServer { - private static Logger logger = Logger.getLogger(GSIServer.class.getName());// LogFactory.getLog(GSIServer.class.getName()); - - protected static final int PORT = 54320; - - /** - * initializes the GSIServer with the default credentials and starting listening on default port - * - * @throws IOException - */ - public GSIServer(int port) throws Exception { - this((String) null, (String) null, port); - } - - /** - * initializes the GSIServer with the default credentials and starting listening on the default port - * - * @throws IOException - */ - public GSIServer() throws Exception { - this((String) null, (String) null, PORT); - } - - /** - * initializes the GSIServer with the provided credentials and starts to listen to client connections on the port passed as a parameter - * - * @param cred - * the credentials used by this server, if null then default credentials are used. - * @param port - * the port number used by this server - */ - public GSIServer(GSSCredential cred, int port) throws Exception { - super(cred, port); - } - - /** - * initializes the GSIServer with the provided credentials and starts to listen to client connections on the port passed as a parameter - * - * @param serverKey - * @param serverCert - * @param port - * @throws Exception - */ - public GSIServer(String serverKey, String serverCert, int port) throws Exception { - super(generateGSSCredential(serverKey, serverCert), port); - } - - public static GSSCredential generateGSSCredential(String serverKey, String serverCert) throws GlobusCredentialException, CredentialException, IOException, GSSException { - X509Credential credentials; - - // first try to read the service cert and key from the jvm properties - if (serverKey == null && serverCert == null) { - // read it from jvm properties - serverKey = System.getProperty("X509_SERVICE_KEY"); - serverCert = System.getProperty("X509_SERVICE_CERT"); - } - - // if not, try to read the service cert and key from the environment - if (serverKey == null && serverCert == null) { - // read it from env - serverKey = System.getenv("X509_HOST_KEY"); - serverCert = System.getenv("X509_HOST_CERT"); - } - - // if not, try to read the default location /etc/grid-security/host{cert,key}.pem - if (serverKey == null && serverCert == null) { - // read it from env - serverKey = "/etc/grid-security/hostkey.pem"; - serverCert = "/etc/grid-security/hostcert.pem"; - File certFile = new File(serverCert); - File keyFile = new File(serverKey); - // last, try to use user proxy, if it exists (currently disabled, look above) - if (!certFile.exists()) - { - serverCert = null; - } - if (!keyFile.exists()) - { - serverKey = null; - } - } - - if (serverKey == null && serverCert == null) { - // no X509_HOST_* var, use client cert (proxy-cert) - credentials = X509Credential.getDefaultCredential(); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Using user proxy certificate:" + credentials.getSubject()); - } - } else if (serverKey != null && serverCert != null) { - credentials = new X509Credential(serverCert, serverKey); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Using host certificate:" + credentials.getSubject()); - } - } else { - throw new IOException("Error: Service credentials could not be loaded."); - } - if (logger.isLoggable(Level.FINEST)) { - logger.log(Level.FINEST, "credentials:" + credentials); - } - return new GlobusGSSCredentialImpl(credentials, GSSCredential.ACCEPT_ONLY); - - } - - /** - * Initialization of the GSI server searching and setting the authorization plugin:
    - * org.globus.gsi.gssapi.auth.AuthorizationPlugin system property may use to define the class implementing an GT2 Authorization Plugin.
    - * If not defined the default grid-mapfile authorization method is used. - * - * @see org.globus.gsi.gssapi.auth.Authorization - * @throws IOException - * @throws IOException - * if grid-map file cannot be loaded - */ - - protected void initialize() { - super.initialize(); - setGssMode(GSIConstants.MODE_GSI); - } - - /** - * Handles individual client connections by starting a different thread. - * - * @param peer - * is connected to a client ready to send request to the gatekeeper. - * @throws IOException - * if authentication/authorization exception - */ - protected void handleConnection(Peer peer) { - Socket socket = peer.getSocket(); - Subject peerSubject = null; - if (logger.isLoggable(Level.FINE)) { - logger.info("Client connected: " + socket.getInetAddress() + ":" + socket.getPort()); - } - // in order to start the SSL handshake we need to call socket.getInput(Output)Stream() - try { - socket.getOutputStream(); - socket.getInputStream(); - // peer.authorizer called - peerSubject = peer.getPeerSubject(); - } catch (Throwable t) { - logger.log(Level.INFO, "Authentication failed:", t); - if (socket != null) { + protected static final int PORT = 54320; + private static Logger logger = Logger.getLogger(GSIServer.class.getName());// LogFactory.getLog(GSIServer.class.getName()); + + /** + * initializes the GSIServer with the default credentials and starting listening on default port + * + * @throws IOException + */ + public GSIServer(int port) throws Exception { + this((String) null, (String) null, port); + } + + /** + * initializes the GSIServer with the default credentials and starting listening on the default port + * + * @throws IOException + */ + public GSIServer() throws Exception { + this((String) null, (String) null, PORT); + } + + /** + * initializes the GSIServer with the provided credentials and starts to listen to client connections on the port passed as a parameter + * + * @param cred the credentials used by this server, if null then default credentials are used. + * @param port the port number used by this server + */ + public GSIServer(GSSCredential cred, int port) throws Exception { + super(cred, port); + } + + /** + * initializes the GSIServer with the provided credentials and starts to listen to client connections on the port passed as a parameter + * + * @param serverKey + * @param serverCert + * @param port + * @throws Exception + */ + public GSIServer(String serverKey, String serverCert, int port) throws Exception { + super(generateGSSCredential(serverKey, serverCert), port); + } + + public static GSSCredential generateGSSCredential(String serverKey, String serverCert) throws GlobusCredentialException, CredentialException, IOException, GSSException { + X509Credential credentials; + + // first try to read the service cert and key from the jvm properties + if (serverKey == null && serverCert == null) { + // read it from jvm properties + serverKey = System.getProperty("X509_SERVICE_KEY"); + serverCert = System.getProperty("X509_SERVICE_CERT"); + } + + // if not, try to read the service cert and key from the environment + if (serverKey == null && serverCert == null) { + // read it from env + serverKey = System.getenv("X509_HOST_KEY"); + serverCert = System.getenv("X509_HOST_CERT"); + } + + // if not, try to read the default location /etc/grid-security/host{cert,key}.pem + if (serverKey == null && serverCert == null) { + // read it from env + serverKey = "/etc/grid-security/hostkey.pem"; + serverCert = "/etc/grid-security/hostcert.pem"; + File certFile = new File(serverCert); + File keyFile = new File(serverKey); + // last, try to use user proxy, if it exists (currently disabled, look above) + if (!certFile.exists()) { + serverCert = null; + } + if (!keyFile.exists()) { + serverKey = null; + } + } + + if (serverKey == null && serverCert == null) { + // no X509_HOST_* var, use client cert (proxy-cert) + credentials = X509Credential.getDefaultCredential(); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Using user proxy certificate:" + credentials.getSubject()); + } + } else if (serverKey != null && serverCert != null) { + credentials = new X509Credential(serverCert, serverKey); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Using host certificate:" + credentials.getSubject()); + } + } else { + throw new IOException("Error: Service credentials could not be loaded."); + } + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "credentials:" + credentials); + } + return new GlobusGSSCredentialImpl(credentials, GSSCredential.ACCEPT_ONLY); + + } + + // DEBUG + public static void main(String[] args) throws Exception { + GSIServer ctrlServer = new GSIServer(); + ctrlServer.start(); + System.out.println("Started"); + } + + /** + * Initialization of the GSI server searching and setting the authorization plugin:
    + * org.globus.gsi.gssapi.auth.AuthorizationPlugin system property may use to define the class implementing an GT2 Authorization Plugin.
    + * If not defined the default grid-mapfile authorization method is used. + * + * @throws IOException + * @throws IOException if grid-map file cannot be loaded + * @see org.globus.gsi.gssapi.auth.Authorization + */ + + protected void initialize() { + super.initialize(); + setGssMode(GSIConstants.MODE_GSI); + } + + /** + * Handles individual client connections by starting a different thread. + * + * @param peer is connected to a client ready to send request to the gatekeeper. + * @throws IOException if authentication/authorization exception + */ + protected void handleConnection(Peer peer) { + Socket socket = peer.getSocket(); + Subject peerSubject = null; + if (logger.isLoggable(Level.FINE)) { + logger.info("Client connected: " + socket.getInetAddress() + ":" + socket.getPort()); + } + // in order to start the SSL handshake we need to call socket.getInput(Output)Stream() + try { + socket.getOutputStream(); + socket.getInputStream(); + // peer.authorizer called + peerSubject = peer.getPeerSubject(); + } catch (Throwable t) { + logger.log(Level.INFO, "Authentication failed:", t); + if (socket != null) { if (logger.isLoggable(Level.INFO)) { logger.log(Level.INFO, "Client disconnected"); } - try { - socket.close(); - } catch (Throwable ignore) { - } - } - return; - } - - // the client is successfully authenticated and authorized - // so, proceed with the actual control conversation - handleConversation(this, socket, peerSubject); - - } - - /** - * This method needs to be implemented by subclasses.
    - * Optimmaly, it should be a non-blocking call starting a separate thread to handle the client: i.e: - * - *

    -	 *   ControlClient c = new ControlClient(parent, socket,Subject peerSubject); 
    -	 *   c.start() 
    -	 * 
    - * - * The default implementation just put an int on the wire. - */ - protected void handleConversation(GSIServer parent, Socket client, Subject peerSubject) { - logger.info("Client connected :" + client + "\n" + peerSubject); - if (peerSubject != null) { - UserNamePrincipal up = (UserNamePrincipal) peerSubject.getPrincipals(UserNamePrincipal.class).toArray()[0]; - System.out.println("LocalID:" + up.getName()); - } - try { - PrintWriter pw = new PrintWriter(client.getOutputStream()); - pw.println("HELLO From GSI Server. Your order please!"); - pw.flush(); - System.out.println("Sent"); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - public String getContact() { - String gid = null; - try { - gid = getCredentials().getName().toString(); - } catch (GSSException e) { - return null; - } - - StringBuffer url = new StringBuffer(); - url.append(getHost()).append(":").append(String.valueOf(getPort())).append(":").append(gid); - - return url.toString(); - } - - // DEBUG - public static void main(String[] args) throws Exception { - GSIServer ctrlServer = new GSIServer(); - ctrlServer.start(); - System.out.println("Started"); - } + try { + socket.close(); + } catch (Throwable ignore) { + } + } + return; + } + + // the client is successfully authenticated and authorized + // so, proceed with the actual control conversation + handleConversation(this, socket, peerSubject); + + } + + /** + * This method needs to be implemented by subclasses.
    + * Optimmaly, it should be a non-blocking call starting a separate thread to handle the client: i.e: + *

    + *

    +     *   ControlClient c = new ControlClient(parent, socket,Subject peerSubject);
    +     *   c.start()
    +     * 
    + *

    + * The default implementation just put an int on the wire. + */ + protected void handleConversation(GSIServer parent, Socket client, Subject peerSubject) { + logger.info("Client connected :" + client + "\n" + peerSubject); + if (peerSubject != null) { + UserNamePrincipal up = (UserNamePrincipal) peerSubject.getPrincipals(UserNamePrincipal.class).toArray()[0]; + System.out.println("LocalID:" + up.getName()); + } + try { + PrintWriter pw = new PrintWriter(client.getOutputStream()); + pw.println("HELLO From GSI Server. Your order please!"); + pw.flush(); + System.out.println("Sent"); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public String getContact() { + String gid = null; + try { + gid = getCredentials().getName().toString(); + } catch (GSSException e) { + return null; + } + + StringBuffer url = new StringBuffer(); + url.append(getHost()).append(":").append(String.valueOf(getPort())).append(":").append(gid); + + return url.toString(); + } } diff --git a/src/lia/gsi/authz/GridMap.java b/src/lia/gsi/authz/GridMap.java index cc4b37e..84102e8 100644 --- a/src/lia/gsi/authz/GridMap.java +++ b/src/lia/gsi/authz/GridMap.java @@ -10,496 +10,469 @@ */ package lia.gsi.authz; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.Vector; - import org.globus.util.QuotedStringTokenizer; +import java.io.*; +import java.util.*; + public class GridMap implements Serializable { - public static final String DEFAULT_GRID_MAP = "/etc/grid-security/grid-mapfile"; - private static Map gridMaps = new HashMap(); - - private static final String COMMENT_CHARS = "#"; - // keywords that need to be replaced - private static final char[] EMAIL_KEYWORD_1 = { 'e', '=' }; - private static final char[] EMAIL_KEYWORD_2 = { 'e', 'm', 'a', 'i', 'l', '=' }; - private static final char[] UID_KEYWORD = { 'u', 'i', 'd', '=' }; - // Length of key words that need to be replaced - private static final int EMAIL_KEYWORD_1_L = 2; - private static final int EMAIL_KEYWORD_2_L = 6; - private static final int UID_KEYWORD_L = 4; - // Keywords to be replaced with. - private static final String EMAIL_KEYWORD = "emailaddress="; - private static final String USERID_KEYWORD = "userid="; - - protected Map map; - - // the file the grim map was loaded from - private File file; - // last time the file was modified - private long lastModified; - - /** - * Returns an instance of the default grid map. If an existing instance is already loaded, it is refreshed. The meaning of default grid map depends on the grid.mapfile - * system property. If the property is defined, this method attempts to load the map file from the file pointed to by the the property. If the "grid.mapfile" system property is - * not defined, the method attempts to load the map file from /etc/grid-security/grid-mapfile. - * - * @exception IOException if an error occurs while loading the default map file. - * @return The default map file instance. - */ - public static GridMap getGridMap() throws IOException { - // java property? - String mapFile = System.getProperty("GRIDMAP"); - // env var? - if (mapFile == null) - mapFile = System.getenv("GRIDMAP"); - // default one - if (mapFile == null) - mapFile = DEFAULT_GRID_MAP; - - return getGridMap(mapFile); - } - - /** - * Returns an instance of the grid map loaded from a specific file. If an instance was loaded previously (by calling this method with the same argument), it is refreshed and - * returned. - * - * @param mapFile - * the file from which the grid map is to be loaded - * @exception IOException - * if an error occurs while loading the grid map file - * @return The grid map file instance loaded from the specified file - */ - public static synchronized GridMap getGridMap(String mapFile) throws IOException { - GridMap gridMap = (GridMap) gridMaps.get(mapFile); - if (gridMap == null) { - gridMap = new GridMap(); - gridMap.load(mapFile); - gridMaps.put(mapFile, gridMap); - } else { - gridMap.refresh(); - } - return gridMap; - } - - /** - * Loads grid map definition from a given file. - * - * @param file - * grid map file - * @exception IOException - * in case of I/O or parsing error. - */ - public void load(String file) throws IOException { - load(new File(file)); - } - - /** - * Loads grid map definition from a given file. - * - * @param file - * grid map file - * @exception IOException - * in case of I/O or parsing error. - */ - public synchronized void load(File file) throws IOException { - InputStream in = null; - try { - in = new FileInputStream(file); - this.file = file; - this.lastModified = file.lastModified(); - load(in); - } finally { - if (in != null) { - try { - in.close(); - } catch (Exception e) { - } - } - } - } - - /** - * Reloads the gridmap from a file only if the gridmap was initially loaded using the {@link #load(File) load} or {@link #load(String) load} functions. The file will only be - * reloaded if it has changed since the last time. - * - * @exception IOException - * in case of I/O error during reload. - */ - public void refresh() throws IOException { - if (this.file == null) { - return; - } - if (this.file.lastModified() != this.lastModified) { - load(this.file); - } - } - - /** - * Loads grid map file definition from a given input stream. The input stream is not closed in case of an error. - * - * @param input - * input stream containing grid map definitions. - * @exception IOException - * in case of I/O error or parsing error. - */ - public void load(InputStream input) throws IOException { - String line; - - BufferedReader reader = new BufferedReader(new InputStreamReader(input)); - - Map localMap = new HashMap(); - GridMapEntry entry; - QuotedStringTokenizer tokenizer; - StringTokenizer idTokenizer; - - while ((line = reader.readLine()) != null) { - line = line.trim(); - if ((line.length() == 0) || (COMMENT_CHARS.indexOf(line.charAt(0)) != -1)) { - continue; - } - - tokenizer = new QuotedStringTokenizer(line); - - String globusID = null; - - if (tokenizer.hasMoreTokens()) { - globusID = tokenizer.nextToken(); - } else { - throw new IOException("Globus ID not defined: " + line); - } - - String userIDs = null; - - if (tokenizer.hasMoreTokens()) { - userIDs = tokenizer.nextToken(); - } else { - throw new IOException("User ID mapping missing: " + line); - } - - idTokenizer = new StringTokenizer(userIDs, ","); - String[] ids = new String[idTokenizer.countTokens()]; - int i = 0; - while (idTokenizer.hasMoreTokens()) { - ids[i++] = idTokenizer.nextToken(); - } - - String normalizedDN = normalizeDN(globusID); - entry = (GridMapEntry) localMap.get(normalizedDN); - if (entry == null) { - entry = new GridMapEntry(); - entry.setGlobusID(globusID); - entry.setUserIDs(ids); - localMap.put(normalizedDN, entry); - } else { - entry.addUserIDs(ids); - } - } - - map = localMap; - } - - /** - * Returns first local user name mapped to the specified globusID. - * - * @param globusID - * globusID - * @return local user name for the specified globusID. Null if the globusID is not mapped to a local user name. - */ - public synchronized String getUserID(String globusID) { - String[] ids = getUserIDs(globusID); - if (ids != null && ids.length > 0) { - return ids[0]; - } else { - return null; - } - } - - /** - * Returns local user names mapped to the specified globusID. - * - * @param globusID - * globusID - * @return array of local user names for the specified globusID. Null if the globusID is not mapped to any local user name. - */ - public synchronized String[] getUserIDs(String globusID) { - if (globusID == null) { - throw new IllegalArgumentException("globusID is null"); - } - - if (map == null) { - // not initialized - // if not running as root, then return own username - // this allows someone to run as themselves without - // having to have a gridmap file. - String user = System.getProperty("user.name"); - if (user == null) - return null; - String tmpUser = user.toLowerCase(); - if (tmpUser.equals("root") || tmpUser.equals("administrator")) { - return null; - } else { - return new String[] { user }; - } - } - - GridMapEntry entry = (GridMapEntry) map.get(normalizeDN(globusID)); - return (entry == null) - ? null - : entry.getUserIDs(); - } - - /** - * Checks if a given globus ID is associated with given local user account. - * - * @param globusID - * globus ID - * @param userID - * userID - * @return true if globus ID is associated with given local user account, false, otherwise. - */ - public synchronized boolean checkUser(String globusID, String userID) { - if (globusID == null) { - throw new IllegalArgumentException("globusID is null"); - } - if (userID == null) { - throw new IllegalArgumentException("userID is null"); - } - - if (map == null) { - // not initialized - // if not running as root, then return own username - // this allows someone to run as themselves without - // having to have a gridmap file. - String user = System.getProperty("user.name"); - if (user == null) - return false; - String tmpUser = user.toLowerCase(); - if (tmpUser.equals("root") || tmpUser.equals("administrator")) { - return false; - } else { - return user.equalsIgnoreCase(userID); - } - } - - GridMapEntry entry = (GridMapEntry) map.get(normalizeDN(globusID)); - return (entry == null) - ? false - : entry.containsUserID(userID); - } - - /** - * Returns globus ID associated with the specified local user name. - * - * @param userID - * local user name - * @return associated globus ID, null if there is not any. - */ - public synchronized String getGlobusID(String userID) { - if (userID == null) { - throw new IllegalArgumentException("userID is null"); - } - - if (map == null) { - return null; - } - - Iterator iter = map.entrySet().iterator(); - Map.Entry mapEntry; - GridMapEntry entry; - while (iter.hasNext()) { - mapEntry = (Map.Entry) iter.next(); - entry = (GridMapEntry) mapEntry.getValue(); - if (entry.containsUserID(userID)) { - return entry.getGlobusID(); - } - } - return null; - } - - /** - * Returns all globus IDs associated with the specified local user name. - * - * @param userID - * local user name - * @return associated globus ID, null if there is not any. - */ - public synchronized String[] getAllGlobusID(String userID) { - if (userID == null) { - throw new IllegalArgumentException("userID is null"); - } - - if (map == null) { - return null; - } - - Vector v = new Vector(); - - Iterator iter = map.entrySet().iterator(); - Map.Entry mapEntry; - GridMapEntry entry; - while (iter.hasNext()) { - mapEntry = (Map.Entry) iter.next(); - entry = (GridMapEntry) mapEntry.getValue(); - if (entry.containsUserID(userID)) { - v.add(entry.getGlobusID()); - } - } - - // create array of strings and add values back in - if (v.size() == 0) { - return null; - } - - String idS[] = new String[v.size()]; - for (int ctr = 0; ctr < v.size(); ctr++) { - idS[ctr] = (String) v.elementAt(ctr); - } - - return idS; - } - - public synchronized void map(String globusID, String userID) { - if (globusID == null) { - throw new IllegalArgumentException("globusID is null"); - } - if (userID == null) { - throw new IllegalArgumentException("userID is null"); - } - - if (map == null) { - map = new HashMap(); - } - - String normalizedDN = normalizeDN(globusID); - - GridMapEntry entry = (GridMapEntry) map.get(normalizedDN); - if (entry == null) { - entry = new GridMapEntry(); - entry.setGlobusID(globusID); - entry.setUserIDs(new String[] { userID }); - map.put(normalizedDN, entry); - } else { - entry.addUserID(userID); - } - } - - static class GridMapEntry implements Serializable { - String globusID; - String[] userIDs; - - public String getFirstUserID() { - return userIDs[0]; - } - - public String[] getUserIDs() { - return userIDs; - } - - public String getGlobusID() { - return globusID; - } - - public void setGlobusID(String globusID) { - this.globusID = globusID; - } - - public void setUserIDs(String[] userIDs) { - this.userIDs = userIDs; - } - - public boolean containsUserID(String userID) { - if (userID == null) { - return false; - } - for (int i = 0; i < userIDs.length; i++) { - if (userIDs[i].equalsIgnoreCase(userID)) { - return true; - } - } - return false; - } - - public void addUserID(String userID) { - if (containsUserID(userID)) - return; - String[] ids = new String[userIDs.length + 1]; - System.arraycopy(userIDs, 0, ids, 0, userIDs.length); - ids[userIDs.length] = userID; - userIDs = ids; - } - - public void addUserIDs(String[] userIDs) { - for (int i = 0; i < userIDs.length; i++) { - addUserID(userIDs[i]); - } - } - - } - - private static boolean keyWordPresent(char[] args, int startIndex, char[] keyword, int length) { - - if (startIndex + length > args.length) { - return false; - } - - int j = startIndex; - for (int i = 0; i < length; i++) { - if (args[j] != keyword[i]) { - return false; - } - j++; - } - return true; - } - - public static String normalizeDN(String globusID) { - - if (globusID == null) { - return null; - } - - globusID = globusID.toLowerCase(); - char[] globusIdChars = globusID.toCharArray(); - - StringBuffer normalizedDN = new StringBuffer(); - - int i = 0; - - while (i < globusIdChars.length) { - - if (globusIdChars[i] == '/') { - - normalizedDN.append("/"); - - if (keyWordPresent(globusIdChars, i + 1, EMAIL_KEYWORD_1, EMAIL_KEYWORD_1_L)) { - normalizedDN.append(EMAIL_KEYWORD); - i = i + EMAIL_KEYWORD_1_L; - } else if (keyWordPresent(globusIdChars, i + 1, EMAIL_KEYWORD_2, EMAIL_KEYWORD_2_L)) { - normalizedDN.append(EMAIL_KEYWORD); - i = i + EMAIL_KEYWORD_2_L; - } else if (keyWordPresent(globusIdChars, i + 1, UID_KEYWORD, UID_KEYWORD_L)) { - normalizedDN.append(USERID_KEYWORD); - i = i + UID_KEYWORD_L; - } - i++; - } else { - normalizedDN.append(globusIdChars[i]); - i++; - } - } - - return normalizedDN.toString(); - } + public static final String DEFAULT_GRID_MAP = "/etc/grid-security/grid-mapfile"; + private static final String COMMENT_CHARS = "#"; + // keywords that need to be replaced + private static final char[] EMAIL_KEYWORD_1 = {'e', '='}; + private static final char[] EMAIL_KEYWORD_2 = {'e', 'm', 'a', 'i', 'l', '='}; + private static final char[] UID_KEYWORD = {'u', 'i', 'd', '='}; + // Length of key words that need to be replaced + private static final int EMAIL_KEYWORD_1_L = 2; + private static final int EMAIL_KEYWORD_2_L = 6; + private static final int UID_KEYWORD_L = 4; + // Keywords to be replaced with. + private static final String EMAIL_KEYWORD = "emailaddress="; + private static final String USERID_KEYWORD = "userid="; + private static Map gridMaps = new HashMap(); + protected Map map; + + // the file the grim map was loaded from + private File file; + // last time the file was modified + private long lastModified; + + /** + * Returns an instance of the default grid map. If an existing instance is already loaded, it is refreshed. The meaning of default grid map depends on the grid.mapfile + * system property. If the property is defined, this method attempts to load the map file from the file pointed to by the the property. If the "grid.mapfile" system property is + * not defined, the method attempts to load the map file from /etc/grid-security/grid-mapfile. + * + * @return The default map file instance. + * @throws IOException if an error occurs while loading the default map file. + */ + public static GridMap getGridMap() throws IOException { + // java property? + String mapFile = System.getProperty("GRIDMAP"); + // env var? + if (mapFile == null) + mapFile = System.getenv("GRIDMAP"); + // default one + if (mapFile == null) + mapFile = DEFAULT_GRID_MAP; + + return getGridMap(mapFile); + } + + /** + * Returns an instance of the grid map loaded from a specific file. If an instance was loaded previously (by calling this method with the same argument), it is refreshed and + * returned. + * + * @param mapFile the file from which the grid map is to be loaded + * @return The grid map file instance loaded from the specified file + * @throws IOException if an error occurs while loading the grid map file + */ + public static synchronized GridMap getGridMap(String mapFile) throws IOException { + GridMap gridMap = (GridMap) gridMaps.get(mapFile); + if (gridMap == null) { + gridMap = new GridMap(); + gridMap.load(mapFile); + gridMaps.put(mapFile, gridMap); + } else { + gridMap.refresh(); + } + return gridMap; + } + + private static boolean keyWordPresent(char[] args, int startIndex, char[] keyword, int length) { + + if (startIndex + length > args.length) { + return false; + } + + int j = startIndex; + for (int i = 0; i < length; i++) { + if (args[j] != keyword[i]) { + return false; + } + j++; + } + return true; + } + + public static String normalizeDN(String globusID) { + + if (globusID == null) { + return null; + } + + globusID = globusID.toLowerCase(); + char[] globusIdChars = globusID.toCharArray(); + + StringBuffer normalizedDN = new StringBuffer(); + + int i = 0; + + while (i < globusIdChars.length) { + + if (globusIdChars[i] == '/') { + + normalizedDN.append("/"); + + if (keyWordPresent(globusIdChars, i + 1, EMAIL_KEYWORD_1, EMAIL_KEYWORD_1_L)) { + normalizedDN.append(EMAIL_KEYWORD); + i = i + EMAIL_KEYWORD_1_L; + } else if (keyWordPresent(globusIdChars, i + 1, EMAIL_KEYWORD_2, EMAIL_KEYWORD_2_L)) { + normalizedDN.append(EMAIL_KEYWORD); + i = i + EMAIL_KEYWORD_2_L; + } else if (keyWordPresent(globusIdChars, i + 1, UID_KEYWORD, UID_KEYWORD_L)) { + normalizedDN.append(USERID_KEYWORD); + i = i + UID_KEYWORD_L; + } + i++; + } else { + normalizedDN.append(globusIdChars[i]); + i++; + } + } + + return normalizedDN.toString(); + } + + /** + * Loads grid map definition from a given file. + * + * @param file grid map file + * @throws IOException in case of I/O or parsing error. + */ + public void load(String file) throws IOException { + load(new File(file)); + } + + /** + * Loads grid map definition from a given file. + * + * @param file grid map file + * @throws IOException in case of I/O or parsing error. + */ + public synchronized void load(File file) throws IOException { + InputStream in = null; + try { + in = new FileInputStream(file); + this.file = file; + this.lastModified = file.lastModified(); + load(in); + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception e) { + } + } + } + } + + /** + * Reloads the gridmap from a file only if the gridmap was initially loaded using the {@link #load(File) load} or {@link #load(String) load} functions. The file will only be + * reloaded if it has changed since the last time. + * + * @throws IOException in case of I/O error during reload. + */ + public void refresh() throws IOException { + if (this.file == null) { + return; + } + if (this.file.lastModified() != this.lastModified) { + load(this.file); + } + } + + /** + * Loads grid map file definition from a given input stream. The input stream is not closed in case of an error. + * + * @param input input stream containing grid map definitions. + * @throws IOException in case of I/O error or parsing error. + */ + public void load(InputStream input) throws IOException { + String line; + + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + + Map localMap = new HashMap(); + GridMapEntry entry; + QuotedStringTokenizer tokenizer; + StringTokenizer idTokenizer; + + while ((line = reader.readLine()) != null) { + line = line.trim(); + if ((line.length() == 0) || (COMMENT_CHARS.indexOf(line.charAt(0)) != -1)) { + continue; + } + + tokenizer = new QuotedStringTokenizer(line); + + String globusID = null; + + if (tokenizer.hasMoreTokens()) { + globusID = tokenizer.nextToken(); + } else { + throw new IOException("Globus ID not defined: " + line); + } + + String userIDs = null; + + if (tokenizer.hasMoreTokens()) { + userIDs = tokenizer.nextToken(); + } else { + throw new IOException("User ID mapping missing: " + line); + } + + idTokenizer = new StringTokenizer(userIDs, ","); + String[] ids = new String[idTokenizer.countTokens()]; + int i = 0; + while (idTokenizer.hasMoreTokens()) { + ids[i++] = idTokenizer.nextToken(); + } + + String normalizedDN = normalizeDN(globusID); + entry = (GridMapEntry) localMap.get(normalizedDN); + if (entry == null) { + entry = new GridMapEntry(); + entry.setGlobusID(globusID); + entry.setUserIDs(ids); + localMap.put(normalizedDN, entry); + } else { + entry.addUserIDs(ids); + } + } + + map = localMap; + } + + /** + * Returns first local user name mapped to the specified globusID. + * + * @param globusID globusID + * @return local user name for the specified globusID. Null if the globusID is not mapped to a local user name. + */ + public synchronized String getUserID(String globusID) { + String[] ids = getUserIDs(globusID); + if (ids != null && ids.length > 0) { + return ids[0]; + } else { + return null; + } + } + + /** + * Returns local user names mapped to the specified globusID. + * + * @param globusID globusID + * @return array of local user names for the specified globusID. Null if the globusID is not mapped to any local user name. + */ + public synchronized String[] getUserIDs(String globusID) { + if (globusID == null) { + throw new IllegalArgumentException("globusID is null"); + } + + if (map == null) { + // not initialized + // if not running as root, then return own username + // this allows someone to run as themselves without + // having to have a gridmap file. + String user = System.getProperty("user.name"); + if (user == null) + return null; + String tmpUser = user.toLowerCase(); + if (tmpUser.equals("root") || tmpUser.equals("administrator")) { + return null; + } else { + return new String[]{user}; + } + } + + GridMapEntry entry = (GridMapEntry) map.get(normalizeDN(globusID)); + return (entry == null) + ? null + : entry.getUserIDs(); + } + + /** + * Checks if a given globus ID is associated with given local user account. + * + * @param globusID globus ID + * @param userID userID + * @return true if globus ID is associated with given local user account, false, otherwise. + */ + public synchronized boolean checkUser(String globusID, String userID) { + if (globusID == null) { + throw new IllegalArgumentException("globusID is null"); + } + if (userID == null) { + throw new IllegalArgumentException("userID is null"); + } + + if (map == null) { + // not initialized + // if not running as root, then return own username + // this allows someone to run as themselves without + // having to have a gridmap file. + String user = System.getProperty("user.name"); + if (user == null) + return false; + String tmpUser = user.toLowerCase(); + if (tmpUser.equals("root") || tmpUser.equals("administrator")) { + return false; + } else { + return user.equalsIgnoreCase(userID); + } + } + + GridMapEntry entry = (GridMapEntry) map.get(normalizeDN(globusID)); + return (entry == null) + ? false + : entry.containsUserID(userID); + } + + /** + * Returns globus ID associated with the specified local user name. + * + * @param userID local user name + * @return associated globus ID, null if there is not any. + */ + public synchronized String getGlobusID(String userID) { + if (userID == null) { + throw new IllegalArgumentException("userID is null"); + } + + if (map == null) { + return null; + } + + Iterator iter = map.entrySet().iterator(); + Map.Entry mapEntry; + GridMapEntry entry; + while (iter.hasNext()) { + mapEntry = (Map.Entry) iter.next(); + entry = (GridMapEntry) mapEntry.getValue(); + if (entry.containsUserID(userID)) { + return entry.getGlobusID(); + } + } + return null; + } + + /** + * Returns all globus IDs associated with the specified local user name. + * + * @param userID local user name + * @return associated globus ID, null if there is not any. + */ + public synchronized String[] getAllGlobusID(String userID) { + if (userID == null) { + throw new IllegalArgumentException("userID is null"); + } + + if (map == null) { + return null; + } + + Vector v = new Vector(); + + Iterator iter = map.entrySet().iterator(); + Map.Entry mapEntry; + GridMapEntry entry; + while (iter.hasNext()) { + mapEntry = (Map.Entry) iter.next(); + entry = (GridMapEntry) mapEntry.getValue(); + if (entry.containsUserID(userID)) { + v.add(entry.getGlobusID()); + } + } + + // create array of strings and add values back in + if (v.size() == 0) { + return null; + } + + String idS[] = new String[v.size()]; + for (int ctr = 0; ctr < v.size(); ctr++) { + idS[ctr] = (String) v.elementAt(ctr); + } + + return idS; + } + + public synchronized void map(String globusID, String userID) { + if (globusID == null) { + throw new IllegalArgumentException("globusID is null"); + } + if (userID == null) { + throw new IllegalArgumentException("userID is null"); + } + + if (map == null) { + map = new HashMap(); + } + + String normalizedDN = normalizeDN(globusID); + + GridMapEntry entry = (GridMapEntry) map.get(normalizedDN); + if (entry == null) { + entry = new GridMapEntry(); + entry.setGlobusID(globusID); + entry.setUserIDs(new String[]{userID}); + map.put(normalizedDN, entry); + } else { + entry.addUserID(userID); + } + } + + static class GridMapEntry implements Serializable { + String globusID; + String[] userIDs; + + public String getFirstUserID() { + return userIDs[0]; + } + + public String[] getUserIDs() { + return userIDs; + } + + public void setUserIDs(String[] userIDs) { + this.userIDs = userIDs; + } + + public String getGlobusID() { + return globusID; + } + + public void setGlobusID(String globusID) { + this.globusID = globusID; + } + + public boolean containsUserID(String userID) { + if (userID == null) { + return false; + } + for (int i = 0; i < userIDs.length; i++) { + if (userIDs[i].equalsIgnoreCase(userID)) { + return true; + } + } + return false; + } + + public void addUserID(String userID) { + if (containsUserID(userID)) + return; + String[] ids = new String[userIDs.length + 1]; + System.arraycopy(userIDs, 0, ids, 0, userIDs.length); + ids[userIDs.length] = userID; + userIDs = ids; + } + + public void addUserIDs(String[] userIDs) { + for (int i = 0; i < userIDs.length; i++) { + addUserID(userIDs[i]); + } + } + + } } diff --git a/src/lia/gsi/authz/GridMapAuthorization.java b/src/lia/gsi/authz/GridMapAuthorization.java index 34e6b35..2ba8f66 100644 --- a/src/lia/gsi/authz/GridMapAuthorization.java +++ b/src/lia/gsi/authz/GridMapAuthorization.java @@ -9,33 +9,33 @@ */ package lia.gsi.authz; -import java.io.IOException; - import org.globus.gsi.gssapi.auth.AuthorizationException; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; -public class GridMapAuthorization extends LocalMappingAuthorization { - - public GridMapAuthorization() throws IOException { - GridMap.getGridMap(); - } - - public String getLocalID(GSSContext context, String host) { - try { - String dn = context.getSrcName().toString(); - String name = GridMap.getGridMap().getUserID(dn); - - if (name == null) { - throw new AuthorizationException("No local mapping for " + dn); - } - return name; - } catch (IOException e) { - return null; - } catch (GSSException e) { - return null; - } - } - - +import java.io.IOException; + +public class GridMapAuthorization extends LocalMappingAuthorization { + + public GridMapAuthorization() throws IOException { + GridMap.getGridMap(); + } + + public String getLocalID(GSSContext context, String host) { + try { + String dn = context.getSrcName().toString(); + String name = GridMap.getGridMap().getUserID(dn); + + if (name == null) { + throw new AuthorizationException("No local mapping for " + dn); + } + return name; + } catch (IOException e) { + return null; + } catch (GSSException e) { + return null; + } + } + + } diff --git a/src/lia/gsi/authz/LocalMappingAuthorization.java b/src/lia/gsi/authz/LocalMappingAuthorization.java index d313ca7..d6279f6 100644 --- a/src/lia/gsi/authz/LocalMappingAuthorization.java +++ b/src/lia/gsi/authz/LocalMappingAuthorization.java @@ -3,63 +3,63 @@ */ package lia.gsi.authz; -import javax.security.auth.Subject; - +import org.globus.gsi.gssapi.JaasGssUtil; import org.globus.gsi.gssapi.auth.Authorization; import org.globus.gsi.gssapi.auth.AuthorizationException; import org.globus.gsi.gssapi.jaas.GlobusPrincipal; -import org.globus.gsi.gssapi.JaasGssUtil; import org.globus.gsi.gssapi.jaas.UserNamePrincipal; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; +import javax.security.auth.Subject; + /** - * * Extend org.globus.gsi.gssapi.auth.Authorization base-class to also provide the localID mapping of the client.
    * This is based on PDP interface from GT4 but in the same time keep the backward-compatibily with GT2 interfaces
    - * The default authorization plugin can be changed at runtime with other implementation of this class by passing: + * The default authorization plugin can be changed at runtime with other implementation of this class by passing: * -Dgsi.authz.Authorization=authzclass property.
    * By default the GridMapAuthorization is used - * + * * @author Adrian Muraru * @date 30.01.2007 - * */ -public abstract class LocalMappingAuthorization extends Authorization { - - Subject peerSubject=null; - /** - * Interface for authorization mechanisms on server side - * The authorization is performed once the connection was authenticated. - * @return: local userID mapping for the given peer (ussualy based on DN) or "null" is the user is not authorized - */ - public abstract String getLocalID(GSSContext context, String host) ; - - public void authorize(GSSContext context, String host) throws AuthorizationException { - - String localID = this.getLocalID(context, host); - if (localID==null){ - String srcName; - try { - srcName = context==null?"":context.getSrcName().toString(); - } catch (GSSException e) { - srcName=""; - } - throw new AuthorizationException("No local mapping for :"+srcName); - } - peerSubject =new Subject(); - GlobusPrincipal nm; - try { - nm = JaasGssUtil.toGlobusPrincipal(context.getSrcName()); - } catch (GSSException e) { - throw new AuthorizationException("Cannot get peer DN"); - } - peerSubject.getPrincipals().add(nm); - peerSubject.getPrincipals().add(new UserNamePrincipal(localID)); - } - - public Subject getPeerSubject() { - return this.peerSubject; - } - +public abstract class LocalMappingAuthorization extends Authorization { + + Subject peerSubject = null; + + /** + * Interface for authorization mechanisms on server side + * The authorization is performed once the connection was authenticated. + * + * @return: local userID mapping for the given peer (ussualy based on DN) or "null" is the user is not authorized + */ + public abstract String getLocalID(GSSContext context, String host); + + public void authorize(GSSContext context, String host) throws AuthorizationException { + + String localID = this.getLocalID(context, host); + if (localID == null) { + String srcName; + try { + srcName = context == null ? "" : context.getSrcName().toString(); + } catch (GSSException e) { + srcName = ""; + } + throw new AuthorizationException("No local mapping for :" + srcName); + } + peerSubject = new Subject(); + GlobusPrincipal nm; + try { + nm = JaasGssUtil.toGlobusPrincipal(context.getSrcName()); + } catch (GSSException e) { + throw new AuthorizationException("Cannot get peer DN"); + } + peerSubject.getPrincipals().add(nm); + peerSubject.getPrincipals().add(new UserNamePrincipal(localID)); + } + + public Subject getPeerSubject() { + return this.peerSubject; + } + } diff --git a/src/lia/gsi/net/GSIBaseServer.java b/src/lia/gsi/net/GSIBaseServer.java index e1dc88f..bf83766 100644 --- a/src/lia/gsi/net/GSIBaseServer.java +++ b/src/lia/gsi/net/GSIBaseServer.java @@ -3,17 +3,7 @@ */ package lia.gsi.net; -import java.io.IOException; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; - import lia.gsi.authz.LocalMappingAuthorization; - import org.globus.gsi.GSIConstants; import org.globus.gsi.gssapi.GSSConstants; import org.globus.gsi.gssapi.net.GssSocket; @@ -29,34 +19,43 @@ import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; +import java.io.IOException; +import java.net.*; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * This class provides the basics for writing various servers. Note: Sockets created by this server have a 5 * minute default timeout. The timeout can be changed using the {@link #setTimeout(int) setTimeout()} function. - * + * * @author Adrian Muraru */ public abstract class GSIBaseServer implements Runnable { private static final Logger logger = Logger.getLogger(GSIBaseServer.class.getName()); - /** Socket timeout in milliseconds. */ + /** + * Socket timeout in milliseconds. + */ private static final int SO_TIMEOUT = Integer.getInteger("GSI_SO_TIMEOUT", 5 * 60 * 1000); protected volatile boolean accept; protected ServerSocket _server = null; - - private Thread serverThread = null; - - private boolean secure = true; - protected String url = null; - protected GSSCredential credentials = null; - protected Integer gssMode = GSIConstants.MODE_SSL; - protected int timeout = SO_TIMEOUT; + /** + * A handler for the deactivation framework. + */ + protected AbstractServerDeactivator deactivator = null; + /** + * This method should be called by all subclasses. + */ + String authzClassName; + private Thread serverThread = null; + private boolean secure = true; public GSIBaseServer() throws IOException { this(null, 0); @@ -80,11 +79,6 @@ public GSIBaseServer(final boolean secure, final int port) throws IOException { initialize(); } - /** - * This method should be called by all subclasses. - */ - String authzClassName; - protected void initialize() { authzClassName = System.getProperty("gsi.authz.Authorization"); @@ -112,6 +106,10 @@ private LocalMappingAuthorization createAuthorizer() { } + public int getTimeout() { + return this.timeout; + } + /** * Sets timeout for the created sockets. By default if not set, 5 minute timeout is used. */ @@ -119,10 +117,6 @@ public void setTimeout(final int timeout) { this.timeout = timeout; } - public int getTimeout() { - return this.timeout; - } - /** * Stops the server but does not stop all the client threads */ @@ -178,7 +172,7 @@ public String getProtocol() { /** * Returns url of this server - * + * * @return url of this server */ public String getURL() { @@ -192,7 +186,7 @@ public String getURL() { /** * Returns port of this server - * + * * @return port number */ public int getPort() { @@ -201,7 +195,7 @@ public int getPort() { /** * Returns hostname of this server - * + * * @return hostname */ public String getHostname() { @@ -212,7 +206,7 @@ public String getHostname() { * Returns hostname of this server. The format of the host conforms to RFC 2732, i.e. for a literal IPv6 address, * this method will return the IPv6 address enclosed in square * brackets ('[' and ']'). - * + * * @return hostname */ public String getHost() { @@ -246,11 +240,11 @@ public void run() { error = true; break; } - - if(socket == null) { + + if (socket == null) { continue; } - + try { socket.setSoTimeout(getTimeout()); @@ -279,7 +273,7 @@ public void run() { } } } - + } logger.log(Level.WARNING, "server thread stopped"); @@ -341,11 +335,6 @@ public void unregisterDefaultDeactivator() { Deactivator.unregisterDeactivation(deactivator); } - /** - * A handler for the deactivation framework. - */ - protected AbstractServerDeactivator deactivator = null; - } class AbstractServerDeactivator implements DeactivationHandler { diff --git a/src/lia/gsi/net/GSIGssSocketFactory.java b/src/lia/gsi/net/GSIGssSocketFactory.java index 34c9031..6a419d2 100644 --- a/src/lia/gsi/net/GSIGssSocketFactory.java +++ b/src/lia/gsi/net/GSIGssSocketFactory.java @@ -15,176 +15,173 @@ */ package lia.gsi.net; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; - -import javax.security.auth.Subject; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.globus.common.CoGProperties; -import org.globus.gsi.*; +import org.globus.gsi.CredentialException; +import org.globus.gsi.GSIConstants; +import org.globus.gsi.GlobusCredentialException; +import org.globus.gsi.X509Credential; import org.globus.gsi.gssapi.GSSConstants; import org.globus.gsi.gssapi.GlobusGSSCredentialImpl; import org.globus.gsi.gssapi.JaasGssUtil; +import org.globus.gsi.gssapi.jaas.GlobusPrincipal; import org.globus.gsi.gssapi.net.GssSocket; import org.globus.gsi.gssapi.net.GssSocketFactory; import org.globus.gsi.gssapi.net.impl.GSIGssSocket; -import org.globus.gsi.gssapi.jaas.GlobusPrincipal; import org.gridforum.jgss.ExtendedGSSContext; import org.gridforum.jgss.ExtendedGSSManager; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSCredential; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; +import org.ietf.jgss.*; + +import javax.security.auth.Subject; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; // additional GSIGssSocketFactory that may be selected using // -Dorg.globus.gsi.gssapi.net.provider public class GSIGssSocketFactory extends GssSocketFactory { - private static final int GSI_CONNECT_TIMEOUT = Integer.getInteger("GSI_CONNECT_TIMEOUT", 20 * 1000); - - private static final Log logger = LogFactory.getLog(GSIGssSocketFactory.class.getName()); - - /** - * @param inetAddress : - * the remote address - * @param port : - * the remote port - * @param doDelegation : - * if true, the client credential is delegated - * @param fullDelegation : - * if doDelegation is set, this parameter specifies the type of delegation (FULL or LIMITED) - * @return the GSI-protected socket connected to the remote host - * @throws IOException - */ - public static java.net.Socket createSocket(java.net.InetAddress inetAddress, int port, boolean doDelegation, boolean fullDelegation) - throws IOException { - // raw socket - Socket socket = null; - try { - //override the search path for the CA directory: (GSI-SSHTERM writes in the ~/.globus/certificates so we don;t use this) - //1) java X509_CERT_DIR property - //2) override with X509_CERT_DIR env var - //3) default /etc/grid-security/certificates - String x509CertDir = System.getProperty("X509_CERT_DIR"); - if (x509CertDir == null) { - x509CertDir = System.getenv("X509_CERT_DIR"); - if (x509CertDir == null) - x509CertDir = "/etc/grid-security/certificates"; - System.setProperty("X509_CERT_DIR", x509CertDir); - } - - String x509UserProxy = System.getenv("X509_USER_PROXY"); - if (x509UserProxy == null) - x509UserProxy = CoGProperties.getDefault().getProxyFile(); - System.out.println("Trying " + x509UserProxy); - GSSCredential credential = createUserCredential(x509UserProxy); - if (credential == null) { - throw new IOException("User credential not initialized !"); - } - - logger.info("createSocket() user credential is " + credential.getName()); - GSSManager manager = ExtendedGSSManager.getInstance(); - org.globus.gsi.gssapi.auth.GSSAuthorization gssAuth = org.globus.gsi.gssapi.auth.HostAuthorization.getInstance(); - GSSName targetName = gssAuth.getExpectedName(null, inetAddress.getCanonicalHostName()); - ExtendedGSSContext context = (ExtendedGSSContext) manager.createContext(targetName, GSSConstants.MECH_OID, credential, - GSSContext.DEFAULT_LIFETIME); - context.setOption(GSSConstants.GSS_MODE, GSIConstants.MODE_GSI); - context.requestCredDeleg(doDelegation); - if (doDelegation) { - - if (fullDelegation) { - context.setOption(GSSConstants.DELEGATION_TYPE, GSIConstants.DELEGATION_TYPE_FULL); - } else { - context.setOption(GSSConstants.DELEGATION_TYPE, GSIConstants.DELEGATION_TYPE_LIMITED); - } - } - - SocketAddress socketAddress = new InetSocketAddress(inetAddress, port); - socket = new Socket(); - socket.connect(socketAddress, GSI_CONNECT_TIMEOUT); - GSIGssSocket gsiSocket = new GSIGssSocket(socket, context); - gsiSocket.setUseClientMode(true); - gsiSocket.setAuthorization(gssAuth); - // Should be GSI_MODE ? - gsiSocket.setWrapMode(GssSocket.SSL_MODE); - gsiSocket.startHandshake(); - socket = gsiSocket; - } catch (Throwable e) { - if (socket != null) { - try { - socket.close(); - } catch (Throwable ignore) { - } - } - throw new IOException(e); - } - // return the wrapped socket - return socket; - - } - - /** - * Retrieve the Globus Subject from a GssSocket - * - * @param socket - * @return javax.security.auth.Subject having a single Principal elementt : The Globus DN - * @throws GSSException - * if the supplied socket is not a GssSocket or the Globus Credentials is not set on the socket - */ - public static Subject getLocalSubject(Socket socket) throws GSSException { - - if (!(socket instanceof GssSocket)) - throw new GSSException(GSSException.NO_CRED); - - GssSocket gssSocket; - gssSocket = (GssSocket) socket; - Subject mySubject = new Subject(); - GlobusPrincipal nm; - try { - nm = JaasGssUtil.toGlobusPrincipal(gssSocket.getContext().getSrcName()); - } catch (Throwable t) { - throw new GSSException(GSSException.NO_CRED); - } - mySubject.getPrincipals().add(nm); - return mySubject; - } - - public Socket createSocket(Socket s, String host, int port, GSSContext context) { - return new GSIGssSocket(s, context); - } - - public Socket createSocket(String host, int port, GSSContext context) throws IOException { - return new GSIGssSocket(host, port, context); - } - - public static GSSCredential createUserCredential(String x509UserProxy) throws GlobusCredentialException, GSSException, CredentialException { - if (x509UserProxy != null) { - X509Credential gcred = new X509Credential(x509UserProxy); - GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); - return cred; - } - X509Credential gcred = X509Credential.getDefaultCredential(); - GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); - return cred; - - } - - public static GSSCredential createUserCredential(String x509ServiceCert, String x509ServiceKey) throws GlobusCredentialException, - GSSException, CredentialException, IOException { - if (x509ServiceCert != null && x509ServiceKey != null) { - X509Credential gcred = new X509Credential(x509ServiceCert, x509ServiceKey); - GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); - return cred; - } - - X509Credential gcred = X509Credential.getDefaultCredential(); - GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); - return cred; - } + private static final int GSI_CONNECT_TIMEOUT = Integer.getInteger("GSI_CONNECT_TIMEOUT", 20 * 1000); + + private static final Log logger = LogFactory.getLog(GSIGssSocketFactory.class.getName()); + + /** + * @param inetAddress : + * the remote address + * @param port : + * the remote port + * @param doDelegation : + * if true, the client credential is delegated + * @param fullDelegation : + * if doDelegation is set, this parameter specifies the type of delegation (FULL or LIMITED) + * @return the GSI-protected socket connected to the remote host + * @throws IOException + */ + public static java.net.Socket createSocket(java.net.InetAddress inetAddress, int port, boolean doDelegation, boolean fullDelegation) + throws IOException { + // raw socket + Socket socket = null; + try { + //override the search path for the CA directory: (GSI-SSHTERM writes in the ~/.globus/certificates so we don;t use this) + //1) java X509_CERT_DIR property + //2) override with X509_CERT_DIR env var + //3) default /etc/grid-security/certificates + String x509CertDir = System.getProperty("X509_CERT_DIR"); + if (x509CertDir == null) { + x509CertDir = System.getenv("X509_CERT_DIR"); + if (x509CertDir == null) + x509CertDir = "/etc/grid-security/certificates"; + System.setProperty("X509_CERT_DIR", x509CertDir); + } + + String x509UserProxy = System.getenv("X509_USER_PROXY"); + if (x509UserProxy == null) + x509UserProxy = CoGProperties.getDefault().getProxyFile(); + System.out.println("Trying " + x509UserProxy); + GSSCredential credential = createUserCredential(x509UserProxy); + if (credential == null) { + throw new IOException("User credential not initialized !"); + } + + logger.info("createSocket() user credential is " + credential.getName()); + GSSManager manager = ExtendedGSSManager.getInstance(); + org.globus.gsi.gssapi.auth.GSSAuthorization gssAuth = org.globus.gsi.gssapi.auth.HostAuthorization.getInstance(); + GSSName targetName = gssAuth.getExpectedName(null, inetAddress.getCanonicalHostName()); + ExtendedGSSContext context = (ExtendedGSSContext) manager.createContext(targetName, GSSConstants.MECH_OID, credential, + GSSContext.DEFAULT_LIFETIME); + context.setOption(GSSConstants.GSS_MODE, GSIConstants.MODE_GSI); + context.requestCredDeleg(doDelegation); + if (doDelegation) { + + if (fullDelegation) { + context.setOption(GSSConstants.DELEGATION_TYPE, GSIConstants.DELEGATION_TYPE_FULL); + } else { + context.setOption(GSSConstants.DELEGATION_TYPE, GSIConstants.DELEGATION_TYPE_LIMITED); + } + } + + SocketAddress socketAddress = new InetSocketAddress(inetAddress, port); + socket = new Socket(); + socket.connect(socketAddress, GSI_CONNECT_TIMEOUT); + GSIGssSocket gsiSocket = new GSIGssSocket(socket, context); + gsiSocket.setUseClientMode(true); + gsiSocket.setAuthorization(gssAuth); + // Should be GSI_MODE ? + gsiSocket.setWrapMode(GssSocket.SSL_MODE); + gsiSocket.startHandshake(); + socket = gsiSocket; + } catch (Throwable e) { + if (socket != null) { + try { + socket.close(); + } catch (Throwable ignore) { + } + } + throw new IOException(e); + } + // return the wrapped socket + return socket; + + } + + /** + * Retrieve the Globus Subject from a GssSocket + * + * @param socket + * @return javax.security.auth.Subject having a single Principal elementt : The Globus DN + * @throws GSSException if the supplied socket is not a GssSocket or the Globus Credentials is not set on the socket + */ + public static Subject getLocalSubject(Socket socket) throws GSSException { + + if (!(socket instanceof GssSocket)) + throw new GSSException(GSSException.NO_CRED); + + GssSocket gssSocket; + gssSocket = (GssSocket) socket; + Subject mySubject = new Subject(); + GlobusPrincipal nm; + try { + nm = JaasGssUtil.toGlobusPrincipal(gssSocket.getContext().getSrcName()); + } catch (Throwable t) { + throw new GSSException(GSSException.NO_CRED); + } + mySubject.getPrincipals().add(nm); + return mySubject; + } + + public static GSSCredential createUserCredential(String x509UserProxy) throws GlobusCredentialException, GSSException, CredentialException { + if (x509UserProxy != null) { + X509Credential gcred = new X509Credential(x509UserProxy); + GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); + return cred; + } + X509Credential gcred = X509Credential.getDefaultCredential(); + GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); + return cred; + + } + + public static GSSCredential createUserCredential(String x509ServiceCert, String x509ServiceKey) throws GlobusCredentialException, + GSSException, CredentialException, IOException { + if (x509ServiceCert != null && x509ServiceKey != null) { + X509Credential gcred = new X509Credential(x509ServiceCert, x509ServiceKey); + GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); + return cred; + } + + X509Credential gcred = X509Credential.getDefaultCredential(); + GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); + return cred; + } + + public Socket createSocket(Socket s, String host, int port, GSSContext context) { + return new GSIGssSocket(s, context); + } + + public Socket createSocket(String host, int port, GSSContext context) throws IOException { + return new GSIGssSocket(host, port, context); + } } diff --git a/src/lia/gsi/net/Peer.java b/src/lia/gsi/net/Peer.java index 80d993a..ed88dd0 100644 --- a/src/lia/gsi/net/Peer.java +++ b/src/lia/gsi/net/Peer.java @@ -3,31 +3,31 @@ */ package lia.gsi.net; -import java.net.Socket; +import lia.gsi.authz.LocalMappingAuthorization; import javax.security.auth.Subject; - -import lia.gsi.authz.LocalMappingAuthorization; +import java.net.Socket; /** * Extended Socket with an optional Subject attribute. The Subject field may contain additional Principal such as: GridID,UserLocalPrincipal + * * @author Adrian Muraru */ public class Peer { - private final Socket socket; - private final LocalMappingAuthorization authorization; - - public Peer(Socket socket, LocalMappingAuthorization authz) { - this.socket=socket; - this.authorization = authz; - } - - public Socket getSocket() { - return this.socket; - } - - public Subject getPeerSubject() { - return this.authorization==null? null: this.authorization.getPeerSubject(); - } - + private final Socket socket; + private final LocalMappingAuthorization authorization; + + public Peer(Socket socket, LocalMappingAuthorization authz) { + this.socket = socket; + this.authorization = authz; + } + + public Socket getSocket() { + return this.socket; + } + + public Subject getPeerSubject() { + return this.authorization == null ? null : this.authorization.getPeerSubject(); + } + } \ No newline at end of file diff --git a/src/lia/gsi/ssh/GSIAuthenticationClient.java b/src/lia/gsi/ssh/GSIAuthenticationClient.java index be44cef..5da6110 100644 --- a/src/lia/gsi/ssh/GSIAuthenticationClient.java +++ b/src/lia/gsi/ssh/GSIAuthenticationClient.java @@ -23,8 +23,8 @@ import org.globus.common.CoGProperties; import org.globus.gsi.CredentialException; import org.globus.gsi.GSIConstants; -import org.globus.gsi.X509Credential; import org.globus.gsi.GlobusCredentialException; +import org.globus.gsi.X509Credential; import org.globus.gsi.gssapi.GSSConstants; import org.globus.gsi.gssapi.GlobusGSSCredentialImpl; import org.globus.gsi.gssapi.GlobusGSSManagerImpl; @@ -40,155 +40,155 @@ public class GSIAuthenticationClient extends SshAuthenticationClient { - private static Logger logger = Logger.getLogger(GSIAuthenticationClient.class.getName()); - - GSSCredential gsscredential; - - public GSIAuthenticationClient() throws GSSException, IOException { - - //override the search path for the CA directory: (GSI-SSHTERM writes in the ~/.globus/certificates so we don;t use this) - //1) java X509_CERT_DIR property - //2) override with X509_CERT_DIR env var - //3) default /etc/grid-security/certificates - String x509CertDir = System.getProperty("X509_CERT_DIR"); - if (x509CertDir == null) { - x509CertDir = System.getenv("X509_CERT_DIR"); - if (x509CertDir == null) - x509CertDir = "/etc/grid-security/certificates"; - System.setProperty("X509_CERT_DIR", x509CertDir); - } - - String x509UserProxy = System.getProperty("X509_USER_PROXY"); - if (x509UserProxy == null) { - x509UserProxy = System.getenv("X509_USER_PROXY"); - if (x509UserProxy != null) - System.setProperty("X509_USER_PROXY", x509UserProxy); - } - if (x509UserProxy == null) - x509UserProxy = CoGProperties.getDefault().getProxyFile(); - if (!new File(x509UserProxy).isFile()) - throw new IOException("User proxy certificate not found in environment"); - - logger.info("Using proxy certificate:" + x509UserProxy); - - try { - gsscredential = createUserCredential(x509UserProxy); - } catch (GlobusCredentialException | CredentialException e) { - throw new IOException("Could not load user proxy certificate from:" + x509UserProxy); - } - if (gsscredential == null) { - throw new IOException("User credential not initialized !Could not load user proxy certificate. Check your environmen if you have X509_USER_CERT proxy set up"); - } - } - - public final String getMethodName() { - return "gssapi"; - } - - public void reset() { - } - - public void authenticate(AuthenticationProtocolClient authenticationprotocolclient, String s) throws IOException, TerminatedStateException { - try { - logger.finest("Registering gss-ssh return messages."); - authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiResponse.class, 60); - authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiToken.class, 61); - authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiError.class, 64); - authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiErrtok.class, 65); - logger.finest("Sending gssapi user auth request."); - ByteArrayWriter bytearraywriter = new ByteArrayWriter(); - bytearraywriter.writeUINT32(new UnsignedInteger32(1L)); - byte abyte0[] = GSSConstants.MECH_OID.getDER(); - bytearraywriter.writeBinaryString(abyte0); - logger.finest("Username:" + getUsername()); - SshMsgUserAuthRequest sshmsguserauthrequest = new SshMsgUserAuthRequest(getUsername(), s, "gssapi", bytearraywriter.toByteArray()); - authenticationprotocolclient.sendMessage(sshmsguserauthrequest); - logger.finest("Receiving user auth response:"); - SshMsgUserauthGssapiResponse sshmsguserauthgssapiresponse = (SshMsgUserauthGssapiResponse) authenticationprotocolclient.readMessage(60); - ByteArrayReader bytearrayreader = new ByteArrayReader(sshmsguserauthgssapiresponse.getRequestData()); - byte abyte1[] = bytearrayreader.readBinaryString(); - if (logger.isLoggable(Level.FINEST)) { - logger.log(Level.FINEST, "Mechanism requested: " + GSSConstants.MECH_OID); - logger.log(Level.FINEST, "Mechanism selected: " + new Oid(abyte1)); - logger.log(Level.FINEST, "Verify that selected mechanism is GSSAPI."); - } - if (!GSSConstants.MECH_OID.equals(new Oid(abyte1))) { - logger.warning("Mechanism do not match!"); - throw new IOException("Mechanism do not match!"); - } - logger.finest("Creating GSS context base on grid credentials."); - GlobusGSSManagerImpl globusgssmanagerimpl = new GlobusGSSManagerImpl(); - - HostAuthorization gssAuth = new HostAuthorization(null); - GSSName targetName = gssAuth.getExpectedName(null, hostname); - - GSSContext gsscontext = globusgssmanagerimpl.createContext(targetName, new Oid(abyte1), gsscredential, GSSCredential.INDEFINITE_LIFETIME - 1); - gsscontext.requestCredDeleg(true); - gsscontext.requestMutualAuth(true); - gsscontext.requestReplayDet(true); - gsscontext.requestSequenceDet(true); - // MOD - // gsscontext.requestConf(false); - gsscontext.requestConf(true); - - Object type = GSIConstants.DELEGATION_TYPE_LIMITED; - gsscontext.requestCredDeleg(false); - ((ExtendedGSSContext) gsscontext).setOption(GSSConstants.DELEGATION_TYPE, type); - - logger.finest("Starting GSS token exchange."); - byte abyte2[] = new byte[0]; - do { - if (gsscontext.isEstablished()) - break; - byte abyte3[] = gsscontext.initSecContext(abyte2, 0, abyte2.length); - if (abyte3 != null) { - ByteArrayWriter bytearraywriter1 = new ByteArrayWriter(); - bytearraywriter1.writeBinaryString(abyte3); - SshMsgUserauthGssapiToken sshmsguserauthgssapitoken = new SshMsgUserauthGssapiToken(bytearraywriter1.toByteArray()); - authenticationprotocolclient.sendMessage(sshmsguserauthgssapitoken); - } - if (!gsscontext.isEstablished()) { - SshMsgUserauthGssapiToken sshmsguserauthgssapitoken1 = (SshMsgUserauthGssapiToken) authenticationprotocolclient.readMessage(61); - ByteArrayReader bytearrayreader1 = new ByteArrayReader(sshmsguserauthgssapitoken1.getRequestData()); - abyte2 = bytearrayreader1.readBinaryString(); - } - } while (true); - logger.log(Level.FINEST, "Sending gssapi exchange complete."); - SshMsgUserauthGssapiExchangeComplete sshmsguserauthgssapiexchangecomplete = new SshMsgUserauthGssapiExchangeComplete(); - authenticationprotocolclient.sendMessage(sshmsguserauthgssapiexchangecomplete); - if (logger.isLoggable(Level.FINEST)) { - logger.log(Level.FINEST, "Context established.\nInitiator : " + gsscontext.getSrcName() + "\nAcceptor : " + gsscontext.getTargName() + "\nLifetime : " - + gsscontext.getLifetime() + "\nIntegrity : " + gsscontext.getIntegState() + "\nConfidentiality : " + gsscontext.getConfState() + "\nAnonymity : " - + gsscontext.getAnonymityState()); - } - } catch (Throwable t) { - logger.log(Level.WARNING,"Got Exception: ", t); - throw new TerminatedStateException(AuthenticationProtocolState.FAILED); - } - } - - public static GSSCredential createUserCredential(String x509UserProxy) throws GlobusCredentialException, GSSException, CredentialException { - if (x509UserProxy != null) { - X509Credential gcred = new X509Credential(x509UserProxy); - GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); - return cred; - } - X509Credential gcred = X509Credential.getDefaultCredential(); - GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); - return cred; - - } - - public Properties getPersistableProperties() { - Properties properties = new Properties(); - return properties; - } - - public void setPersistableProperties(Properties properties) { - } - - public boolean canAuthenticate() { - return true; - } + private static Logger logger = Logger.getLogger(GSIAuthenticationClient.class.getName()); + + GSSCredential gsscredential; + + public GSIAuthenticationClient() throws GSSException, IOException { + + //override the search path for the CA directory: (GSI-SSHTERM writes in the ~/.globus/certificates so we don;t use this) + //1) java X509_CERT_DIR property + //2) override with X509_CERT_DIR env var + //3) default /etc/grid-security/certificates + String x509CertDir = System.getProperty("X509_CERT_DIR"); + if (x509CertDir == null) { + x509CertDir = System.getenv("X509_CERT_DIR"); + if (x509CertDir == null) + x509CertDir = "/etc/grid-security/certificates"; + System.setProperty("X509_CERT_DIR", x509CertDir); + } + + String x509UserProxy = System.getProperty("X509_USER_PROXY"); + if (x509UserProxy == null) { + x509UserProxy = System.getenv("X509_USER_PROXY"); + if (x509UserProxy != null) + System.setProperty("X509_USER_PROXY", x509UserProxy); + } + if (x509UserProxy == null) + x509UserProxy = CoGProperties.getDefault().getProxyFile(); + if (!new File(x509UserProxy).isFile()) + throw new IOException("User proxy certificate not found in environment"); + + logger.info("Using proxy certificate:" + x509UserProxy); + + try { + gsscredential = createUserCredential(x509UserProxy); + } catch (GlobusCredentialException | CredentialException e) { + throw new IOException("Could not load user proxy certificate from:" + x509UserProxy); + } + if (gsscredential == null) { + throw new IOException("User credential not initialized !Could not load user proxy certificate. Check your environmen if you have X509_USER_CERT proxy set up"); + } + } + + public static GSSCredential createUserCredential(String x509UserProxy) throws GlobusCredentialException, GSSException, CredentialException { + if (x509UserProxy != null) { + X509Credential gcred = new X509Credential(x509UserProxy); + GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); + return cred; + } + X509Credential gcred = X509Credential.getDefaultCredential(); + GSSCredential cred = new GlobusGSSCredentialImpl(gcred, GSSCredential.INITIATE_ONLY); + return cred; + + } + + public final String getMethodName() { + return "gssapi"; + } + + public void reset() { + } + + public void authenticate(AuthenticationProtocolClient authenticationprotocolclient, String s) throws IOException, TerminatedStateException { + try { + logger.finest("Registering gss-ssh return messages."); + authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiResponse.class, 60); + authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiToken.class, 61); + authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiError.class, 64); + authenticationprotocolclient.registerMessage(com.sshtools.j2ssh.authentication.SshMsgUserauthGssapiErrtok.class, 65); + logger.finest("Sending gssapi user auth request."); + ByteArrayWriter bytearraywriter = new ByteArrayWriter(); + bytearraywriter.writeUINT32(new UnsignedInteger32(1L)); + byte abyte0[] = GSSConstants.MECH_OID.getDER(); + bytearraywriter.writeBinaryString(abyte0); + logger.finest("Username:" + getUsername()); + SshMsgUserAuthRequest sshmsguserauthrequest = new SshMsgUserAuthRequest(getUsername(), s, "gssapi", bytearraywriter.toByteArray()); + authenticationprotocolclient.sendMessage(sshmsguserauthrequest); + logger.finest("Receiving user auth response:"); + SshMsgUserauthGssapiResponse sshmsguserauthgssapiresponse = (SshMsgUserauthGssapiResponse) authenticationprotocolclient.readMessage(60); + ByteArrayReader bytearrayreader = new ByteArrayReader(sshmsguserauthgssapiresponse.getRequestData()); + byte abyte1[] = bytearrayreader.readBinaryString(); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Mechanism requested: " + GSSConstants.MECH_OID); + logger.log(Level.FINEST, "Mechanism selected: " + new Oid(abyte1)); + logger.log(Level.FINEST, "Verify that selected mechanism is GSSAPI."); + } + if (!GSSConstants.MECH_OID.equals(new Oid(abyte1))) { + logger.warning("Mechanism do not match!"); + throw new IOException("Mechanism do not match!"); + } + logger.finest("Creating GSS context base on grid credentials."); + GlobusGSSManagerImpl globusgssmanagerimpl = new GlobusGSSManagerImpl(); + + HostAuthorization gssAuth = new HostAuthorization(null); + GSSName targetName = gssAuth.getExpectedName(null, hostname); + + GSSContext gsscontext = globusgssmanagerimpl.createContext(targetName, new Oid(abyte1), gsscredential, GSSCredential.INDEFINITE_LIFETIME - 1); + gsscontext.requestCredDeleg(true); + gsscontext.requestMutualAuth(true); + gsscontext.requestReplayDet(true); + gsscontext.requestSequenceDet(true); + // MOD + // gsscontext.requestConf(false); + gsscontext.requestConf(true); + + Object type = GSIConstants.DELEGATION_TYPE_LIMITED; + gsscontext.requestCredDeleg(false); + ((ExtendedGSSContext) gsscontext).setOption(GSSConstants.DELEGATION_TYPE, type); + + logger.finest("Starting GSS token exchange."); + byte abyte2[] = new byte[0]; + do { + if (gsscontext.isEstablished()) + break; + byte abyte3[] = gsscontext.initSecContext(abyte2, 0, abyte2.length); + if (abyte3 != null) { + ByteArrayWriter bytearraywriter1 = new ByteArrayWriter(); + bytearraywriter1.writeBinaryString(abyte3); + SshMsgUserauthGssapiToken sshmsguserauthgssapitoken = new SshMsgUserauthGssapiToken(bytearraywriter1.toByteArray()); + authenticationprotocolclient.sendMessage(sshmsguserauthgssapitoken); + } + if (!gsscontext.isEstablished()) { + SshMsgUserauthGssapiToken sshmsguserauthgssapitoken1 = (SshMsgUserauthGssapiToken) authenticationprotocolclient.readMessage(61); + ByteArrayReader bytearrayreader1 = new ByteArrayReader(sshmsguserauthgssapitoken1.getRequestData()); + abyte2 = bytearrayreader1.readBinaryString(); + } + } while (true); + logger.log(Level.FINEST, "Sending gssapi exchange complete."); + SshMsgUserauthGssapiExchangeComplete sshmsguserauthgssapiexchangecomplete = new SshMsgUserauthGssapiExchangeComplete(); + authenticationprotocolclient.sendMessage(sshmsguserauthgssapiexchangecomplete); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Context established.\nInitiator : " + gsscontext.getSrcName() + "\nAcceptor : " + gsscontext.getTargName() + "\nLifetime : " + + gsscontext.getLifetime() + "\nIntegrity : " + gsscontext.getIntegState() + "\nConfidentiality : " + gsscontext.getConfState() + "\nAnonymity : " + + gsscontext.getAnonymityState()); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Got Exception: ", t); + throw new TerminatedStateException(AuthenticationProtocolState.FAILED); + } + } + + public Properties getPersistableProperties() { + Properties properties = new Properties(); + return properties; + } + + public void setPersistableProperties(Properties properties) { + } + + public boolean canAuthenticate() { + return true; + } } diff --git a/src/lia/gsi/ssh/TextSSHClient.java b/src/lia/gsi/ssh/TextSSHClient.java index 3edf07d..82c459f 100644 --- a/src/lia/gsi/ssh/TextSSHClient.java +++ b/src/lia/gsi/ssh/TextSSHClient.java @@ -3,64 +3,64 @@ */ package lia.gsi.ssh; -import java.io.BufferedReader; -import java.io.InputStreamReader; - import com.sshtools.common.configuration.SshToolsConnectionProfile; import com.sshtools.j2ssh.SshClient; import com.sshtools.j2ssh.authentication.AuthenticationProtocolState; import com.sshtools.j2ssh.session.SessionChannelClient; +import java.io.BufferedReader; +import java.io.InputStreamReader; + /** * @author Adrian Muraru */ public class TextSSHClient { - public static void main(String[] args) throws Exception { - GSIAuthenticationClient gsiAuth = null; - try { - gsiAuth = new GSIAuthenticationClient(); - gsiAuth.setUsername(args[1]); - } catch (Exception e) { - System.err.println("Cannot load grid credentials."); - e.printStackTrace(); - return; - } - System.out.println("Local GSI Credential loaded."); + public static void main(String[] args) throws Exception { + GSIAuthenticationClient gsiAuth = null; + try { + gsiAuth = new GSIAuthenticationClient(); + gsiAuth.setUsername(args[1]); + } catch (Exception e) { + System.err.println("Cannot load grid credentials."); + e.printStackTrace(); + return; + } + System.out.println("Local GSI Credential loaded."); - SshClient ssh = new SshClient(); - SshToolsConnectionProfile properties = new SshToolsConnectionProfile(); - properties.setPort(1975); - properties.setForwardingAutoStartMode(false); - properties.setHost(args[0]); - properties.setUsername(args[1]); - ssh.setUseDefaultForwarding(false); - ssh.connect(properties); - System.out.println("Available methods:" + ssh.getAvailableAuthMethods(args[1])); - try { - // Authenticate the user - int result = ssh.authenticate(gsiAuth, args[0]); - if (result != AuthenticationProtocolState.COMPLETE) { - // Authentication complete - System.out.println("Auth failed:" + result); - return; - } - // Open a session channel - SessionChannelClient session = ssh.openSessionChannel(); - session.requestPseudoTerminal("ansi", 0, 0, 0, 0, ""); - if (!session.executeCommand(args[2])){ - System.out.println("Command failed"); - ssh.disconnect(); - return; - } - BufferedReader bfr = new BufferedReader(new InputStreamReader(session.getInputStream())); - String line; - while ((line = bfr.readLine()) != null) - System.out.println(line); - } catch (Exception e) { - e.printStackTrace(); - ssh.disconnect(); - } - } + SshClient ssh = new SshClient(); + SshToolsConnectionProfile properties = new SshToolsConnectionProfile(); + properties.setPort(1975); + properties.setForwardingAutoStartMode(false); + properties.setHost(args[0]); + properties.setUsername(args[1]); + ssh.setUseDefaultForwarding(false); + ssh.connect(properties); + System.out.println("Available methods:" + ssh.getAvailableAuthMethods(args[1])); + try { + // Authenticate the user + int result = ssh.authenticate(gsiAuth, args[0]); + if (result != AuthenticationProtocolState.COMPLETE) { + // Authentication complete + System.out.println("Auth failed:" + result); + return; + } + // Open a session channel + SessionChannelClient session = ssh.openSessionChannel(); + session.requestPseudoTerminal("ansi", 0, 0, 0, 0, ""); + if (!session.executeCommand(args[2])) { + System.out.println("Command failed"); + ssh.disconnect(); + return; + } + BufferedReader bfr = new BufferedReader(new InputStreamReader(session.getInputStream())); + String line; + while ((line = bfr.readLine()) != null) + System.out.println(line); + } catch (Exception e) { + e.printStackTrace(); + ssh.disconnect(); + } + } } diff --git a/src/lia/util/net/common/AbstractBPool.java b/src/lia/util/net/common/AbstractBPool.java index 86afca6..17fa008 100644 --- a/src/lia/util/net/common/AbstractBPool.java +++ b/src/lia/util/net/common/AbstractBPool.java @@ -17,12 +17,14 @@ /** * This class should unify both header and payload buffers - * + * * @author ramiro */ public abstract class AbstractBPool { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final transient Logger logger = Logger.getLogger(AbstractBPool.class.getName()); protected final int bufferSize; @@ -30,16 +32,11 @@ public abstract class AbstractBPool { protected final int maxPollIter; protected final BlockingQueue thePool; - - private final boolean trackAllocations; - - private final IdentityHashMap mapTrack = new IdentityHashMap(); - protected final AtomicBoolean limitReached = new AtomicBoolean(false); - protected final AtomicInteger poolSize = new AtomicInteger(0); - protected final boolean randomGen; + private final boolean trackAllocations; + private final IdentityHashMap mapTrack = new IdentityHashMap(); public AbstractBPool(int bufferSize, int maxPollIter) { this(bufferSize, maxPollIter, false); @@ -235,7 +232,7 @@ public ByteBuffer poll() { public ByteBuffer poll(long timeout, TimeUnit unit) throws InterruptedException { final boolean logFinest = logger.isLoggable(Level.FINEST); final boolean logFiner = logFinest || logger.isLoggable(Level.FINER); - + ByteBuffer retBuff = thePool.poll(); try { @@ -280,7 +277,7 @@ public ByteBuffer poll(long timeout, TimeUnit unit) throws InterruptedException public boolean put(ByteBuffer buff) { final boolean logFinest = logger.isLoggable(Level.FINEST); final boolean logFiner = logFinest || logger.isLoggable(Level.FINER); - + if (logFiner) { StringBuilder sb = new StringBuilder(); sb.append(" PUT BACK TO POOL: buffer: ").append(Utils.buffToString(buff)); @@ -306,7 +303,7 @@ public boolean put(ByteBuffer buff) { } // test and clear the interrupted flag - for (;;) { + for (; ; ) { final boolean isInterrupted = Thread.interrupted(); try { final boolean returned = thePool.offer(buff); diff --git a/src/lia/util/net/common/AbstractFDTCloseable.java b/src/lia/util/net/common/AbstractFDTCloseable.java index da2a02f..0f33372 100644 --- a/src/lia/util/net/common/AbstractFDTCloseable.java +++ b/src/lia/util/net/common/AbstractFDTCloseable.java @@ -9,30 +9,17 @@ import java.util.logging.Logger; /** - * - * Convenient class which implements FDTCloseable. It uses also a thread to + * Convenient class which implements FDTCloseable. It uses also a thread to * notify the {@link internalClose} for all classes which extended this class * * @author ramiro - * */ public abstract class AbstractFDTCloseable implements FDTCloseable { - /** Logger used by this class */ - private static final Logger logger = Logger.getLogger(AbstractFDTCloseable.class.getName()); - /** - * The lock can be used by subclasses to synchronize the access to - * closed field - * - * internalClose is called with this lock taken + * Logger used by this class */ - protected final Object closeLock = new Object(); - - protected volatile boolean closed; - private volatile String downMessage; - private volatile Throwable downCause; - + private static final Logger logger = Logger.getLogger(AbstractFDTCloseable.class.getName()); //helper thread used to notify internalClose in an async way private static final AsynchronousCloseThread closer; @@ -53,48 +40,15 @@ public abstract class AbstractFDTCloseable implements FDTCloseable { } /** - * - * Helper thread to perform all internalClose notifications in an asynchronous fashion - * It should respect the "trace" of the close inside the FDT app - * - * @author ramiro + * The lock can be used by subclasses to synchronize the access to + * closed field + *

    + * internalClose is called with this lock taken */ - private static final class AsynchronousCloseThread extends Thread { - - BlockingQueue workingQueue; - - private AsynchronousCloseThread() { - workingQueue = new LinkedBlockingQueue<>(); - this.setDaemon(true); - this.setName(" AsyncCloseThread [ " + workingQueue.size() + " ]"); - } - - public void run() { - AbstractFDTCloseable closeable = null; - - for (;;) { - try { - this.setName(" AsyncCloseThread waiting to take wqSize: " + workingQueue.size()); - - closeable = null; - closeable = workingQueue.take(); - - this.setName(" AsyncCloseThread CLOSING [ " + closeable + " ] wqSize: " + workingQueue.size()); - - //internalClose() MUST be called with closeLock taken! - synchronized (closeable.closeLock) { - closeable.internalClose(); - } - - } catch (InterruptedException ie) { - logger.log(Level.WARNING, "[ AsynchronousCloseThread ] [ HANDLED ] Got InterruptedException on task [ " + closeable + " ] Exc:", ie); - Thread.interrupted(); - } catch (Throwable t) { - logger.log(Level.WARNING, "[ AsynchronousCloseThread ] [ HANDLED ] Got generic exception on task [ " + closeable + " ] Exc:", t); - } - } - } - } + protected final Object closeLock = new Object(); + protected volatile boolean closed; + private volatile String downMessage; + private volatile Throwable downCause; //TODO - It is safe, but it is deadlock proned // Probably this the classes must be instantiated only once; @@ -144,4 +98,47 @@ public Throwable downCause() { * this is called with closeLock taken */ protected abstract void internalClose() throws Exception; + + /** + * Helper thread to perform all internalClose notifications in an asynchronous fashion + * It should respect the "trace" of the close inside the FDT app + * + * @author ramiro + */ + private static final class AsynchronousCloseThread extends Thread { + + BlockingQueue workingQueue; + + private AsynchronousCloseThread() { + workingQueue = new LinkedBlockingQueue<>(); + this.setDaemon(true); + this.setName(" AsyncCloseThread [ " + workingQueue.size() + " ]"); + } + + public void run() { + AbstractFDTCloseable closeable = null; + + for (; ; ) { + try { + this.setName(" AsyncCloseThread waiting to take wqSize: " + workingQueue.size()); + + closeable = null; + closeable = workingQueue.take(); + + this.setName(" AsyncCloseThread CLOSING [ " + closeable + " ] wqSize: " + workingQueue.size()); + + //internalClose() MUST be called with closeLock taken! + synchronized (closeable.closeLock) { + closeable.internalClose(); + } + + } catch (InterruptedException ie) { + logger.log(Level.WARNING, "[ AsynchronousCloseThread ] [ HANDLED ] Got InterruptedException on task [ " + closeable + " ] Exc:", ie); + Thread.interrupted(); + } catch (Throwable t) { + logger.log(Level.WARNING, "[ AsynchronousCloseThread ] [ HANDLED ] Got generic exception on task [ " + closeable + " ] Exc:", t); + } + } + } + } } diff --git a/src/lia/util/net/common/AbstractFDTIOEntity.java b/src/lia/util/net/common/AbstractFDTIOEntity.java index c526d5c..85c15a3 100644 --- a/src/lia/util/net/common/AbstractFDTIOEntity.java +++ b/src/lia/util/net/common/AbstractFDTIOEntity.java @@ -3,26 +3,30 @@ */ package lia.util.net.common; -import java.util.concurrent.atomic.AtomicLong; - import lia.util.net.copy.Accountable; +import java.util.concurrent.atomic.AtomicLong; + /** * Abstract class for all other classes inside FDT which may be {@link Accountable} * and {@link FDTCloseable}in the same time - * + * * @author ramiro */ public abstract class AbstractFDTIOEntity extends AbstractFDTCloseable implements Accountable { private final AtomicLong totalProcessedBytes; private final AtomicLong totalUtilBytes; - - public AbstractFDTIOEntity(long initialProcessedBytes, long initialUtilBytes ) { + + public AbstractFDTIOEntity(long initialProcessedBytes, long initialUtilBytes) { totalProcessedBytes = new AtomicLong(initialProcessedBytes); totalUtilBytes = new AtomicLong(initialUtilBytes); } - + + public AbstractFDTIOEntity() { + this(0, 0); + } + public long addAndGetTotalBytes(long delta) { return totalProcessedBytes.addAndGet(delta); } @@ -39,8 +43,4 @@ public long getUtilBytes() { return totalUtilBytes.get(); } - public AbstractFDTIOEntity() { - this(0, 0); - } - } diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 2b94190..0ed9096 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -29,34 +29,9 @@ */ public class Config { - /** - * Logger used by this class - */ - private static final Logger logger = Logger.getLogger("lia.util.net.common.Config"); // The size of the buffer which is sent over the wire! // TODO make this a parameter public static final int NETWORK_BUFF_LEN_SIZE; - - static { - int defaultMSSSize; - int minMTU; - try { - minMTU = getMinMTU(); - defaultMSSSize = minMTU - 40; - } catch (Throwable ignore) { - defaultMSSSize = 1460; - } - - if (defaultMSSSize < 1000) { - defaultMSSSize = 1460; - } - - NETWORK_BUFF_LEN_SIZE = defaultMSSSize; - } - - // env props which will be sent to remote peer - private final static String[] exportedSysProps = {"user.name", "user.home", "user.dir", "file.separator", - "file.encoding", "path.separator"}; // public static final String SINGLE_CMDLINE_ARGS[] = { "-S", "-pull", "-N", "-gsi", "-bio", "-r", "-fbs", "-ll", // "-loop", "-enableLisaRestart", "-md5", "-printStats", "-gsissh", "-noupdates", "-silent"}; public static final String[] SINGLE_CMDLINE_ARGS = {"-v", "-vv", "-vvv", "-loop", "-r", "-pull", "-printStats", @@ -66,13 +41,11 @@ public class Config { "-writeMode", "-lisa_rep_delay", "-apmon_rep_delay", "-fl", "-reportDelay", "-ka"}; public static final String POSSIBLE_VALUE_CMDLINE_ARGS[] = {"-enable_apmon", "-lisafdtclient", "-lisafdtserver", "-f", "-F", "-h", "-H", "--help", "-help," + "-u", "-U", "--update", "-update"}; - /** * used in conjuction with -fl to delimit the eventual destination file name * e.g. {@code /orginal/file/name / /destination/file/name} */ public static final String REGEX_REMAP_DELIMITER = "(\\s)+/(\\s)+"; - // all of this are set by the ant script public static final String FDT_MAJOR_VERSION = "0"; public static final String FDT_MINOR_VERSION = "25"; @@ -80,7 +53,6 @@ public class Config { public static final String FDT_FULL_VERSION = FDT_MAJOR_VERSION + "." + FDT_MINOR_VERSION + "." + FDT_MAINTENANCE_VERSION; public static final String FDT_RELEASE_DATE = "2017-04-20"; - private volatile static Config _thisInstance; // the size of header packet sent over the wire - // TODO - this should be dynamic ... or not ( performance resons ?! ) public static final int HEADER_SIZE = 56; @@ -89,23 +61,83 @@ public class Config { public static final int KILO = 1024; // 1 MByte public static final int DEFAULT_BUFFER_SIZE = KILO * KILO; // 1MB - private int byteBufferSize = DEFAULT_BUFFER_SIZE; - // default will be false - private final boolean isNagleEnabled; - // shall I get the data from server? - used only by the client - private boolean isPullMode = false; - private boolean isCoordinatorMode; - private boolean isRetrievingLogFile; - private boolean isThirdPartyCopyAgent; // this should be used for syncronizations at application level () public static final Object BIG_FDTAPP_LOCK = new Object(); // default is 4 public static final int DEFAULT_SOCKET_NO = 4; - private int sockNum = DEFAULT_SOCKET_NO; public static final int DEFAULT_PORT_NO = 43210; public static final long DEFAULT_KEEP_ALIVE_NANOS = TimeUnit.MINUTES.toNanos(2); public static final int DEFAULT_PORT_NO_GSI = 54320; public static final int DEFAULT_PORT_NO_SSH = 22; + /** + * Check if remote server is needed. We use SSH channels to control remote startup.
    + * In SSH/SCP mode we have three types of syntax we need to support: + *

      + *
    • fdt /local/path [user]@remotehost:/remote/path :
      + * In this case a remote server si started on remote host and client starts in "PUSH" mode The server accept + * connection just from the the given client and exits when the transfer finishes + *
    • fdt /local/path [user]@remotehost:/remote/path :
      + * In this case a remote server si started on remote host and client starts in "PULL" mode The server accept + * connection just from the the given client and exits when the transfer finishes + *
    • fdt [user]@remotehost1:/remote/path1 [user]@remotehost2:/remote/path2 :
      + * In this case both the server and the client are started remotely (the local fdt acts as an agent for the transfer + *
    + */ + // different client/server configuration set using SSH + public static final int SSH_NO_REMOTE = -1; + // REMOTE server, local client in push mode + public static final int SSH_REMOTE_SERVER_LOCAL_CLIENT_PUSH = 1; + // REMOTE server, local client in pull mode + public static final int SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL = 2; + // REMOTE server, REMOTE client in push mode) + public static final int SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH = 3; + /** + * Logger used by this class + */ + private static final Logger logger = Logger.getLogger("lia.util.net.common.Config"); + // env props which will be sent to remote peer + private final static String[] exportedSysProps = {"user.name", "user.home", "user.dir", "file.separator", + "file.encoding", "path.separator"}; + private volatile static Config _thisInstance; + + static { + int defaultMSSSize; + int minMTU; + try { + minMTU = getMinMTU(); + defaultMSSSize = minMTU - 40; + } catch (Throwable ignore) { + defaultMSSSize = 1460; + } + + if (defaultMSSSize < 1000) { + defaultMSSSize = 1460; + } + + NETWORK_BUFF_LEN_SIZE = defaultMSSSize; + } + + // default will be false + private final boolean isNagleEnabled; + private final boolean isStandAlone; + private final String sshKeyPath; + private final String apMonHosts; + private final boolean isLisaRestartEnabled; + private final String writeMode; + private final String preFilters; + private final String postFilters; + private final String monID; + private final boolean isNetTest; + private final boolean isGenTest; + private final long keepAliveDelayNanos; + private final FileChannelProviderFactory fileChannelProviderFactory; + private int byteBufferSize = DEFAULT_BUFFER_SIZE; + // shall I get the data from server? - used only by the client + private boolean isPullMode = false; + private boolean isCoordinatorMode; + private boolean isRetrievingLogFile; + private boolean isThirdPartyCopyAgent; + private int sockNum = DEFAULT_SOCKET_NO; private int sockBufSize = -1; private long rateLimit = -1; private long rateLimitDelayMillis = 300L; @@ -121,15 +153,12 @@ public class Config { private int destPort; private int remoteTransferPort; private ArrayBlockingQueue transportPorts; - private final boolean isStandAlone; private String[] fileList; private String[] remappedFileList; private String destDir; private String listFilesFrom; private String sIP; private String dIP; - private final String sshKeyPath; - private final String apMonHosts; private boolean bComputeMD5 = false; private boolean bRecursive = false; private boolean bCheckUpdate = false; @@ -152,11 +181,6 @@ public class Config { private String sDestinationUser = null; private String sLocalAddresses = null; private String sStartServerCommand = null; - private final boolean isLisaRestartEnabled; - private final String writeMode; - private final String preFilters; - private final String postFilters; - private final String monID; private String logLevel; private String massStorageConfig = null; private String massStorageType = null; @@ -166,66 +190,9 @@ public class Config { private boolean isNoTmpFlagSet = false; private boolean isNoLockFlagSet = false; private long consoleReportingTaskDelay = 5; - private final boolean isNetTest; - private final boolean isGenTest; - private final long keepAliveDelayNanos; - private final FileChannelProviderFactory fileChannelProviderFactory; private Map sessionPortMap = new HashMap<>(); private Map> sessionSocketMap = new HashMap<>(); - private static final int getMinMTU() { - int retMTU = 1500; - - try { - final Enumeration netInterfacesEnum = NetworkInterface.getNetworkInterfaces(); - while (netInterfacesEnum.hasMoreElements()) { - final NetworkInterface netInteface = netInterfacesEnum.nextElement(); - - try { - if (!netInteface.isUp()) { - continue; - } - } catch (NoSuchMethodError nsme) { - // java < 1.6 - if (logger.isLoggable(Level.FINE)) { - System.out.println("The current JVM is not able to determine if the net interface " - + netInteface + "is up and running. JVM >= 1.6 should support this feature"); - } - return retMTU; - } catch (Throwable t) { - System.err.println(" Cannot determine if the interface: " + netInteface + " is up"); - return retMTU; - } - - int cMTU = -1; - try { - cMTU = netInteface.getMTU(); - } catch (SocketException se) { - System.err.println(" Cannot get MTU for netInterface: " + netInteface + " SocketException: " + se); - } catch (NoSuchMethodError nsme) { - if (logger.isLoggable(Level.FINE)) { - System.out.println("The current JVM is not able to determine the MTU for the net interface " - + netInteface + "is up and running. JVM >= 1.6 should support this feature"); - } - continue; - } catch (Throwable t) { - // probably incompatible JVM version - System.err.println(" Cannot get MTU for netInterface: " + netInteface + " Exception: " + t); - } - - if ((cMTU < retMTU) && (cMTU > 0)) { - retMTU = cMTU; - } - }// while - } catch (SocketException se) { - System.err.println(" Cannot get min MTU for current instance of FDT. SocketException: " + se); - } catch (Throwable t) { - System.err.println(" Cannot get min MTU for current instance of FDT. Exception: " + t); - } - - return retMTU; - } - /** * @param configMap * @throws InvalidFDTParameterException if incorrect values are supplied for parameters @@ -562,49 +529,67 @@ private Config(final Map configMap) throws InvalidFDTParameterEx } } - private String[] getLogFiles(String sessionID) { - return new String[]{"/tmp/" + sessionID + ".log"}; - } + private static final int getMinMTU() { + int retMTU = 1500; - public String getListFilesFrom() { - return listFilesFrom; - } + try { + final Enumeration netInterfacesEnum = NetworkInterface.getNetworkInterfaces(); + while (netInterfacesEnum.hasMoreElements()) { + final NetworkInterface netInteface = netInterfacesEnum.nextElement(); - public void setListFilesFrom(String listFilesFrom) { - this.listFilesFrom = listFilesFrom; - } + try { + if (!netInteface.isUp()) { + continue; + } + } catch (NoSuchMethodError nsme) { + // java < 1.6 + if (logger.isLoggable(Level.FINE)) { + System.out.println("The current JVM is not able to determine if the net interface " + + netInteface + "is up and running. JVM >= 1.6 should support this feature"); + } + return retMTU; + } catch (Throwable t) { + System.err.println(" Cannot determine if the interface: " + netInteface + " is up"); + return retMTU; + } - private String getFDTMode(Map configMap) { - if (configMap.get("-coord") != null) { - return "coordinator"; - } else if (configMap.get("-ls") != null) { - return "list files"; - } else if (configMap.get("-agent") != null) { - return "agent worker"; + int cMTU = -1; + try { + cMTU = netInteface.getMTU(); + } catch (SocketException se) { + System.err.println(" Cannot get MTU for netInterface: " + netInteface + " SocketException: " + se); + } catch (NoSuchMethodError nsme) { + if (logger.isLoggable(Level.FINE)) { + System.out.println("The current JVM is not able to determine the MTU for the net interface " + + netInteface + "is up and running. JVM >= 1.6 should support this feature"); + } + continue; + } catch (Throwable t) { + // probably incompatible JVM version + System.err.println(" Cannot get MTU for netInterface: " + netInteface + " Exception: " + t); + } + + if ((cMTU < retMTU) && (cMTU > 0)) { + retMTU = cMTU; + } + }// while + } catch (SocketException se) { + System.err.println(" Cannot get min MTU for current instance of FDT. SocketException: " + se); + } catch (Throwable t) { + System.err.println(" Cannot get min MTU for current instance of FDT. Exception: " + t); } - return (hostname == null) && (configMap.get("SCPSyntaxUsed") == null) ? "server" : "client"; + + return retMTU; } public static int getBulkSockConnect() { return 30; } - public long getKeepAliveDelay(TimeUnit unit) { - return unit.convert(keepAliveDelayNanos, TimeUnit.NANOSECONDS); - } - public static long getBulkSockConnectWait() { return 1500; } - public Map getConfigMap() { - return configMap; - } - - public void setConfigMap(Map configMap) { - this.configMap = configMap; - } - public static final String getUsage() { return Utils.getUsage(); } @@ -613,18 +598,6 @@ public static int getMaxTakePollIter() { return 1000; } - public long getReportingTaskDelay() { - return consoleReportingTaskDelay; - } - - public Level getStatsLevel() { - return statsLevel; - } - - public String getMonID() { - return monID; - } - public static final Config getInstance() { synchronized (Config.class) { while (_thisInstance == null) { @@ -639,10 +612,6 @@ public static final Config getInstance() { return _thisInstance; } - public int getRetryIOCount() { - return IORetryFactor; - } - public static final void initInstance(final Map configMap) throws Exception { synchronized (Config.class) { @@ -653,6 +622,92 @@ public static final void initInstance(final Map configMap) throw } } + private static void closeSessionRelatedSocks(List socks) { + if (socks != null) { + for (Object o : socks) { + if (o instanceof ServerSocketChannel) { + try { + ((ServerSocketChannel) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close ServerSocketChannel", e); + } + } + if (o instanceof ServerSocket) { + try { + ((ServerSocket) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close ServerSocket", e); + } + } + if (o instanceof SocketChannel) { + try { + ((SocketChannel) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close SocketChannel", e); + } + } + if (o instanceof Socket) { + try { + ((Socket) o).close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close Socket", e); + } + } + } + } + } + + private String[] getLogFiles(String sessionID) { + return new String[]{"/tmp/" + sessionID + ".log"}; + } + + public String getListFilesFrom() { + return listFilesFrom; + } + + public void setListFilesFrom(String listFilesFrom) { + this.listFilesFrom = listFilesFrom; + } + + private String getFDTMode(Map configMap) { + if (configMap.get("-coord") != null) { + return "coordinator"; + } else if (configMap.get("-ls") != null) { + return "list files"; + } else if (configMap.get("-agent") != null) { + return "agent worker"; + } + return (hostname == null) && (configMap.get("SCPSyntaxUsed") == null) ? "server" : "client"; + } + + public long getKeepAliveDelay(TimeUnit unit) { + return unit.convert(keepAliveDelayNanos, TimeUnit.NANOSECONDS); + } + + public Map getConfigMap() { + return configMap; + } + + public void setConfigMap(Map configMap) { + this.configMap = configMap; + } + + public long getReportingTaskDelay() { + return consoleReportingTaskDelay; + } + + public Level getStatsLevel() { + return statsLevel; + } + + public String getMonID() { + return monID; + } + + public int getRetryIOCount() { + return IORetryFactor; + } + public int getByteBufferSize() { return byteBufferSize; } @@ -700,6 +755,10 @@ public int getLisaPort() { return lisaPort; } + public void setLisaPort(int lisaPort) { + this.lisaPort = lisaPort; + } + public String getSshKeyPath() { return sshKeyPath; } @@ -712,19 +771,18 @@ public boolean isNoLockFlagSet() { return isNoLockFlagSet; } - public void setHostName(String hostname) { - this.configMap.put("-destinationHost", hostname); - this.hostname = hostname; - } - public String getHostName() { - if (configMap.get("-agent") != null) - { + if (configMap.get("-agent") != null) { return dIP; } return hostname; } + public void setHostName(String hostname) { + this.configMap.put("-destinationHost", hostname); + this.hostname = hostname; + } + public int getPort() { return portNo; } @@ -733,22 +791,22 @@ public int getGSIPort() { return portNoGSI; } - public int getSSHPort() { - return portNoSSH; - } - - public void setPortNo(int port) { - this.portNo = port; - } - public void setGSIPort(int port) { this.portNoGSI = port; } + public int getSSHPort() { + return portNoSSH; + } + public void setSSHPort(int port) { this.portNoSSH = port; } + public void setPortNo(int port) { + this.portNo = port; + } + public boolean isStandAlone() { return isStandAlone; } @@ -761,6 +819,10 @@ public String[] getFileList() { return fileList; } + public void setFileList(String[] fileList) { + this.fileList = fileList; + } + public String[] getRemappedFileList() { return remappedFileList; } @@ -773,24 +835,20 @@ public String getPostFilters() { return postFilters; } - public void setDestinationDir(String destDir) { - this.destDir = destDir; - } - public String getDestinationDir() { return destDir; } - public void setDestinationPort(int destPort) { - this.destPort = destPort; + public void setDestinationDir(String destDir) { + this.destDir = destDir; } public int getDestinationPort() { return destPort; } - public void setRemoteTransferPort(int remoteTransferPort) { - this.remoteTransferPort = remoteTransferPort; + public void setDestinationPort(int destPort) { + this.destPort = destPort; } public void registerTransferPortForSession(int newTransferPort, String sessionID) { @@ -839,45 +897,14 @@ public void releaseRemoteTransferPort(String sessionID) { } } - private static void closeSessionRelatedSocks(List socks) { - if (socks != null) { - for (Object o : socks) { - if (o instanceof ServerSocketChannel) { - try { - ((ServerSocketChannel) o).close(); - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to close ServerSocketChannel", e); - } - } - if (o instanceof ServerSocket) { - try { - ((ServerSocket) o).close(); - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to close ServerSocket", e); - } - } - if (o instanceof SocketChannel) { - try { - ((SocketChannel) o).close(); - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to close SocketChannel", e); - } - } - if (o instanceof Socket) { - try { - ((Socket) o).close(); - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to close Socket", e); - } - } - } - } - } - public int getRemoteTransferPort() { return remoteTransferPort; } + public void setRemoteTransferPort(int remoteTransferPort) { + this.remoteTransferPort = remoteTransferPort; + } + public String getSourceIP() { return sIP; } @@ -890,15 +917,6 @@ public void setDestinationIP(String dIP) { this.dIP = dIP; } - public void setFileList(String[] fileList) { - this.fileList = fileList; - } - - public void setLisaPort(int lisaPort) { - this.lisaPort = lisaPort; - } - - // TODO - As param ... public int getNumberOfSelectors() { return selectorsNo; @@ -908,13 +926,8 @@ public String getApMonHosts() { return apMonHosts; } - public void setPullMode(boolean pullMode) { - this.isPullMode = pullMode; - if (pullMode) { - this.configMap.put("-pull", true); - } else { - this.configMap.remove("-pull"); - } + public boolean isCoordinatorMode() { + return isCoordinatorMode; } public void setCoordinatorMode(boolean coordinatorMode) { @@ -926,13 +939,12 @@ public void setCoordinatorMode(boolean coordinatorMode) { } } - public void setThirdPartyCopyAgent(boolean isThirdPartyCopyAgent) { - this.isThirdPartyCopyAgent = isThirdPartyCopyAgent; - if (isThirdPartyCopyAgent) { - this.configMap.put("-agent", true); - } else { - this.configMap.remove("-agent"); - } + public boolean isListFilesMode() { + return listFilesFrom != null && configMap.get("-ls") != null; + } + + public boolean isRetrievingLogFile() { + return isRetrievingLogFile || configMap.containsKey("-sID"); } public void setRetrievingLogFile(Object sessionID) { @@ -944,23 +956,19 @@ public void setRetrievingLogFile(Object sessionID) { } } - public boolean isCoordinatorMode() { - return isCoordinatorMode; - } - - public boolean isListFilesMode() { - return listFilesFrom != null && configMap.get("-ls") != null; - } - - public boolean isRetrievingLogFile() { - return isRetrievingLogFile || configMap.containsKey("-sID"); - } - - public boolean isPullMode() { return isPullMode; } + public void setPullMode(boolean pullMode) { + this.isPullMode = pullMode; + if (pullMode) { + this.configMap.put("-pull", true); + } else { + this.configMap.remove("-pull"); + } + } + public boolean shouldUpdate() { return bCheckUpdate; } @@ -1001,29 +1009,6 @@ public boolean isGSISSHModeEnabled() { return bGSISSHMode; } - /** - * Check if remote server is needed. We use SSH channels to control remote startup.
    - * In SSH/SCP mode we have three types of syntax we need to support: - *
      - *
    • fdt /local/path [user]@remotehost:/remote/path :
      - * In this case a remote server si started on remote host and client starts in "PUSH" mode The server accept - * connection just from the the given client and exits when the transfer finishes - *
    • fdt /local/path [user]@remotehost:/remote/path :
      - * In this case a remote server si started on remote host and client starts in "PULL" mode The server accept - * connection just from the the given client and exits when the transfer finishes - *
    • fdt [user]@remotehost1:/remote/path1 [user]@remotehost2:/remote/path2 :
      - * In this case both the server and the client are started remotely (the local fdt acts as an agent for the transfer - *
    - */ - // different client/server configuration set using SSH - public static final int SSH_NO_REMOTE = -1; - // REMOTE server, local client in push mode - public static final int SSH_REMOTE_SERVER_LOCAL_CLIENT_PUSH = 1; - // REMOTE server, local client in pull mode - public static final int SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL = 2; - // REMOTE server, REMOTE client in push mode) - public static final int SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH = 3; - public int getReadersCount() { return readersCount; } @@ -1107,14 +1092,14 @@ public boolean isGenTest() { return isGenTest; } - public void setLogLevel(String logLevel) { - this.logLevel = logLevel; - } - public String getLogLevel() { return logLevel; } + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + public FileChannelProviderFactory getFileChannelProviderFactory() { return this.fileChannelProviderFactory; } @@ -1122,4 +1107,13 @@ public FileChannelProviderFactory getFileChannelProviderFactory() { public boolean isThirdPartyCopyAgent() { return this.isThirdPartyCopyAgent; } + + public void setThirdPartyCopyAgent(boolean isThirdPartyCopyAgent) { + this.isThirdPartyCopyAgent = isThirdPartyCopyAgent; + if (isThirdPartyCopyAgent) { + this.configMap.put("-agent", true); + } else { + this.configMap.remove("-agent"); + } + } } diff --git a/src/lia/util/net/common/ControlStream.java b/src/lia/util/net/common/ControlStream.java index f0ba971..5b03fa4 100644 --- a/src/lia/util/net/common/ControlStream.java +++ b/src/lia/util/net/common/ControlStream.java @@ -7,59 +7,56 @@ import java.io.InputStream; /** - * * @author Adrian Muraru - * */ public interface ControlStream { - /** Start the connection with the configured parameters */ - void connect() throws IOException; - - public void startProgram(String cmd) throws IOException; - - public InputStream getProgramStdOut() throws IOException; - - public InputStream getProgramStdErr() throws IOException; - - /** - * Wait for the control message and log the remaining ouput asynchronously in the fdt_.log file (optional) - * - * @param expect: - * the control message we are looking for - * @param allowEOF: - * if this is true it means that the EOF is accepted as a *control message* in the protocol - * @param grabRemainingLog: - * if true start a backround thread and save the output in fdt_.log file - * @throws IOException - */ - public void waitForControlMessage(String expect, boolean allowEOF, boolean grabRemainingLog) throws IOException; - - /** - * @see #waitForControlMessage(String, boolean, boolean) Wait for the control message but do save the remaining log in a file (thow it away /dev/null) - * @param expect - * @param allowEOF - * @throws IOException - */ - public void waitForControlMessage(String expect, boolean allowEOF) throws IOException; - - /** - * @see SSHControlStream#waitForControlMessage(String, boolean,boolean) - * @param expect - * @throws IOException - */ - public void waitForControlMessage(String expect) throws IOException; - - /** - * save the remote stderr stream in a local file BUG: for some reason this SSH library streams the program stdout and stderr on the same stream (stdout) back to the client - * - * @unused : see BUG - * @param localFileName - */ - public void saveStdErr() throws IOException; - - public int getExitCode(); - - public void close(); + /** + * Start the connection with the configured parameters + */ + void connect() throws IOException; + + public void startProgram(String cmd) throws IOException; + + public InputStream getProgramStdOut() throws IOException; + + public InputStream getProgramStdErr() throws IOException; + + /** + * Wait for the control message and log the remaining ouput asynchronously in the fdt_.log file (optional) + * + * @param expect: the control message we are looking for + * @param allowEOF: if this is true it means that the EOF is accepted as a *control message* in the protocol + * @param grabRemainingLog: if true start a backround thread and save the output in fdt_.log file + * @throws IOException + */ + public void waitForControlMessage(String expect, boolean allowEOF, boolean grabRemainingLog) throws IOException; + + /** + * @param expect + * @param allowEOF + * @throws IOException + * @see #waitForControlMessage(String, boolean, boolean) Wait for the control message but do save the remaining log in a file (thow it away /dev/null) + */ + public void waitForControlMessage(String expect, boolean allowEOF) throws IOException; + + /** + * @param expect + * @throws IOException + * @see SSHControlStream#waitForControlMessage(String, boolean, boolean) + */ + public void waitForControlMessage(String expect) throws IOException; + + /** + * save the remote stderr stream in a local file BUG: for some reason this SSH library streams the program stdout and stderr on the same stream (stdout) back to the client + * + * @param localFileName + * @unused : see BUG + */ + public void saveStdErr() throws IOException; + + public int getExitCode(); + + public void close(); } \ No newline at end of file diff --git a/src/lia/util/net/common/DDCopy.java b/src/lia/util/net/common/DDCopy.java index e385fa4..9709a05 100644 --- a/src/lia/util/net/common/DDCopy.java +++ b/src/lia/util/net/common/DDCopy.java @@ -12,193 +12,101 @@ import java.util.concurrent.atomic.AtomicLong; /** - * * This class is a simple dd implementation in java with arguments very similar as * standard *nix dd command * * @author ramiro */ public class DDCopy { - - private static final long KILO = 1024; - private static final long MEGA = KILO * 1024; - private static final long GIGA = MEGA * 1024; - private static final long TERA = GIGA * 1024; - private static final long PETA = TERA * 1024; - + + private static final long KILO = 1024; + private static final long MEGA = KILO * 1024; + private static final long GIGA = MEGA * 1024; + private static final long TERA = GIGA * 1024; + private static final long PETA = TERA * 1024; + //I need heeeelpp ... or I neeed love, love, love ( try google ) private static final String USAGE_MESSAGE = - "\nUsage: java -cp fdt.jar " + DDCopy.class.getName() + " [ OPTIONS ] ARGS\n"+ - "\nARGS: if= of=\n" + - "\n\nWhere OPTIONS can be:\n" + - "\n bs=[K|M]\t size of the buffer used for read/write." + - "\n \t\t\t [K(ilo) | M(ega)] may be used as suffixes. Default 4K" + - "\n bn=\t Number of buffers used to readv()/writev() at once." + - "\n \t\t\t If this parameter is 1, or is missing the program will " + - "\n \t\t\t read()/write() a single buffer at a time, otherwise " + - "\n \t\t\t the readv()/writev() will be used. Default is 1" + - "\n count=\t Number of \"blocks\" to write." + - "\n \t\t\t A \"block\" is represents how much data is read/write" + - "\n \t\t\t The size of a \"block\" is: *" + - "\n \t\t\t If <= 0 the copy stops when EOF is reached" + - "\n \t\t\t reading the . The default is 0" + - "\n statsdelay=\t Number of seconds between reports." + - "\n \t\t\t Default is 2 seconds. If <= 0 no reports " + - "\n \t\t\t will be printed" + - "\n flags=\t\t The field can have of the following values: " + - "\n \t\t\t SYNC For every write both data and metadata are" + - "\n \t\t\t written synchronously" + - "\n \t\t\t DSYCN Same as SYNC, but only the data is written" + - "\n \t\t\t synchronously." + - "\n \t\t\t NOSYNC The sync() is left to be done by the" + - "\n \t\t\t underlying OS" + - "\n \t\t\t The default value is DSYNC" + - "\n rformat=\t Report format. Possible values are:" + - "\n \t\t\t K - KiloBytes" + - "\n \t\t\t M - MegaBytes" + - "\n \t\t\t G - GigaBytes" + - "\n \t\t\t T - TeraBytes" + - "\n \t\t\t P - PetaBytes" + - "\n \t\t\t The default value is self adjusted. If the factor " + - "\n \t\t\t is too big only 0s will be displayed" + - "\n"; - - - /** - * Statistics - */ - private static final class ReportingThread extends Thread { - - long lastTime; - long lastCount; - long now; - long cCount; - - - public ReportingThread() { - setDaemon(true); - setName("DDCopy reporting thread"); - } - - public void run() { - - //first iteration - lastCount = bytesNo.get(); - lastTime = System.currentTimeMillis(); - - for(;;) { - - try { - Thread.sleep(delay); - } catch(Throwable t1) {} - - if(!hasToRun.get()) return; - - now = System.currentTimeMillis(); - cCount = bytesNo.get(); - - double speed = (cCount - lastCount)/((now - lastTime)/1000D); - double avgSpeed = cCount/((now - START_TIME)/1000D); - - lastTime = now; - lastCount = cCount; - - System.out.println("[" + new Date().toString() + "] Current Speed = " + format(speed, reportingFactor, "B/s") + - " Avg Speed: " + format(avgSpeed, reportingFactor, "B/s") + - " Total Transfer: " + format(cCount, reportingFactor, "B") - ); - }//for - } - } - - /** - * Shutdown hook - */ - private static final class ShutdownHook extends Thread { - public void run() { - setName("Shutdown Hook Thread"); - - if(verbose) { - System.out.println("\n\n Entering shutdown hook \n\n"); - } - - hasToRun.set(false); - if(reportingThread != null) { - reportingThread.interrupt(); - } - - final long totalTime = System.currentTimeMillis() - START_TIME; - final long totalBytes = bytesNo.get(); - final double avgSpeed = totalBytes / (totalTime/1000D); - - System.out.println("\n" + - "\n Total Transfer: " + format(totalBytes, reportingFactor, "Bytes") + " ( " + totalBytes + " bytes )"+ - "\n Time: " + totalTime/1000 + " seconds" + - "\n Avg Speed: " + format(avgSpeed, reportingFactor, "B/s") + - "\n"); - - System.out.flush(); - System.err.flush(); - } - } - + "\nUsage: java -cp fdt.jar " + DDCopy.class.getName() + " [ OPTIONS ] ARGS\n" + + "\nARGS: if= of=\n" + + "\n\nWhere OPTIONS can be:\n" + + "\n bs=[K|M]\t size of the buffer used for read/write." + + "\n \t\t\t [K(ilo) | M(ega)] may be used as suffixes. Default 4K" + + "\n bn=\t Number of buffers used to readv()/writev() at once." + + "\n \t\t\t If this parameter is 1, or is missing the program will " + + "\n \t\t\t read()/write() a single buffer at a time, otherwise " + + "\n \t\t\t the readv()/writev() will be used. Default is 1" + + "\n count=\t Number of \"blocks\" to write." + + "\n \t\t\t A \"block\" is represents how much data is read/write" + + "\n \t\t\t The size of a \"block\" is: *" + + "\n \t\t\t If <= 0 the copy stops when EOF is reached" + + "\n \t\t\t reading the . The default is 0" + + "\n statsdelay=\t Number of seconds between reports." + + "\n \t\t\t Default is 2 seconds. If <= 0 no reports " + + "\n \t\t\t will be printed" + + "\n flags=\t\t The field can have of the following values: " + + "\n \t\t\t SYNC For every write both data and metadata are" + + "\n \t\t\t written synchronously" + + "\n \t\t\t DSYCN Same as SYNC, but only the data is written" + + "\n \t\t\t synchronously." + + "\n \t\t\t NOSYNC The sync() is left to be done by the" + + "\n \t\t\t underlying OS" + + "\n \t\t\t The default value is DSYNC" + + "\n rformat=\t Report format. Possible values are:" + + "\n \t\t\t K - KiloBytes" + + "\n \t\t\t M - MegaBytes" + + "\n \t\t\t G - GigaBytes" + + "\n \t\t\t T - TeraBytes" + + "\n \t\t\t P - PetaBytes" + + "\n \t\t\t The default value is self adjusted. If the factor " + + "\n \t\t\t is too big only 0s will be displayed" + + "\n"; //the if= param private static String sourceName; //thie of= param private static String destinationName; - //how much data was trasfered private static AtomicLong bytesNo = new AtomicLong(0); - //how many buffers shall I use in a single write? private static int BUFF_NO = 1; - //the buffer size - private static int BUFF_SIZE = 4 * (int)KILO; - + private static int BUFF_SIZE = 4 * (int) KILO; //how much shall I p(l)ay ? private static int COUNT = 0; - //Are you lost? Try verbose private static boolean verbose = false; - //How worried are you? Take a brake ... try to increase this value private static long delay = 2 * 1000; - //how fast can you read loooong numbers on your screen private static long reportingFactor = 0; - //I am taking care of your worries ;) private static Thread reportingThread; - //shall we take a break and go for a beer ?? private static AtomicBoolean hasToRun = new AtomicBoolean(true); - //how shall the destinationFile be written: FAST(SYNC), FASTER(DSYNC), DON'T CARE(NOSYNC) private static String wrFlags = "rw"; - //when did you learn to write? private static long START_TIME; - + //do it nicer - TODO make same arrays and use for() ... it's not the 5th grade private static final String format(final double number, final long factor, final String append) { String appendUM; double fNo = number; - - if(factor == 0) { - if(number > PETA) { + + if (factor == 0) { + if (number > PETA) { fNo /= PETA; appendUM = "P" + append; - } else if(number > TERA) { + } else if (number > TERA) { fNo /= TERA; appendUM = "T" + append; - } else if(number > GIGA) { + } else if (number > GIGA) { fNo /= GIGA; appendUM = "G" + append; - } else if(number > MEGA) { + } else if (number > MEGA) { fNo /= MEGA; appendUM = "M" + append; - } else if(number > KILO) { + } else if (number > KILO) { fNo /= KILO; appendUM = "K" + append; } else { @@ -224,212 +132,207 @@ private static final String format(final double number, final long factor, final appendUM = append; } } - + return DecimalFormat.getNumberInstance().format(fNo) + " " + appendUM; } - - - /** - * @param args - */ - + private static final void printHelp() { System.out.println(USAGE_MESSAGE); } - + public static void main(String[] args) throws Exception { try { //check for help - for(int i=0; i parameter ). Use -h for help.\n"); System.exit(1); } - - if(destinationName == null || destinationName.trim().length() == 0) { + + if (destinationName == null || destinationName.trim().length() == 0) { System.out.println("\n No destination specified ( 'of=' parameter ). Use -h for help.\n"); System.exit(1); } - - - if(verbose) { + + + if (verbose) { StringBuilder sb = new StringBuilder(); sb.append("Source: ").append(sourceName); sb.append(" Destination: ").append(destinationName); sb.append(""); } - + final FileChannel sourceChannel = new RandomAccessFile(sourceName, "r").getChannel(); final FileChannel destinationChannel = new RandomAccessFile(destinationName, wrFlags).getChannel(); - - + + ByteBuffer[] bbuff = new ByteBuffer[BUFF_NO]; - - for(int i=0; i 0) { + if (delay > 0) { reportingThread = new ReportingThread(); reportingThread.start(); } - + //register for shudown hook Runtime.getRuntime().addShutdownHook(new ShutdownHook()); - + long count = 0; START_TIME = System.currentTimeMillis(); - - for(int j=0; (COUNT > 0)?j 0) ? j < COUNT : true; j++) { count = sourceChannel.read(bbuff); - - if(count == -1) { + + if (count == -1) { //EOF break; } - - for(int i=0; i buffersPool; - private final LinkedList fdtBuffersPool; - - //Synch variables. We will not set an upper limit for the pool ... + //Synch variables. We will not set an upper limit for the pool ... // final Lock lock; final Condition notTaking; final Condition notEmpty; - + private final LinkedList buffersPool; + private final LinkedList fdtBuffersPool; private volatile boolean taking = false; private volatile boolean limitReached = false; - + private FDTBufferPool() { lock = new ReentrantLock(); notTaking = lock.newCondition(); @@ -49,35 +45,13 @@ private FDTBufferPool() { buffersPool = new LinkedList(); fdtBuffersPool = new LinkedList(); } - - private ByteBuffer tryAllocateBuffer() { - - if(!limitReached) { - try { - return ByteBuffer.allocateDirect(BUFFER_SIZE); - }catch(OutOfMemoryError oom) { - logger.log(Level.INFO, " ByteBuffer reached max limit. You may consider to increase to -XX:MaxDirectMemorySize=256m "); - limitReached = true; - return null; - }catch(Throwable t) { - logger.log(Level.WARNING, " Got general exception trying to allocate the mem. Please notify the developers! ", t); - return null; - } finally { - if(!limitReached) { - POOL_SIZE.incrementAndGet(); - } - } - } - - return null; - } - + public static final FDTBufferPool getInstance() { - + //double checked locking - if(!initialized) { - synchronized(FDTBufferPool.class) { - while(!initialized) { + if (!initialized) { + synchronized (FDTBufferPool.class) { + while (!initialized) { try { FDTBufferPool.class.wait(); } catch (Throwable t) { @@ -86,22 +60,21 @@ public static final FDTBufferPool getInstance() { } } } - + return _theInstance; } /** - * - * This function must be called to instantiate the pool. Subsequent calls - * + * This function must be called to instantiate the pool. Subsequent calls + * * @param buffSize * @return */ public static final boolean initInstance(int buffSize) { - - synchronized(FDTBufferPool.class) { - if(!initialized) { - + + synchronized (FDTBufferPool.class) { + if (!initialized) { + BUFFER_SIZE = buffSize; _theInstance = new FDTBufferPool(); initialized = true; @@ -109,14 +82,36 @@ public static final boolean initInstance(int buffSize) { return true; } } - + return false; } - + + private ByteBuffer tryAllocateBuffer() { + + if (!limitReached) { + try { + return ByteBuffer.allocateDirect(BUFFER_SIZE); + } catch (OutOfMemoryError oom) { + logger.log(Level.INFO, " ByteBuffer reached max limit. You may consider to increase to -XX:MaxDirectMemorySize=256m "); + limitReached = true; + return null; + } catch (Throwable t) { + logger.log(Level.WARNING, " Got general exception trying to allocate the mem. Please notify the developers! ", t); + return null; + } finally { + if (!limitReached) { + POOL_SIZE.incrementAndGet(); + } + } + } + + return null; + } + public int getBufferSize() { return BUFFER_SIZE; } - + public int getSize() { return buffersPool.size(); } @@ -124,121 +119,120 @@ public int getSize() { public int getCapacity() { return POOL_SIZE.get(); } - + /** - * * @param size - in bytes * @return * @throws InterruptedException */ public FDTBuffer take(int size) throws InterruptedException { - + FDTBuffer fdtBuffer = null; ByteBuffer[] buffs = null; int allocated = 0; - int reminder = size%BUFFER_SIZE; - int buffCount = (size < BUFFER_SIZE)?1:(size/BUFFER_SIZE + ((reminder != 0)?1:0)); - + int reminder = size % BUFFER_SIZE; + int buffCount = (size < BUFFER_SIZE) ? 1 : (size / BUFFER_SIZE + ((reminder != 0) ? 1 : 0)); + lock.lock(); try { //do not allow different threads to fill "partial" FDTBuffer-s - while(taking) { + while (taking) { notTaking.await(); } - + taking = true; - + fdtBuffer = fdtBuffersPool.poll(); - if(fdtBuffer == null) { + if (fdtBuffer == null) { fdtBuffer = new FDTBuffer(); } - + buffs = new ByteBuffer[buffCount]; - - while(allocated < buffCount) { + + while (allocated < buffCount) { ByteBuffer buff = buffersPool.poll(); - if(buff == null && !limitReached) { + if (buff == null && !limitReached) { buff = tryAllocateBuffer(); } - - if(buff == null) { - while(buffersPool.size() == 0) { + + if (buff == null) { + while (buffersPool.size() == 0) { notEmpty.await(); } - + buff = buffersPool.poll(); } - + buffs[allocated++] = buff; - + }//end while - - if(reminder != 0) { + + if (reminder != 0) { buffs[buffCount - 1].limit(reminder); } - + fdtBuffer.setBuffer(buffs); - + } finally { - - if(fdtBuffer == null || fdtBuffer.get() == null) {//error + + if (fdtBuffer == null || fdtBuffer.get() == null) {//error try { - if(fdtBuffer.get() != null) { + if (fdtBuffer.get() != null) { fdtBuffer.free(); fdtBuffersPool.offer(fdtBuffer); } - } catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, " Got exception returning fdtBuffer to the pull", t); } - int i=0; + int i = 0; try { - for(; i < allocated; i++) { + for (; i < allocated; i++) { buffersPool.add(buffs[i]); } - } catch(Throwable t) { - logger.log(Level.WARNING, " Got exception returning buffers to the pull [ currentIdx = " + i - + " allocated = " + allocated + } catch (Throwable t) { + logger.log(Level.WARNING, " Got exception returning buffers to the pull [ currentIdx = " + i + + " allocated = " + allocated + " buffCount = " + buffCount + " ]", t); } } - + try { //signal the waiting threads taking = false; notTaking.signal(); - } catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, " \n\n Got exception signaling notTaking Condition. Something has gone dreadfully wrong \n\n", t); } - + lock.unlock(); } - + return fdtBuffer; - + } - + public boolean put(FDTBuffer fdtBuffer) { lock.lock(); try { - if(fdtBuffer.free()) { + if (fdtBuffer.free()) { ByteBuffer[] buffs = fdtBuffer.get(); - for(int i=0; i + * The close() methods return true if they have been already called + * * @author ramiro - * */ public interface FDTCloseable { public boolean close(String downMessage, Throwable downCause); + public boolean isClosed(); - + } diff --git a/src/lia/util/net/common/FDTCommandLine.java b/src/lia/util/net/common/FDTCommandLine.java index 48be73f..24e47f5 100644 --- a/src/lia/util/net/common/FDTCommandLine.java +++ b/src/lia/util/net/common/FDTCommandLine.java @@ -7,32 +7,29 @@ import java.util.HashMap; /** - * * Simple class to keep the optins given in the command line - * + * * @author ramiro - * */ //TODO - maybe in the future will use a more standard POSIX interface for the command line public class FDTCommandLine { //immutable instances - no need for synchronization - + //any params should be in this map ( if the param is only a flag it's value will be non-null ) private final HashMap optionsMap; - + //all the arguments which do not had an associated flag private final ArrayList leftArgs; - + /** - * * @param args */ public FDTCommandLine(final String[] args) { HashMap tmpCmdOptions = new HashMap(); ArrayList tmpParamsLeft = new ArrayList(); - + //parse the options first int i = 0; for (i = 0; i < args.length; i++) { @@ -47,23 +44,23 @@ public FDTCommandLine(final String[] args) { break; } }//for() - + for (; i < args.length; i++) { tmpParamsLeft.add(args[i]); } - + optionsMap = tmpCmdOptions; leftArgs = tmpParamsLeft; } - + public HashMap getOptionsMap() { return optionsMap; } - + public String getOption(String key) { return optionsMap.get(key); } - + public ArrayList getLeftArguments() { return leftArgs; } diff --git a/src/lia/util/net/common/FDTVersion.java b/src/lia/util/net/common/FDTVersion.java index 9768680..7502168 100644 --- a/src/lia/util/net/common/FDTVersion.java +++ b/src/lia/util/net/common/FDTVersion.java @@ -7,17 +7,16 @@ /** - * * @author ramiro */ public final class FDTVersion implements Comparable { - + final int major; final int minor; final int maintenance; - + final String releaseDate; - + /** * @param major * @param minor @@ -33,34 +32,33 @@ private FDTVersion(int major, int minor, int maintenance, String releaseDate) { public static FDTVersion fromVersionString(final String versionString) { - if(versionString == null) { + if (versionString == null) { throw new NullPointerException("Null version string"); } - + final int rDateDelim = versionString.indexOf('-'); - final String vString = (rDateDelim > 0)?versionString.substring(0, rDateDelim):versionString; - final String rDate = (rDateDelim < 0)?"":versionString.substring(rDateDelim+1); - + final String vString = (rDateDelim > 0) ? versionString.substring(0, rDateDelim) : versionString; + final String rDate = (rDateDelim < 0) ? "" : versionString.substring(rDateDelim + 1); + final StringTokenizer st = new StringTokenizer(vString, "."); int major = 0; int minor = 0; int maint = 0; - - if(st.hasMoreTokens()) { + + if (st.hasMoreTokens()) { major = Integer.parseInt(st.nextToken()); } - if(st.hasMoreTokens()) { + if (st.hasMoreTokens()) { minor = Integer.parseInt(st.nextToken()); } - if(st.hasMoreTokens()) { + if (st.hasMoreTokens()) { maint = Integer.parseInt(st.nextToken()); } - + return new FDTVersion(major, minor, maint, rDate); } - - + /* (non-Javadoc) * @see java.lang.Object#toString() */ @@ -68,14 +66,14 @@ public static FDTVersion fromVersionString(final String versionString) { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("FDTVersion [") - .append(major) - .append(".") - .append(minor) - .append(".") - .append(maintenance) - .append("-") - .append(releaseDate) - .append("]"); + .append(major) + .append(".") + .append(minor) + .append(".") + .append(maintenance) + .append("-") + .append(releaseDate) + .append("]"); return builder.toString(); } @@ -83,12 +81,12 @@ public String toString() { @Override public int compareTo(FDTVersion other) { int d = this.major - other.major; - if(d == 0) { + if (d == 0) { d = this.minor - other.minor; - if(d == 0) { + if (d == 0) { d = this.maintenance - other.maintenance; - if(d == 0) { - if(this.releaseDate != null && other.releaseDate != null) { + if (d == 0) { + if (this.releaseDate != null && other.releaseDate != null) { return this.releaseDate.compareTo(other.releaseDate); } } diff --git a/src/lia/util/net/common/FileChannelProvider.java b/src/lia/util/net/common/FileChannelProvider.java index ba91c4c..dea9ff2 100644 --- a/src/lia/util/net/common/FileChannelProvider.java +++ b/src/lia/util/net/common/FileChannelProvider.java @@ -10,33 +10,29 @@ /** - * * Generic provider interface for {@link FileChannel} inside FDT. - * + *

    * There is no assumption on the read/write direction for the returned {@link FileChannel}. - * + * * @author ramiro */ public interface FileChannelProvider { - + /** - * * @param fileName * @return * @throws IOException */ public File getFile(final String fileName) throws IOException; - + /** - * * @param file * @return * @throws IOException */ public int getPartitionID(final File file) throws IOException; - + /** - * * @param file * @return * @throws IOException diff --git a/src/lia/util/net/common/FileChannelProviderFactory.java b/src/lia/util/net/common/FileChannelProviderFactory.java index 040ac42..f8a4c4d 100644 --- a/src/lia/util/net/common/FileChannelProviderFactory.java +++ b/src/lia/util/net/common/FileChannelProviderFactory.java @@ -5,16 +5,17 @@ package lia.util.net.common; //import lia.util.net.copy.FDTCoordinatorSession; + import lia.util.net.copy.FDTReaderSession; import lia.util.net.copy.FDTWriterSession; /** - * * @author ramiro */ public interface FileChannelProviderFactory { FileChannelProvider newReaderFileChannelProvider(FDTReaderSession readerSession); + FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerSession); // FileChannelProvider newCoordinatorChannelProvider(FDTCoordinatorSession coordinatorSession); } diff --git a/src/lia/util/net/common/GSISSHControlStream.java b/src/lia/util/net/common/GSISSHControlStream.java index b28b004..27747e9 100644 --- a/src/lia/util/net/common/GSISSHControlStream.java +++ b/src/lia/util/net/common/GSISSHControlStream.java @@ -3,293 +3,277 @@ */ package lia.util.net.common; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.ietf.jgss.GSSException; - import ch.ethz.ssh2.StreamGobbler; - import com.sshtools.common.configuration.SshToolsConnectionProfile; import com.sshtools.j2ssh.SshClient; import com.sshtools.j2ssh.authentication.AuthenticationProtocolState; import com.sshtools.j2ssh.session.SessionChannelClient; +import org.ietf.jgss.GSSException; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; /** - * * @author Adrian Muraru - * */ public class GSISSHControlStream implements ControlStream { - // configuration parameters - private final String hostname; - private final String username; - private final int port; + // configuration parameters + private final String hostname; + private final String username; + private final int port; - /** - * the SSH connection & session - */ - private SshClient conn; + /** + * the SSH connection & session + */ + private SshClient conn; - private SessionChannelClient sess; + private SessionChannelClient sess; - private String cmd; + private String cmd; /** * Creates a new GSI SSH control connection on the default ssh port. - * + *

    * Same as {@link #GSISSHControlStream(String, String, int) GSISSHControlStream(hostname, username, 22)} - * - * @param hostname: - * remote host - * @param username: - * remote account - * @throws IOException - * in case of failure + * + * @param hostname: remote host + * @param username: remote account + * @throws IOException in case of failure */ public GSISSHControlStream(String hostname, String username) { this(hostname, username, 22); } - + /** * Creates a new SSH control connection on the specified remote GSI sshd server port - * - * @param port: - * remote GSI-sshd port - * @param hostname: - * remote host - * @param username: - * remote account - * @throws IOException - * in case of failure - */ - public GSISSHControlStream(String hostname, String username, int port) { - this.hostname = hostname; - this.username = username; - this.port = port; - } - - public void connect() throws IOException { - lia.gsi.ssh.GSIAuthenticationClient gsiAuth = null; - try { - gsiAuth = new lia.gsi.ssh.GSIAuthenticationClient(); - gsiAuth.setUsername(username); - } catch (GSSException e) { - throw new IOException("Cannot load grid credentials."); - } - conn = new SshClient(); - SshToolsConnectionProfile properties = new SshToolsConnectionProfile(); - // TODO: add new "port" parameter - properties.setPort(port); - properties.setForwardingAutoStartMode(false); - properties.setHost(hostname); - properties.setUsername(username); - conn.setUseDefaultForwarding(false); - conn.connect(properties); - try { - // Authenticate the user - int result = conn.authenticate(gsiAuth, hostname); - if (result != AuthenticationProtocolState.COMPLETE) { - throw new IOException("GSI authentication failed"); - } - // Open a session channel - sess = conn.openSessionChannel(); - sess.requestPseudoTerminal("javash", 0, 0, 0, 0, ""); - } catch (Throwable t) { - throw new IOException(t.getMessage()); - } - } + * + * @param port: remote GSI-sshd port + * @param hostname: remote host + * @param username: remote account + * @throws IOException in case of failure + */ + public GSISSHControlStream(String hostname, String username, int port) { + this.hostname = hostname; + this.username = username; + this.port = port; + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#startProgram(java.lang.String) - */ - public void startProgram(String cmd) throws IOException { - this.cmd = "/bin/bash --login -c '" + cmd + " 2>&1'"; - this.sess.executeCommand(this.cmd); - } + // TEST + public static void main(String[] args) throws IOException { + ControlStream cs = new GSISSHControlStream(args[0], args[1]); + cs.startProgram(args[2]); - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#getProgramStdOut() - */ - public InputStream getProgramStdOut() { - return this.sess.getInputStream(); - } + /* read stdout */ + InputStream stdout = new StreamGobbler(cs.getProgramStdOut()); + BufferedReader br = new BufferedReader(new InputStreamReader(stdout)); + while (true) { + String line = br.readLine(); + if (line == null) + break; + System.out.println(line); + } + System.out.println("ExitCode:" + cs.getExitCode()); + cs.close(); + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#getProgramStdErr() - */ - public InputStream getProgramStdErr() throws IOException { - return this.sess.getStderrInputStream(); - } + public void connect() throws IOException { + lia.gsi.ssh.GSIAuthenticationClient gsiAuth = null; + try { + gsiAuth = new lia.gsi.ssh.GSIAuthenticationClient(); + gsiAuth.setUsername(username); + } catch (GSSException e) { + throw new IOException("Cannot load grid credentials."); + } + conn = new SshClient(); + SshToolsConnectionProfile properties = new SshToolsConnectionProfile(); + // TODO: add new "port" parameter + properties.setPort(port); + properties.setForwardingAutoStartMode(false); + properties.setHost(hostname); + properties.setUsername(username); + conn.setUseDefaultForwarding(false); + conn.connect(properties); + try { + // Authenticate the user + int result = conn.authenticate(gsiAuth, hostname); + if (result != AuthenticationProtocolState.COMPLETE) { + throw new IOException("GSI authentication failed"); + } + // Open a session channel + sess = conn.openSessionChannel(); + sess.requestPseudoTerminal("javash", 0, 0, 0, 0, ""); + } catch (Throwable t) { + throw new IOException(t.getMessage()); + } + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#waitForControlMessage(java.lang.String, boolean, boolean) - */ - public void waitForControlMessage(String expect, boolean allowEOF, boolean grabRemainingLog) throws IOException { - /* read stdout */ - // InputStream stdout = new StreamGobbler(getProgramStdOut()); - BufferedReader br = new BufferedReader(new InputStreamReader(getProgramStdOut())); - final String outputPrefix = "[" + this.hostname + "]$ "; - while (true) { - String line = br.readLine(); - if (line == null) { - if (allowEOF) - return; - // else - throw new IOException("[" + this.cmd + "] exited. No control message received]"); - } - System.err.println(outputPrefix + line); - if (line.trim().equalsIgnoreCase(expect)) { - LogWriter lw = grabRemainingLog - ? new LogWriter(br, "fdt_" + this.hostname + ".log") - : new LogWriter(br); - lw.setDaemon(true); - lw.start(); - return; - } - } - } + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#startProgram(java.lang.String) + */ + public void startProgram(String cmd) throws IOException { + this.cmd = "/bin/bash --login -c '" + cmd + " 2>&1'"; + this.sess.executeCommand(this.cmd); + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#waitForControlMessage(java.lang.String, boolean) - */ - public void waitForControlMessage(String expect, boolean allowEOF) throws IOException { - this.waitForControlMessage(expect, allowEOF, false); - } + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#getProgramStdOut() + */ + public InputStream getProgramStdOut() { + return this.sess.getInputStream(); + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#waitForControlMessage(java.lang.String) - */ - public void waitForControlMessage(String expect) throws IOException { - this.waitForControlMessage(expect, false, true); - } + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#getProgramStdErr() + */ + public InputStream getProgramStdErr() throws IOException { + return this.sess.getStderrInputStream(); + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#saveStdErr() - */ - public void saveStdErr() throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(getProgramStdErr())); - LogWriter lw = new LogWriter(br, "fdt_" + this.hostname + ".err"); - lw.setDaemon(true); - lw.start(); - } + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#waitForControlMessage(java.lang.String, boolean, boolean) + */ + public void waitForControlMessage(String expect, boolean allowEOF, boolean grabRemainingLog) throws IOException { + /* read stdout */ + // InputStream stdout = new StreamGobbler(getProgramStdOut()); + BufferedReader br = new BufferedReader(new InputStreamReader(getProgramStdOut())); + final String outputPrefix = "[" + this.hostname + "]$ "; + while (true) { + String line = br.readLine(); + if (line == null) { + if (allowEOF) + return; + // else + throw new IOException("[" + this.cmd + "] exited. No control message received]"); + } + System.err.println(outputPrefix + line); + if (line.trim().equalsIgnoreCase(expect)) { + LogWriter lw = grabRemainingLog + ? new LogWriter(br, "fdt_" + this.hostname + ".log") + : new LogWriter(br); + lw.setDaemon(true); + lw.start(); + return; + } + } + } - /** - * asynch write in a local log file of the remote stderr stream - */ - static class LogWriter extends Thread { - BufferedReader br; - String logFile; + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#waitForControlMessage(java.lang.String, boolean) + */ + public void waitForControlMessage(String expect, boolean allowEOF) throws IOException { + this.waitForControlMessage(expect, allowEOF, false); + } - public LogWriter(BufferedReader br) { - this.br = br; - this.logFile = null; - } + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#waitForControlMessage(java.lang.String) + */ + public void waitForControlMessage(String expect) throws IOException { + this.waitForControlMessage(expect, false, true); + } - public LogWriter(BufferedReader br, String fileName) { - this.br = br; - this.logFile = fileName; - } + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#saveStdErr() + */ + public void saveStdErr() throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(getProgramStdErr())); + LogWriter lw = new LogWriter(br, "fdt_" + this.hostname + ".err"); + lw.setDaemon(true); + lw.start(); + } - public void run() { - BufferedWriter out = null; - try { - if (this.logFile != null) { - out = new BufferedWriter(new FileWriter(logFile, false)); - final Date date = new Date(); - out.write("==============" + new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z").format(date) + "================\n"); - } - while (true) { - String line = br.readLine(); + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#getExitCode() + */ + public int getExitCode() { + return this.sess.getExitCode(); + } + + /* + * (non-Javadoc) + * + * @see lia.util.net.common.ControlStream#close() + */ + public void close() { + try { + if (this.sess != null) { + this.sess.close(); + } + } catch (IOException e) { + + } - if (line == null) { - if (out != null) - out.close(); - return; - } - if (out != null) { - out.write(line + "\n"); - out.flush(); - } - } - } catch (IOException e) { - System.err.println("Cannot write remote log:" + logFile); - try { - if (out != null) - out.close(); - } catch (IOException e1) { - } - return; - } - } - } + if (this.conn != null) { + this.conn.disconnect(); + } + } - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#getExitCode() - */ - public int getExitCode() { - return this.sess.getExitCode(); - } + /** + * asynch write in a local log file of the remote stderr stream + */ + static class LogWriter extends Thread { + BufferedReader br; + String logFile; - /* - * (non-Javadoc) - * - * @see lia.util.net.common.ControlStream#close() - */ - public void close() { - try { - if(this.sess != null) { - this.sess.close(); - } - } catch (IOException e) { - - } + public LogWriter(BufferedReader br) { + this.br = br; + this.logFile = null; + } - if(this.conn != null) { - this.conn.disconnect(); - } - } + public LogWriter(BufferedReader br, String fileName) { + this.br = br; + this.logFile = fileName; + } - // TEST - public static void main(String[] args) throws IOException { - ControlStream cs = new GSISSHControlStream(args[0], args[1]); - cs.startProgram(args[2]); + public void run() { + BufferedWriter out = null; + try { + if (this.logFile != null) { + out = new BufferedWriter(new FileWriter(logFile, false)); + final Date date = new Date(); + out.write("==============" + new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z").format(date) + "================\n"); + } + while (true) { + String line = br.readLine(); - /* read stdout */ - InputStream stdout = new StreamGobbler(cs.getProgramStdOut()); - BufferedReader br = new BufferedReader(new InputStreamReader(stdout)); - while (true) { - String line = br.readLine(); - if (line == null) - break; - System.out.println(line); - } - System.out.println("ExitCode:" + cs.getExitCode()); - cs.close(); - } + if (line == null) { + if (out != null) + out.close(); + return; + } + if (out != null) { + out.write(line + "\n"); + out.flush(); + } + } + } catch (IOException e) { + System.err.println("Cannot write remote log:" + logFile); + try { + if (out != null) + out.close(); + } catch (IOException e1) { + } + return; + } + } + } } diff --git a/src/lia/util/net/common/HeaderBufferPool.java b/src/lia/util/net/common/HeaderBufferPool.java index e026691..4363f47 100644 --- a/src/lia/util/net/common/HeaderBufferPool.java +++ b/src/lia/util/net/common/HeaderBufferPool.java @@ -2,22 +2,24 @@ * $Id$ */ package lia.util.net.common; + import java.util.logging.Level; import java.util.logging.Logger; /** - * - * This class is only for the love of fast development. + * This class is only for the love of fast development. * TODO - We should have only one buffer pool in entire FDT Application; * when this will happen this class will disappear .... - * + * * @author ramiro */ public class HeaderBufferPool extends AbstractBPool { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final transient Logger logger = Logger.getLogger(HeaderBufferPool.class.getName()); - + //the list of ByteBuffer-s private static HeaderBufferPool _theInstance; //used for double checked locking @@ -49,7 +51,7 @@ public static final boolean initInstance() { synchronized (HeaderBufferPool.class) { if (!initialized) { - + _theInstance = new HeaderBufferPool(Config.HEADER_SIZE, 0, Config.TRACK_ALLOCATIONS); initialized = true; diff --git a/src/lia/util/net/common/InvalidFDTParameterException.java b/src/lia/util/net/common/InvalidFDTParameterException.java index 00458b1..02e366d 100644 --- a/src/lia/util/net/common/InvalidFDTParameterException.java +++ b/src/lia/util/net/common/InvalidFDTParameterException.java @@ -4,22 +4,22 @@ package lia.util.net.common; /** - * * Used to signal various exception/errors related to FDT config/params - * + * * @author ramiro */ public class InvalidFDTParameterException extends Exception { - private static final long serialVersionUID = -4780995072523010199L; - public InvalidFDTParameterException() { + private static final long serialVersionUID = -4780995072523010199L; + + public InvalidFDTParameterException() { super(); } - + public InvalidFDTParameterException(String message) { super(message); } - + public InvalidFDTParameterException(String message, Throwable cause) { super(message, cause); } diff --git a/src/lia/util/net/common/KernelTest.java b/src/lia/util/net/common/KernelTest.java index cbca729..ec770fa 100644 --- a/src/lia/util/net/common/KernelTest.java +++ b/src/lia/util/net/common/KernelTest.java @@ -17,93 +17,94 @@ import java.nio.channels.FileChannel; /** - * * @author ramiro */ -public class KernelTest extends Thread{ - +public class KernelTest extends Thread { + public static final int BUFF_SIZE = 512 * 1024; - + static ByteBuffer _theBuffer; static int NUMBER_OF_THREADS = 5; - - + + int id; - - /** Creates a new instance of CrushTest */ + + /** + * Creates a new instance of CrushTest + */ public KernelTest(int id) { this.id = id; setName("KernelTest worker id: " + id); setDaemon(true); } - - public void run() { - FileChannel channel = null; - - try { - channel = new FileInputStream("/dev/zero").getChannel(); - }catch(Throwable t) { - t.printStackTrace(); - } - - if(channel == null) { - return; - } - - System.out.println("KernelTest thread " + id + " started"); - - for(;;) { - try { - _theBuffer.clear(); - channel.read(_theBuffer); - - } catch(Throwable t){ - t.printStackTrace(); - } - } - } - + public static final void main(String[] args) throws Exception { - - for(int i=0; i getPublicIPs4() { - List ips4 = new ArrayList(); - Enumeration ifs; - try { - ifs = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { - e.printStackTrace(); - return ips4; - } - while (ifs.hasMoreElements()) { - NetworkInterface iface = ifs.nextElement(); - Enumeration iad = iface.getInetAddresses(); - while (iad.hasMoreElements()) { - InetAddress localIP = iad.nextElement(); - if (!localIP.isSiteLocalAddress() && !localIP.isLoopbackAddress()) { - // found an IPv4 address and proxyAddress not set yet - if (localIP instanceof java.net.Inet4Address) - ips4.add(localIP.getHostAddress()); - } - } - } - return ips4; - } - - /** - * @return A list of colon separated list of local public ips - * If no public address is found an empty string is returned - */ - static public String getStringPublicIPs4(){ - final List ip4s = LocalHost.getPublicIPs4(); - if (ip4s.size()<=0) return ""; - final StringBuilder sb=new StringBuilder(); - for (String ip:ip4s){ - sb.append(ip).append(':'); - } - return sb.length()>=1?sb.deleteCharAt(sb.length()-1).toString():""; - } + static public List getPublicIPs4() { + List ips4 = new ArrayList(); + Enumeration ifs; + try { + ifs = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + e.printStackTrace(); + return ips4; + } + while (ifs.hasMoreElements()) { + NetworkInterface iface = ifs.nextElement(); + Enumeration iad = iface.getInetAddresses(); + while (iad.hasMoreElements()) { + InetAddress localIP = iad.nextElement(); + if (!localIP.isSiteLocalAddress() && !localIP.isLoopbackAddress()) { + // found an IPv4 address and proxyAddress not set yet + if (localIP instanceof java.net.Inet4Address) + ips4.add(localIP.getHostAddress()); + } + } + } + return ips4; + } - static public List getPublicIPs6() { - List ips6 = new ArrayList(); - Enumeration ifs; - try { - ifs = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { - e.printStackTrace(); - return ips6; - } - while (ifs.hasMoreElements()) { - NetworkInterface iface = ifs.nextElement(); - Enumeration iad = iface.getInetAddresses(); - while (iad.hasMoreElements()) { - InetAddress localIP = iad.nextElement(); - if (!localIP.isSiteLocalAddress() && !localIP.isLinkLocalAddress() && !localIP.isLoopbackAddress()) { - if (localIP instanceof java.net.Inet6Address) - ips6.add(localIP.getHostAddress()); - } - } - } - return ips6; - } + /** + * @return A list of colon separated list of local public ips + * If no public address is found an empty string is returned + */ + static public String getStringPublicIPs4() { + final List ip4s = LocalHost.getPublicIPs4(); + if (ip4s.size() <= 0) return ""; + final StringBuilder sb = new StringBuilder(); + for (String ip : ip4s) { + sb.append(ip).append(':'); + } + return sb.length() >= 1 ? sb.deleteCharAt(sb.length() - 1).toString() : ""; + } - static public String getPublicIP4() { + static public List getPublicIPs6() { + List ips6 = new ArrayList(); + Enumeration ifs; + try { + ifs = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + e.printStackTrace(); + return ips6; + } + while (ifs.hasMoreElements()) { + NetworkInterface iface = ifs.nextElement(); + Enumeration iad = iface.getInetAddresses(); + while (iad.hasMoreElements()) { + InetAddress localIP = iad.nextElement(); + if (!localIP.isSiteLocalAddress() && !localIP.isLinkLocalAddress() && !localIP.isLoopbackAddress()) { + if (localIP instanceof java.net.Inet6Address) + ips6.add(localIP.getHostAddress()); + } + } + } + return ips6; + } - Enumeration ifs; - try { - ifs = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { - e.printStackTrace(); - return null; - } + static public String getPublicIP4() { - while (ifs.hasMoreElements()) { - NetworkInterface iface = ifs.nextElement(); - Enumeration iad = iface.getInetAddresses(); - while (iad.hasMoreElements()) { - InetAddress localIP = iad.nextElement(); - if (!localIP.isSiteLocalAddress() && !localIP.isLoopbackAddress()) { - // found an IPv4 address - if (localIP instanceof java.net.Inet4Address) - return localIP.getHostAddress(); - } - } - } - return null; - } + Enumeration ifs; + try { + ifs = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + e.printStackTrace(); + return null; + } - static public String getPublicIP6() { + while (ifs.hasMoreElements()) { + NetworkInterface iface = ifs.nextElement(); + Enumeration iad = iface.getInetAddresses(); + while (iad.hasMoreElements()) { + InetAddress localIP = iad.nextElement(); + if (!localIP.isSiteLocalAddress() && !localIP.isLoopbackAddress()) { + // found an IPv4 address + if (localIP instanceof java.net.Inet4Address) + return localIP.getHostAddress(); + } + } + } + return null; + } - Enumeration ifs; - try { - ifs = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { - e.printStackTrace(); - return null; - } + static public String getPublicIP6() { - while (ifs.hasMoreElements()) { - NetworkInterface iface = ifs.nextElement(); - Enumeration iad = iface.getInetAddresses(); - while (iad.hasMoreElements()) { - InetAddress localIP = iad.nextElement(); - if (!localIP.isSiteLocalAddress() && !localIP.isLinkLocalAddress() && !localIP.isLoopbackAddress()) { - if (localIP instanceof java.net.Inet6Address) - return localIP.getHostAddress(); - } - } - } - return null; - } + Enumeration ifs; + try { + ifs = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + e.printStackTrace(); + return null; + } - /** - * @param args - */ - public static void main(String[] args) { - System.out.println(getPublicIP4()); - System.out.println(getPublicIP6()); - System.out.println(getPublicIPs4()); - System.out.println(getPublicIPs6()); - - - - - } + while (ifs.hasMoreElements()) { + NetworkInterface iface = ifs.nextElement(); + Enumeration iad = iface.getInetAddresses(); + while (iad.hasMoreElements()) { + InetAddress localIP = iad.nextElement(); + if (!localIP.isSiteLocalAddress() && !localIP.isLinkLocalAddress() && !localIP.isLoopbackAddress()) { + if (localIP instanceof java.net.Inet6Address) + return localIP.getHostAddress(); + } + } + } + return null; + } + + /** + * @param args + */ + public static void main(String[] args) { + System.out.println(getPublicIP4()); + System.out.println(getPublicIP6()); + System.out.println(getPublicIPs4()); + System.out.println(getPublicIPs6()); + + + } } diff --git a/src/lia/util/net/common/MassStorage.java b/src/lia/util/net/common/MassStorage.java index e8bd5c5..233d77f 100644 --- a/src/lia/util/net/common/MassStorage.java +++ b/src/lia/util/net/common/MassStorage.java @@ -5,9 +5,8 @@ import java.io.FileInputStream; import java.util.Properties; - -import java.util.logging.Logger; import java.util.logging.Level; +import java.util.logging.Logger; /** * @author Ilya Narsky @@ -36,6 +35,12 @@ public class MassStorage { private int verbose;// verbosity level + static public boolean checkType(String type) { + if (type.compareTo("dcache") == 0) + return true; + return false; + } + public String siteStorageID() { return this.siteStorageID; } @@ -76,12 +81,6 @@ public int verbose() { return this.verbose; } - static public boolean checkType(String type) { - if (type.compareTo("dcache") == 0) - return true; - return false; - } - public boolean init(String configFile) { // get properties Properties prop = new Properties(); diff --git a/src/lia/util/net/common/NetMatcher.java b/src/lia/util/net/common/NetMatcher.java index d42a6ee..6b09384 100644 --- a/src/lia/util/net/common/NetMatcher.java +++ b/src/lia/util/net/common/NetMatcher.java @@ -36,6 +36,17 @@ public class NetMatcher { private ArrayList networks; + public NetMatcher() { + } + + public NetMatcher(final String[] nets) { + initInetNetworks(nets); + } + + public NetMatcher(final Collection nets) { + initInetNetworks(nets); + } + public void initInetNetworks(final Collection nets) { networks = new ArrayList(); for (final String netName : nets) @@ -71,7 +82,7 @@ public boolean matchInetNetwork(final String hostIP) { boolean sameNet = false; - if (ip != null) for (Iterator iter = networks.iterator(); (!sameNet) && iter.hasNext();) { + if (ip != null) for (Iterator iter = networks.iterator(); (!sameNet) && iter.hasNext(); ) { InetNetwork network = iter.next(); sameNet = network.contains(ip); } @@ -81,24 +92,13 @@ public boolean matchInetNetwork(final String hostIP) { public boolean matchInetNetwork(final InetAddress ip) { boolean sameNet = false; - for (Iterator iter = networks.iterator(); (!sameNet) && iter.hasNext();) { + for (Iterator iter = networks.iterator(); (!sameNet) && iter.hasNext(); ) { InetNetwork network = iter.next(); sameNet = network.contains(ip); } return sameNet; } - public NetMatcher() { - } - - public NetMatcher(final String[] nets) { - initInetNetworks(nets); - } - - public NetMatcher(final Collection nets) { - initInetNetworks(nets); - } - public String toString() { return networks.toString(); } @@ -114,8 +114,19 @@ class InetNetwork { * RFC 1519, which describe CIDR: Classless Inter-Domain Routing. */ - private InetAddress network; + private static java.lang.reflect.Method getByAddress = null; + + static { + try { + Class inetAddressClass = Class.forName("java.net.InetAddress"); + Class[] parameterTypes = {byte[].class}; + getByAddress = inetAddressClass.getMethod("getByAddress", parameterTypes); + } catch (Exception e) { + getByAddress = null; + } + } + private InetAddress network; private InetAddress netmask; public InetNetwork(InetAddress ip, InetAddress netmask) { @@ -123,26 +134,6 @@ public InetNetwork(InetAddress ip, InetAddress netmask) { this.netmask = netmask; } - public boolean contains(final String name) throws java.net.UnknownHostException { - return network.equals(maskIP(InetAddress.getByName(name), netmask)); - } - - public boolean contains(final InetAddress ip) { - return network.equals(maskIP(ip, netmask)); - } - - public String toString() { - return network.getHostAddress() + "/" + netmask.getHostAddress(); - } - - public int hashCode() { - return maskIP(network, netmask).hashCode(); - } - - public boolean equals(Object obj) { - return (obj != null) && (obj instanceof InetNetwork) && ((((InetNetwork) obj).network.equals(network)) && (((InetNetwork) obj).netmask.equals(netmask))); - } - public static InetNetwork getFromString(String netspec) throws java.net.UnknownHostException { if (netspec.endsWith("*")) netspec = normalizeFromAsterisk(netspec); @@ -158,7 +149,7 @@ public static InetNetwork getFromString(String netspec) throws java.net.UnknownH public static final InetAddress maskIP(final byte[] ip, final byte[] mask) { try { - return getByAddress(new byte[] { (byte) (mask[0] & ip[0]), (byte) (mask[1] & ip[1]), (byte) (mask[2] & ip[2]), (byte) (mask[3] & ip[3])}); + return getByAddress(new byte[]{(byte) (mask[0] & ip[0]), (byte) (mask[1] & ip[1]), (byte) (mask[2] & ip[2]), (byte) (mask[3] & ip[3])}); } catch (final Exception ignored) { } return null; @@ -171,14 +162,14 @@ public static InetAddress maskIP(final InetAddress ip, final InetAddress mask) { /* * This converts from an uncommon "wildcard" CIDR format * to "address + mask" format: - * + * * * => 000.000.000.0/000.000.000.0 * xxx.* => xxx.000.000.0/255.000.000.0 * xxx.xxx.* => xxx.xxx.000.0/255.255.000.0 * xxx.xxx.xxx.* => xxx.xxx.xxx.0/255.255.255.0 */ static private String normalizeFromAsterisk(final String netspec) { - String[] masks = { "0.0.0.0/0.0.0.0", "0.0.0/255.0.0.0", "0.0/255.255.0.0", "0/255.255.255.0"}; + String[] masks = {"0.0.0.0/0.0.0.0", "0.0.0/255.0.0.0", "0.0/255.255.0.0", "0/255.255.255.0"}; char[] srcb = netspec.toCharArray(); int octets = 0; for (int i = 1; i < netspec.length(); i++) { @@ -200,22 +191,10 @@ static private String normalizeFromCIDR(final String netspec) { return netspec.substring(0, netspec.indexOf('/') + 1) + Integer.toString(mask >> 24 & 0xFF, 10) + "." + Integer.toString(mask >> 16 & 0xFF, 10) + "." + Integer.toString(mask >> 8 & 0xFF, 10) + "." + Integer.toString(mask >> 0 & 0xFF, 10); } - private static java.lang.reflect.Method getByAddress = null; - - static { - try { - Class inetAddressClass = Class.forName("java.net.InetAddress"); - Class[] parameterTypes = { byte[].class}; - getByAddress = inetAddressClass.getMethod("getByAddress", parameterTypes); - } catch (Exception e) { - getByAddress = null; - } - } - private static InetAddress getByAddress(byte[] ip) throws java.net.UnknownHostException { InetAddress addr = null; if (getByAddress != null) try { - addr = (InetAddress) getByAddress.invoke(null, new Object[] { ip}); + addr = (InetAddress) getByAddress.invoke(null, new Object[]{ip}); } catch (IllegalAccessException e) { } catch (java.lang.reflect.InvocationTargetException e) { } @@ -229,8 +208,28 @@ private static InetAddress getByAddress(byte[] ip) throws java.net.UnknownHostEx //test public static void main(String[] args) { NetMatcher nm = new NetMatcher(); - nm.initInetNetworks(new String[] { "192.168.0.0/24"}); + nm.initInetNetworks(new String[]{"192.168.0.0/24"}); System.out.println(nm.matchInetNetwork("192.168.0.2")); } + + public boolean contains(final String name) throws java.net.UnknownHostException { + return network.equals(maskIP(InetAddress.getByName(name), netmask)); + } + + public boolean contains(final InetAddress ip) { + return network.equals(maskIP(ip, netmask)); + } + + public String toString() { + return network.getHostAddress() + "/" + netmask.getHostAddress(); + } + + public int hashCode() { + return maskIP(network, netmask).hashCode(); + } + + public boolean equals(Object obj) { + return (obj != null) && (obj instanceof InetNetwork) && ((((InetNetwork) obj).network.equals(network)) && (((InetNetwork) obj).netmask.equals(netmask))); + } } diff --git a/src/lia/util/net/common/NetloggerRecord.java b/src/lia/util/net/common/NetloggerRecord.java index ec303af..07bbef0 100644 --- a/src/lia/util/net/common/NetloggerRecord.java +++ b/src/lia/util/net/common/NetloggerRecord.java @@ -8,27 +8,56 @@ * @author nlmills@g.clemson.edu */ public class NetloggerRecord { - /** the size of the data block read from the disk and posted to the network */ + /** + * the size of the data block read from the disk and posted to the network + */ private int block; - /** tcp buffer size (if 0 system defaults were used) */ + /** + * tcp buffer size (if 0 system defaults were used) + */ private int buffer; - /** the FTP rfc959 completion code of the transfer */ + /** + * the FTP rfc959 completion code of the transfer + */ private String code = "429"; // = "Connection closed; transfer aborted." - /** time the transfer completed */ + /** + * time the transfer completed + */ private Date completed = new Date(); - /** the destination host */ + /** + * the destination host + */ private InetAddress destination = Utils.getLoopbackAddress(); - /** hostname of the server */ + /** + * hostname of the server + */ private InetAddress host = Utils.getLoopbackAddress(); - /** the total number of bytes transferred */ + /** + * the total number of bytes transferred + */ private long nbytes; - /** time the transfer started */ + /** + * time the transfer started + */ private Date start = completed; - /** the number of parallel TCP streams used in the transfer */ + /** + * the number of parallel TCP streams used in the transfer + */ private int streams; - /** the transfer type (RETR or STOR) */ + /** + * the transfer type (RETR or STOR) + */ private String type = "RETR"; + public static String toULMDate(Date date) { + // year month day hour minute second . millisecond + SimpleDateFormat ulmFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); + // Date only has millisecond precision, so tack on zeros for microseconds + String ulmDate = ulmFormat.format(date) + "000"; + + return ulmDate; + } + public int getBuffer() { return buffer; } @@ -109,29 +138,20 @@ public void setType(String type) { this.type = type; } - public static String toULMDate(Date date) { - // year month day hour minute second . millisecond - SimpleDateFormat ulmFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); - // Date only has millisecond precision, so tack on zeros for microseconds - String ulmDate = ulmFormat.format(date) + "000"; - - return ulmDate; - } - public String toULMString() { String ulm = Utils.joinString(" ", - "DATE="+toULMDate(completed), - "HOST="+host.getHostName(), + "DATE=" + toULMDate(completed), + "HOST=" + host.getHostName(), "PROG=fdt", //XXX what about NL.EVNT=FTP_INFO? - "START="+toULMDate(start), - "BUFFER="+buffer, - "BLOCK="+block, - "NBYTES="+nbytes, - "STREAMS="+streams, - "DEST=["+destination.getHostAddress()+"]", - "TYPE="+type, - "CODE="+code); + "START=" + toULMDate(start), + "BUFFER=" + buffer, + "BLOCK=" + block, + "NBYTES=" + nbytes, + "STREAMS=" + streams, + "DEST=[" + destination.getHostAddress() + "]", + "TYPE=" + type, + "CODE=" + code); return ulm; } diff --git a/src/lia/util/net/common/SSHControlStream.java b/src/lia/util/net/common/SSHControlStream.java index 25acc29..3c12da9 100644 --- a/src/lia/util/net/common/SSHControlStream.java +++ b/src/lia/util/net/common/SSHControlStream.java @@ -3,41 +3,32 @@ */ package lia.util.net.common; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.logging.Level; -import java.util.logging.Logger; - import ch.ethz.ssh2.Connection; import ch.ethz.ssh2.InteractiveCallback; import ch.ethz.ssh2.Session; import ch.ethz.ssh2.StreamGobbler; import ch.ethz.ssh2.util.PasswordReader; +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * * @author Adrian Muraru - * */ public class SSHControlStream implements ControlStream { - private static final Logger logger = Logger.getLogger(SSHControlStream.class.getName()); static final String knownHostPath = System.getProperty("user.home") + "/.ssh/known_hosts"; static final String idDSAPath = System.getProperty("user.home") + "/.ssh/id_dsa.fdt"; static final String idRSAPath = System.getProperty("user.home") + "/.ssh/id_rsa.fdt"; - + private static final Logger logger = Logger.getLogger(SSHControlStream.class.getName()); // configuration parameters private final String hostname; private final String username; private final int port; - + /** * the SSH connection & session */ @@ -47,15 +38,12 @@ public class SSHControlStream implements ControlStream { /** * Creates a new SSH control connection on the default ssh port. - * + *

    * Same as {@link #SSHControlStream(String, String, int) SSHControlStream(hostname, username, 22)} - * - * @param hostname: - * remote host - * @param username: - * remote account - * @throws IOException - * in case of failure + * + * @param hostname: remote host + * @param username: remote account + * @throws IOException in case of failure */ public SSHControlStream(String hostname, String username) { this(hostname, username, 22); @@ -63,22 +51,37 @@ public SSHControlStream(String hostname, String username) { /** * Creates a new SSH control connection on the specified remote sshd port - * - * @param port: - * remote sshd port - * @param hostname: - * remote host - * @param username: - * remote account - * @throws IOException - * in case of failure + * + * @param port: remote sshd port + * @param hostname: remote host + * @param username: remote account + * @throws IOException in case of failure */ public SSHControlStream(String hostname, String username, int port) { - this.hostname = hostname; - this.username = username; - this.port = port; + this.hostname = hostname; + this.username = username; + this.port = port; } - + + // TEST + public static void main(String[] args) throws IOException { + ControlStream cs = new SSHControlStream(args[0], args[1]); + cs.startProgram(args[2]); + + /* read stdout */ + InputStream stdout = new StreamGobbler(cs.getProgramStdOut()); + BufferedReader br = new BufferedReader(new InputStreamReader(stdout)); + while (true) { + String line = br.readLine(); + if (line == null) { + break; + } + System.out.println(line); + } + System.out.println("ExitCode:" + cs.getExitCode()); + cs.close(); + } + public void connect() throws IOException { this.conn = new Connection(hostname, port); conn.connect(); @@ -114,7 +117,7 @@ public void connect() throws IOException { if (sshKeyPath == null) { enableKEY = false; } - + while (true) { if ((enableKEY || enableDSA || enableRSA) && conn.isAuthMethodAvailable(username, "publickey")) { @@ -236,12 +239,11 @@ public void connect() throws IOException { this.sess = this.conn.openSession(); this.sess.requestPTY("javash", 0, 0, 0, 0, null); } - + public String getPassword(String message) throws IOException { return PasswordReader.readPassword(message); } - /* (non-Javadoc) * @see lia.util.net.common.ControlStream#startProgram(java.lang.String) */ @@ -277,7 +279,7 @@ public void waitForControlMessage(String expect, boolean allowEOF, boolean grabR if (line == null) { if (allowEOF) { return; - //else + //else } throw new IOException("[" + this.cmd + "] exited. No control message received]"); } @@ -315,6 +317,25 @@ public void saveStdErr() throws IOException { lw.start(); } + /* (non-Javadoc) + * @see lia.util.net.common.ControlStream#getExitCode() + */ + public int getExitCode() { + return this.sess.getExitStatus(); + } + + /* (non-Javadoc) + * @see lia.util.net.common.ControlStream#close() + */ + public void close() { + if (this.sess != null) { + this.sess.close(); + } + if (this.conn != null) { + this.conn.close(); + } + } + /** * asynch write in a local log file of the remote stderr stream */ @@ -368,25 +389,6 @@ public void run() { } } - /* (non-Javadoc) - * @see lia.util.net.common.ControlStream#getExitCode() - */ - public int getExitCode() { - return this.sess.getExitStatus(); - } - - /* (non-Javadoc) - * @see lia.util.net.common.ControlStream#close() - */ - public void close() { - if(this.sess != null) { - this.sess.close(); - } - if(this.conn != null) { - this.conn.close(); - } - } - /** * The logic that one has to implement if "keyboard-interactive" autentication shall be supported. */ @@ -444,23 +446,4 @@ public int getPromptCount() { return promptCount; } } - - // TEST - public static void main(String[] args) throws IOException { - ControlStream cs = new SSHControlStream(args[0], args[1]); - cs.startProgram(args[2]); - - /* read stdout */ - InputStream stdout = new StreamGobbler(cs.getProgramStdOut()); - BufferedReader br = new BufferedReader(new InputStreamReader(stdout)); - while (true) { - String line = br.readLine(); - if (line == null) { - break; - } - System.out.println(line); - } - System.out.println("ExitCode:" + cs.getExitCode()); - cs.close(); - } } diff --git a/src/lia/util/net/common/StoragePathDecoder.java b/src/lia/util/net/common/StoragePathDecoder.java index 519d1e8..e10aa6d 100644 --- a/src/lia/util/net/common/StoragePathDecoder.java +++ b/src/lia/util/net/common/StoragePathDecoder.java @@ -4,65 +4,64 @@ package lia.util.net.common; /** - * * @author Ilya Narsky - * */ public class StoragePathDecoder { - + private String storageType; private String siteStorageID; private String storageRoot; private String pathToFileFromRoot; - + + public StoragePathDecoder(String path, + String siteStorageID, + String storageRoot) { + if (siteStorageID != null) this.siteStorageID = siteStorageID; + if (storageRoot != null) this.storageRoot = storageRoot; + if (path != null) this.decode(path); + } + public String storageType() { - return this.storageType; + return this.storageType; } + public String siteStorageID() { - return this.siteStorageID; + return this.siteStorageID; } + public String storageRoot() { - return this.storageRoot; + return this.storageRoot; } + public String pathToFileFromRoot() { - return this.pathToFileFromRoot; - } - - - public StoragePathDecoder(String path, - String siteStorageID, - String storageRoot) { - if( siteStorageID != null ) this.siteStorageID = siteStorageID; - if( storageRoot != null ) this.storageRoot = storageRoot; - if( path != null ) this.decode(path); + return this.pathToFileFromRoot; } - public boolean hasStorageInfo() { - return (this.storageType!=null && this.storageType.length()!=0); + return (this.storageType != null && this.storageType.length() != 0); } - - + + private void decode(String path) { - // remove spaces - String temp = path.trim(); - - // the first part up to "_//" is treated as storage - if( temp.contains("_//") ) { - String[] splitByStorage = temp.split("_//",2); - this.storageType = splitByStorage[0]; - temp = splitByStorage[1]; - } - - // remove the site name from the string - temp = temp.replaceFirst(this.siteStorageID,""); - - // remove the storage root from the string - temp = temp.replaceFirst(this.storageRoot,""); - - // the rest of it is supposed to be the full path to file - this.pathToFileFromRoot = temp; + // remove spaces + String temp = path.trim(); + + // the first part up to "_//" is treated as storage + if (temp.contains("_//")) { + String[] splitByStorage = temp.split("_//", 2); + this.storageType = splitByStorage[0]; + temp = splitByStorage[1]; + } + + // remove the site name from the string + temp = temp.replaceFirst(this.siteStorageID, ""); + + // remove the storage root from the string + temp = temp.replaceFirst(this.storageRoot, ""); + + // the rest of it is supposed to be the full path to file + this.pathToFileFromRoot = temp; }// end of decode() - + } diff --git a/src/lia/util/net/common/Test.java b/src/lia/util/net/common/Test.java index d16380f..bf70a3f 100644 --- a/src/lia/util/net/common/Test.java +++ b/src/lia/util/net/common/Test.java @@ -11,24 +11,24 @@ * @author Adrian Muraru */ public class Test { - public static void main(String[] args) { - - String sshFormat = "((([a-zA-Z0-9][a-zA-Z0-9]*)@)?(([a-zA-Z0-9][a-zA-Z0-9\\-]*\\.?)+):)?((" + (File.separatorChar == '\\' ? File.separatorChar : "") + File.separatorChar - + "?[a-zA-Z_\\-0-9\\.]*)+)";// - Pattern pattern = Pattern.compile(sshFormat); - Matcher match = pattern.matcher("dest.xml"); - System.out.println(match.matches()); - - String user = match.group(3); - if (user != null && user.trim().length() == 0) - user = null; - String host = match.group(4); - if (host != null && host.trim().length() == 0) - host = null; - String path = match.group(6); - if (path == null || path.trim().length() == 0) - path = "."; - System.out.println( " user " + (user == null ? "none" : user) + " host " + host + " path " + path); - - } + public static void main(String[] args) { + + String sshFormat = "((([a-zA-Z0-9][a-zA-Z0-9]*)@)?(([a-zA-Z0-9][a-zA-Z0-9\\-]*\\.?)+):)?((" + (File.separatorChar == '\\' ? File.separatorChar : "") + File.separatorChar + + "?[a-zA-Z_\\-0-9\\.]*)+)";// + Pattern pattern = Pattern.compile(sshFormat); + Matcher match = pattern.matcher("dest.xml"); + System.out.println(match.matches()); + + String user = match.group(3); + if (user != null && user.trim().length() == 0) + user = null; + String host = match.group(4); + if (host != null && host.trim().length() == 0) + host = null; + String path = match.group(6); + if (path == null || path.trim().length() == 0) + path = "."; + System.out.println(" user " + (user == null ? "none" : user) + " host " + host + " path " + path); + + } } diff --git a/src/lia/util/net/common/Test2MD5Sum.java b/src/lia/util/net/common/Test2MD5Sum.java index 1efd63a..e563984 100644 --- a/src/lia/util/net/common/Test2MD5Sum.java +++ b/src/lia/util/net/common/Test2MD5Sum.java @@ -15,83 +15,84 @@ import java.util.TreeMap; /** - * * @author ramiro */ public class Test2MD5Sum { - - /** Creates a new instance of Test2MD5Sum */ + + /** + * Creates a new instance of Test2MD5Sum + */ public Test2MD5Sum() { } - + public static final void main(String[] args) throws Exception { - - if(args.length != 2) { + + if (args.length != 2) { System.err.println("Usage java -jar fdt.jar " + Test2MD5Sum.class.getName() + " md5sumFile1 md5sumFile2"); System.exit(1); } - + TreeMap firstMap = getMap(args[0]); TreeMap secondMap = getMap(args[1]); - + System.out.println(" FirstMaps size: " + firstMap.size() + " SecondMap size: " + secondMap.size()); - - if(firstMap.size() != secondMap.size()) { + + if (firstMap.size() != secondMap.size()) { System.err.println(" Different size() ... will exit"); System.exit(1); } ArrayList nokFList = new ArrayList(); //check the first map - for(Map.Entry entry: firstMap.entrySet() ) { + for (Map.Entry entry : firstMap.entrySet()) { final String fName = entry.getKey(); final String md5Sum = entry.getValue(); - + final String md5Check = secondMap.get(fName); - - if(md5Check == null) { + + if (md5Check == null) { System.err.println(" The file " + fName + " form first map cannot be found in the second map"); System.exit(1); } - - if(md5Check.equals(md5Sum)) { + + if (md5Check.equals(md5Sum)) { System.out.println(" File " + fName + " [ " + md5Sum + " ] is OK"); } else { System.err.println(" File " + fName + " [ " + md5Sum + " ] is NOT OK"); nokFList.add(fName); } } - - if(nokFList.size() == 0) { + + if (nokFList.size() == 0) { System.out.println(" Total md5sums compared: " + firstMap.size() + " ... All OK!"); } else { System.out.println(" Total md5sums compared: " + firstMap.size() + " ... NOT OK = " + nokFList.size()); int i = 1; - for(String fName: nokFList) { - System.out.println(" NOKFile " + (i++) +" : " + fName); + for (String fName : nokFList) { + System.out.println(" NOKFile " + (i++) + " : " + fName); } } } - + private static final TreeMap getMap(final String fName) throws Exception { BufferedReader br = new BufferedReader(new FileReader(fName)); - + String line; TreeMap map = new TreeMap(); - - while((line = br.readLine()) != null) { + + while ((line = br.readLine()) != null) { int firstSpace = line.indexOf(" "); - if(firstSpace < 0) { + if (firstSpace < 0) { System.out.println(" Ignoring line " + line + " from file " + fName); } else { map.put(line.substring(firstSpace + 1), line.substring(0, firstSpace)); } } - + br.close(); - + return map; } } diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index 95cd384..0f28e42 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -34,72 +34,44 @@ */ public final class Utils { + public static final char ZERO = '0'; + public static final int VALUE_2_STRING_NO_UNIT = 1; + public static final int VALUE_2_STRING_UNIT = 2; + public static final int VALUE_2_STRING_SHORT_UNIT = 3; /** * Logger used by this class */ private static final Logger logger = Logger.getLogger(Utils.class.getName()); - private static final ScheduledThreadPoolExecutor scheduledExecutor = getSchedExecService("FDT Monitoring ThPool", 5, Thread.MIN_PRIORITY); - public static final char ZERO = '0'; - - /** - * reference to the monitor reporting api, initialized in the constructor of {@link FDT} - */ - private static ApMon apmon = null; - - private static boolean apmonInitied = false; - - public static final int VALUE_2_STRING_NO_UNIT = 1; - - public static final int VALUE_2_STRING_UNIT = 2; - - public static final int VALUE_2_STRING_SHORT_UNIT = 3; - private static final int AV_PROCS; - private static final long KILO_BIT = 1000; - public static final long MEGA_BIT = KILO_BIT * 1000; - private static final long GIGA_BIT = MEGA_BIT * 1000; - private static final long TERA_BIT = GIGA_BIT * 1000; - private static final long PETA_BIT = TERA_BIT * 1000; - private static final long KILO_BYTE = 1024; - public static final long MEGA_BYTE = KILO_BYTE * 1024; - private static final long GIGA_BYTE = MEGA_BYTE * 1024; - private static final long TERA_BYTE = GIGA_BYTE * 1024; - private static final long PETA_BYTE = TERA_BYTE * 1024; - private static final long[] BYTE_MULTIPLIERS = new long[]{KILO_BYTE, MEGA_BYTE, GIGA_BYTE, TERA_BYTE, PETA_BYTE}; - private static final String[] BYTE_SUFIXES = new String[]{"KB", "MB", "GB", "TB", "PB"}; - private static final long[] BIT_MULTIPLIERS = new long[]{KILO_BIT, MEGA_BIT, GIGA_BIT, TERA_BIT, PETA_BIT}; - private static final String[] BIT_SUFIXES = new String[]{"Kb", "Mb", "Gb", "Tb", "Pb"}; - private static final int URL_CONNECTION_TIMEOUT = 20 * 1000; - private static final Object lock = new Object(); - private static final long SECONDS_IN_MINUTE = TimeUnit.MINUTES.toSeconds(1); - private static final long SECONDS_IN_HOUR = TimeUnit.HOURS.toSeconds(1); - private static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); - private static final String[] SELECTION_KEY_OPS_NAMES = {"OP_ACCEPT", "OP_CONNECT", "OP_READ", "OP_WRITE"}; - private static final int[] SELECTION_KEY_OPS_VALUES = {SelectionKey.OP_ACCEPT, SelectionKey.OP_CONNECT, SelectionKey.OP_READ, SelectionKey.OP_WRITE}; + /** + * reference to the monitor reporting api, initialized in the constructor of {@link FDT} + */ + private static ApMon apmon = null; + private static boolean apmonInitied = false; // // END this should not be here any more after FDT will use only Java6 @@ -1210,9 +1182,7 @@ public static boolean updateFDT(final String currentVersion, final String update String downloadUrl = getDownloadURL(jsonObject); downloadFDT(fos, downloadUrl); // try to check the version - } - else - { + } else { downloadFDT(fos, updateURL); } jf = new JarFile(tmpUpdateFile); @@ -1232,9 +1202,7 @@ public static boolean updateFDT(final String currentVersion, final String update } logger.info("Remote FDT version: " + remoteVersion + " Local FDT version: " + currentVersion + ". Update available."); - } - else - { + } else { logger.info("Skipped version checking."); } @@ -1301,14 +1269,10 @@ public static boolean updateFDT(final String currentVersion, final String update private static void downloadFDT(FileOutputStream fos, String downloadURL) throws IOException { InputStream downInputStream; - if (!downloadURL.endsWith("fdt.jar")) - { - if(!downloadURL.endsWith("/")) - { + if (!downloadURL.endsWith("fdt.jar")) { + if (!downloadURL.endsWith("/")) { downloadURL = downloadURL + "/fdt.jar"; - } - else - { + } else { downloadURL = downloadURL + "fdt.jar"; } } @@ -1740,22 +1704,20 @@ static InetAddress getLoopbackAddress() { public static ArrayBlockingQueue getTransportPortsValue(Map configMap, String key, int defaultPortNo) { ArrayBlockingQueue transportPorts = new ArrayBlockingQueue<>(10); - int i=0; + int i = 0; Object obj = configMap.get(key); - if (obj == null || obj.toString().isEmpty()) - { + if (obj == null || obj.toString().isEmpty()) { transportPorts.add(defaultPortNo); return transportPorts; } String tp = obj.toString(); - StringTokenizer stk=new StringTokenizer(tp,","); - String s[]=new String[10]; - while(stk.hasMoreTokens()) - { - s[i]=stk.nextToken(); - transportPorts.add(Integer.parseInt(s[i])); - i++; - } + StringTokenizer stk = new StringTokenizer(tp, ","); + String s[] = new String[10]; + while (stk.hasMoreTokens()) { + s[i] = stk.nextToken(); + transportPorts.add(Integer.parseInt(s[i])); + i++; + } return transportPorts; } diff --git a/src/lia/util/net/copy/Accountable.java b/src/lia/util/net/copy/Accountable.java index b76fc3f..a77542e 100644 --- a/src/lia/util/net/copy/Accountable.java +++ b/src/lia/util/net/copy/Accountable.java @@ -4,19 +4,21 @@ package lia.util.net.copy; /** - * * Simple interface implemented by all the FDT classes which may have something * to, or that, count :) - * - * @author ramiro * + * @author ramiro */ public interface Accountable { - + public long getUtilBytes(); + public long getTotalBytes(); + public long getSize(); + public long addAndGetUtilBytes(long delta); + public long addAndGetTotalBytes(long delta); - + } diff --git a/src/lia/util/net/copy/AccountableEntity.java b/src/lia/util/net/copy/AccountableEntity.java index f0ed271..a639677 100644 --- a/src/lia/util/net/copy/AccountableEntity.java +++ b/src/lia/util/net/copy/AccountableEntity.java @@ -6,16 +6,24 @@ import java.util.concurrent.atomic.AtomicLong; /** - * * Default implementation for {@link Accountable} + * * @author ramiro - * */ public abstract class AccountableEntity implements Accountable { AtomicLong totalProcessedBytes; AtomicLong totalUtilBytes; - + + public AccountableEntity() { + this(0, 0); + } + + public AccountableEntity(long initialProcessedBytes, long initialUtilBytes) { + totalProcessedBytes = new AtomicLong(initialProcessedBytes); + totalUtilBytes = new AtomicLong(initialUtilBytes); + } + public long addAndGetTotalBytes(long delta) { return totalProcessedBytes.addAndGet(delta); } @@ -32,14 +40,5 @@ public long getUtilBytes() { return totalUtilBytes.get(); } - public AccountableEntity() { - this(0, 0); - } - public abstract long getSize(); - - public AccountableEntity(long initialProcessedBytes, long initialUtilBytes) { - totalProcessedBytes = new AtomicLong(initialProcessedBytes); - totalUtilBytes = new AtomicLong(initialUtilBytes); - } } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 8448781..ffa067d 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -34,38 +34,20 @@ public class FDT { public static final String MONALISA2_CERN_CH = "monalisa2.cern.ch:28884"; - private static final String name = "FDT"; - - private static final Logger logger = Logger.getLogger(FDT.class.getName()); - - private static String UPDATE_OWNER = "fast-data-transfer"; - private static String UPDATE_REPO = "fdt"; - public static String UPDATE_URL = "https://api.github.com/repos/" + UPDATE_OWNER + "/" + UPDATE_REPO + "/releases"; - public static final String FDT_FULL_VERSION = "0.26.0-201708081850"; - /** * two weeks between checking for updates */ public static final long UPDATE_PERIOD = 2 * 24 * 3600 * 1000; - + private static final String name = "FDT"; + private static final Logger logger = Logger.getLogger(FDT.class.getName()); + private static String UPDATE_OWNER = "fast-data-transfer"; + private static String UPDATE_REPO = "fdt"; + public static String UPDATE_URL = "https://api.github.com/repos/" + UPDATE_OWNER + "/" + UPDATE_REPO + "/releases"; private static Config config; private static Properties localProps = new Properties(); - /** - * Helper class for "graceful" shutdown of FDT. - */ - private final static class GracefulStopper extends AbstractFDTCloseable { - - private boolean internalClosed = false; - - protected synchronized void internalClose() throws Exception { - this.internalClosed = true; - this.notifyAll(); - } - } - FDT() throws Exception { // initialize monitoring, if requested @@ -79,8 +61,7 @@ protected synchronized void internalClose() throws Exception { // wait for remote config if (sessionID.equals("-1")) { logger.log(Level.WARNING, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " but no free transfer ports available"); - } else - { + } else { logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " Remote Job Session ID: " + sessionID); } System.exit(0); @@ -109,42 +90,6 @@ protected synchronized void internalClose() throws Exception { } } - private void waitForTask() throws Exception { - if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { - // this is really wrong ... It cannot be already initialized - throw new FDTProcolException("The buffer pool cannot be already initialized"); - } - - ExecutorService executor = null; - ServerSocketChannel ssc = null; - ServerSocket ss = null; - Selector sel = null; - try { - executor = Utils.getStandardExecService("[ Acceptable ServersThreadPool ] ", - 2, - 10, - new ArrayBlockingQueue(65500), - Thread.NORM_PRIORITY - 2); - ssc = ServerSocketChannel.open(); - ssc.configureBlocking(false); - ss = ssc.socket(); - ss.bind(new InetSocketAddress(config.getPort())); - sel = Selector.open(); - ssc.register(sel, SelectionKey.OP_ACCEPT); - System.out.println("READY"); - Utils.waitAndWork(executor, ss, sel, config); - } finally { - logger.log(Level.INFO, "[FDT] [ waitForTask ] main loop FINISHED!"); - // close all the stuff - Utils.closeIgnoringExceptions(ssc); - Utils.closeIgnoringExceptions(sel); - Utils.closeIgnoringExceptions(ss); - if (executor != null) { - executor.shutdown(); - } - } - } - private static void scheduleReportingTasks() { Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, TimeUnit.SECONDS); @@ -155,93 +100,6 @@ private static void scheduleReportingTasks() { } } - private void initApMon() throws Exception { - final String configApMonHosts = config.getApMonHosts(); - if (configApMonHosts != null) { - long lStart = System.currentTimeMillis(); - - ApMon apmon = null; - - final String apMonHosts = (configApMonHosts.length() > 0) ? configApMonHosts : MONALISA2_CERN_CH; - - logger.info("Trying to instantiate apMon to: " + apMonHosts); - try { - Vector vHosts = new Vector<>(); - Vector vPorts = new Vector<>(); - final String[] apMonDstTks = apMonHosts.split(","); - - if (apMonDstTks.length == 0) { - logger.log(Level.WARNING, "\n\nApMon enabled but no hosts defined! Cannot send apmon statistics\n\n"); - } else { - for (String host_port : apMonDstTks) { - int index; - String host; - int port; - if ((index = host_port.indexOf(':')) != -1) { - host = host_port.substring(0, index); - try { - port = Integer.parseInt(host_port.substring(index + 1)); - } catch (Exception ex) { - port = 28884; - } - } else { - host = host_port; - port = 28884; - } - vHosts.add(host); - vPorts.add(port); - } - - ApMon.setLogLevel("WARNING"); - apmon = new ApMon(vHosts, vPorts); - apmon.setConfRecheck(false, -1); - apmon.setGenMonitoring(true, 40); - // apmon.setJobMonitoring(, ) - // apmon.setMaxMsgRate(50); - String cluster_name; - String node_name; - if (config.getHostName() != null) {// client - cluster_name = "Clients"; - node_name = config.getHostName(); - } else {// server - cluster_name = "Servers"; - node_name = apmon.getMyHostname(); - } - apmon.setMonitorClusterNode(cluster_name, node_name); - // apmon.setRecheckInterval(-1) - apmon.setSysMonitoring(true, 40); - try { - apmon.sendParameter(cluster_name, node_name, "FDT_version", FDT_FULL_VERSION); - } catch (Exception e) { - logger.info("Send operation failed: "); - e.printStackTrace(); - } - - } - } catch (Throwable ex) { - logger.log(Level.WARNING, "Error initializing ApMon engine.", ex); - } finally { - Utils.initApMonInstance(apmon); - } - - try { - if (Utils.getApMon() != null) { - ApMonReportingTask apmrt = new ApMonReportingTask(); - Utils.getMonitoringExecService().scheduleWithFixedDelay(apmrt, 1, - config.getApMonReportingInterval(), TimeUnit.SECONDS); - } else { - logger.log(Level.WARNING, "Cannot start ApMonReportingTask because apMon is null!"); - } - } catch (Throwable t) { - logger.log(Level.WARNING, "Cannot start ApMonReportingTask because got Exception.", t); - } - - long lEnd = System.currentTimeMillis(); - logger.info("ApMon initialization took " + (lEnd - lStart) + " ms"); - } - } - - private static void printOutResults(List filesInDir) { StringBuilder sb = new StringBuilder(); sb.append("\r\n"); @@ -749,4 +607,139 @@ private static String initLogging(String[] args) throws IOException { Utils.initLogger(logLevel, logFile, localProps); return logLevel; } + + private void waitForTask() throws Exception { + if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { + // this is really wrong ... It cannot be already initialized + throw new FDTProcolException("The buffer pool cannot be already initialized"); + } + + ExecutorService executor = null; + ServerSocketChannel ssc = null; + ServerSocket ss = null; + Selector sel = null; + try { + executor = Utils.getStandardExecService("[ Acceptable ServersThreadPool ] ", + 2, + 10, + new ArrayBlockingQueue(65500), + Thread.NORM_PRIORITY - 2); + ssc = ServerSocketChannel.open(); + ssc.configureBlocking(false); + ss = ssc.socket(); + ss.bind(new InetSocketAddress(config.getPort())); + sel = Selector.open(); + ssc.register(sel, SelectionKey.OP_ACCEPT); + System.out.println("READY"); + Utils.waitAndWork(executor, ss, sel, config); + } finally { + logger.log(Level.INFO, "[FDT] [ waitForTask ] main loop FINISHED!"); + // close all the stuff + Utils.closeIgnoringExceptions(ssc); + Utils.closeIgnoringExceptions(sel); + Utils.closeIgnoringExceptions(ss); + if (executor != null) { + executor.shutdown(); + } + } + } + + private void initApMon() throws Exception { + final String configApMonHosts = config.getApMonHosts(); + if (configApMonHosts != null) { + long lStart = System.currentTimeMillis(); + + ApMon apmon = null; + + final String apMonHosts = (configApMonHosts.length() > 0) ? configApMonHosts : MONALISA2_CERN_CH; + + logger.info("Trying to instantiate apMon to: " + apMonHosts); + try { + Vector vHosts = new Vector<>(); + Vector vPorts = new Vector<>(); + final String[] apMonDstTks = apMonHosts.split(","); + + if (apMonDstTks.length == 0) { + logger.log(Level.WARNING, "\n\nApMon enabled but no hosts defined! Cannot send apmon statistics\n\n"); + } else { + for (String host_port : apMonDstTks) { + int index; + String host; + int port; + if ((index = host_port.indexOf(':')) != -1) { + host = host_port.substring(0, index); + try { + port = Integer.parseInt(host_port.substring(index + 1)); + } catch (Exception ex) { + port = 28884; + } + } else { + host = host_port; + port = 28884; + } + vHosts.add(host); + vPorts.add(port); + } + + ApMon.setLogLevel("WARNING"); + apmon = new ApMon(vHosts, vPorts); + apmon.setConfRecheck(false, -1); + apmon.setGenMonitoring(true, 40); + // apmon.setJobMonitoring(, ) + // apmon.setMaxMsgRate(50); + String cluster_name; + String node_name; + if (config.getHostName() != null) {// client + cluster_name = "Clients"; + node_name = config.getHostName(); + } else {// server + cluster_name = "Servers"; + node_name = apmon.getMyHostname(); + } + apmon.setMonitorClusterNode(cluster_name, node_name); + // apmon.setRecheckInterval(-1) + apmon.setSysMonitoring(true, 40); + try { + apmon.sendParameter(cluster_name, node_name, "FDT_version", FDT_FULL_VERSION); + } catch (Exception e) { + logger.info("Send operation failed: "); + e.printStackTrace(); + } + + } + } catch (Throwable ex) { + logger.log(Level.WARNING, "Error initializing ApMon engine.", ex); + } finally { + Utils.initApMonInstance(apmon); + } + + try { + if (Utils.getApMon() != null) { + ApMonReportingTask apmrt = new ApMonReportingTask(); + Utils.getMonitoringExecService().scheduleWithFixedDelay(apmrt, 1, + config.getApMonReportingInterval(), TimeUnit.SECONDS); + } else { + logger.log(Level.WARNING, "Cannot start ApMonReportingTask because apMon is null!"); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Cannot start ApMonReportingTask because got Exception.", t); + } + + long lEnd = System.currentTimeMillis(); + logger.info("ApMon initialization took " + (lEnd - lStart) + " ms"); + } + } + + /** + * Helper class for "graceful" shutdown of FDT. + */ + private final static class GracefulStopper extends AbstractFDTCloseable { + + private boolean internalClosed = false; + + protected synchronized void internalClose() throws Exception { + this.internalClosed = true; + this.notifyAll(); + } + } } diff --git a/src/lia/util/net/copy/FDTMain.java b/src/lia/util/net/copy/FDTMain.java index 7da8ac2..1c4c87d 100644 --- a/src/lia/util/net/copy/FDTMain.java +++ b/src/lia/util/net/copy/FDTMain.java @@ -7,11 +7,10 @@ /** * Just a wrapper to check for Java version.
    - * FDT main class is invoked using reflection - * - * @since FDT 0.9.22 + * FDT main class is invoked using reflection + * * @author ramiro - * + * @since FDT 0.9.22 */ public class FDTMain { @@ -49,18 +48,18 @@ public static void main(String[] args) throws Exception { // Reflection is the only way to go ... try { Class fdtMainClass = Class.forName("lia.util.net.copy.FDT"); - Class[] sClass = new Class[] { - args.getClass() + Class[] sClass = new Class[]{ + args.getClass() }; Method mainMethod = fdtMainClass.getDeclaredMethod("main", sClass); - mainMethod.invoke((Object) null, new Object[] { - args + mainMethod.invoke((Object) null, new Object[]{ + args }); - } catch(Throwable t) { + } catch (Throwable t) { t.printStackTrace(); System.exit(22); } - + } } diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 76b18d9..afb50d4 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -33,41 +33,26 @@ */ public class FDTReaderSession extends FDTSession implements FileBlockProducer { + public static final long END_RCV_WAIT_DELAY = TimeUnit.SECONDS.toNanos(120); /** * Logger used by this class */ private static final Logger logger = Logger.getLogger(FDTReaderSession.class.getName()); - private static final DiskReaderManager diskManager = DiskReaderManager.getInstance(); - private static final Config config = Config.getInstance(); - - private final TreeMap> readersMap; - + private static final int MAX_TAKE_POLL_ITER = Config.getMaxTakePollIter(); public final BlockingQueue fileBlockQueue; - + private final TreeMap> readersMap; + private final boolean isFileList; + private final AtomicBoolean finalCleaupExecuted = new AtomicBoolean(false); + private final AtomicBoolean finishNotifiedExecuted = new AtomicBoolean(false); private volatile ExecutorService execService; - private String remoteDir; - private boolean recursive; - - private final boolean isFileList; - private int totalFileBlocks = 0; - private ProcessorInfo processorInfo; - private int readersCount = 1; - private static final int MAX_TAKE_POLL_ITER = Config.getMaxTakePollIter(); - - private final AtomicBoolean finalCleaupExecuted = new AtomicBoolean(false); - - private final AtomicBoolean finishNotifiedExecuted = new AtomicBoolean(false); - - public static final long END_RCV_WAIT_DELAY = TimeUnit.SECONDS.toNanos(120); - /** * LOCAL SESSION - look in the Config * diff --git a/src/lia/util/net/copy/FDTServer.java b/src/lia/util/net/copy/FDTServer.java index 1cb2e2a..423e9bd 100644 --- a/src/lia/util/net/copy/FDTServer.java +++ b/src/lia/util/net/copy/FDTServer.java @@ -47,15 +47,6 @@ public class FDTServer extends AbstractFDTCloseable { UUID fdtSessionID; - static final class FDTServerMonitorTask implements Runnable { - - public void run() { - // TODO Later - } - } - - - public FDTServer(int port) throws Exception { hasToRun = new AtomicBoolean(true); @@ -88,19 +79,6 @@ public FDTServer(int port) throws Exception { System.out.println("READY"); } - /** - * Safe to call multiple times; will return false if the server was already signaled to stop - *
    - *

    - * Note: Invoking this method acts as a signal for the server. Any ongoing transfers will continue until they finish - *

    - * - * @return true if server was signaled to stop - */ - public boolean stopServer() { - return hasToRun.compareAndSet(true, false); - } - public static final boolean filterSourceAddress(java.net.Socket socket) { /** * check if remote client is allowed (based on optional filter specified in command line) if the address does @@ -121,6 +99,24 @@ public static final boolean filterSourceAddress(java.net.Socket socket) { } + public static final void main(String[] args) throws Exception { + FDTServer jncs = new FDTServer(config.getPort()); + jncs.doWork(); + } + + /** + * Safe to call multiple times; will return false if the server was already signaled to stop + *
    + *

    + * Note: Invoking this method acts as a signal for the server. Any ongoing transfers will continue until they finish + *

    + * + * @return true if server was signaled to stop + */ + public boolean stopServer() { + return hasToRun.compareAndSet(true, false); + } + public UUID getFdtSessionID() { return fdtSessionID; } @@ -193,11 +189,6 @@ public void doWork() throws Exception { } } - public static final void main(String[] args) throws Exception { - FDTServer jncs = new FDTServer(config.getPort()); - jncs.doWork(); - } - public void run() { try { @@ -218,4 +209,11 @@ protected void internalClose() { } + static final class FDTServerMonitorTask implements Runnable { + + public void run() { + // TODO Later + } + } + } diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 2bf23f5..de9991d 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -37,135 +37,81 @@ public abstract class FDTSession extends IOSession implements ControlChannelNotifier, Comparable, Accountable, LisaCtrlNotifier { - /** - * Logger used by this class - */ - private static final Logger logger = Logger.getLogger(FDTSession.class.getName()); - - private static final String LISA_RATE_LIMIT_CMD = "limit"; - - private static final Config config = Config.getInstance(); - public static final short SERVER = 0; - public static final short CLIENT = 1; - public static final short COORDINATOR = 2; - public static final int UNINITIALIZED = 0; // I think only OOM can do this - public static final int STARTED = 1 << 0; - public static final int INIT_CONF_SENT = 1 << 1; - public static final int INIT_CONF_RCV = 1 << 2; - public static final int FINAL_CONF_SENT = 1 << 3; - public static final int FINAL_CONF_RCV = 1 << 4; - public static final int START_SENT = 1 << 5; - public static final int START_RCV = 1 << 6; - public static final int TRANSFERING = 1 << 7; - public static final int END_SENT = 1 << 8; - public static final int END_RCV = 1 << 8; - public static final int COORDINATOR_MSG_RCVD = 1 << 9; - public static final int LIST_FILES_MSG_RCVD = 1 << 10; - - protected AtomicLong totalProcessedBytes; - - protected AtomicLong totalUtilBytes; - - protected String monID; - - // should be 0 in case everything works fine and !=0 in case of an error - protected short currentStatus; - - protected ServerSocketChannel ssc; - - protected ServerSocket ss; - - protected Selector sel; - - protected SocketChannel sc; - - protected Socket s; - - ExecutorService executor; - protected static final String[] FDT_SESION_STATES = {"UNINITIALIZED", "STARTED", "INIT_CONF_SENT", "INIT_CONF_RCV", "FINAL_CONF_SENT", "FINAL_CONF_RCV", "START_SENT", "START_RCV", "TRANSFERING", "END_SENT", "END_RCV"}; - - protected Map> partitionsMap; - + /** + * Logger used by this class + */ + private static final Logger logger = Logger.getLogger(FDTSession.class.getName()); + private static final String LISA_RATE_LIMIT_CMD = "limit"; + private static final Config config = Config.getInstance(); /** * can be either SERVER, either CLIENT */ protected final short role; // for the moment could be boolean ... but never know for future extensions, e.g third - - // party transfers! - protected final Object protocolLock = new Object(); - - protected ControlChannel controlChannel; - //to keep the order in which they were added use a LinkedHashMap protected final Map fileSessions = new LinkedHashMap(); - //to keep the order in which they were added use a LinkedHashMap protected final Map md5Sums = new LinkedHashMap(); - protected final boolean isNetTest; + protected final Object ctrlNotifLock = new Object(); + protected final boolean customLog; + final FDTSessionMonitoringTask monitoringTask; + final ScheduledFuture monitoringTaskFuture; + private final Object lock = new Object(); + protected AtomicLong totalProcessedBytes; + protected AtomicLong totalUtilBytes; + // party transfers! + protected String monID; + // should be 0 in case everything works fine and !=0 in case of an error + protected short currentStatus; + protected ServerSocketChannel ssc; + protected ServerSocket ss; + protected Selector sel; + protected SocketChannel sc; + protected Socket s; + protected Map> partitionsMap; + protected ControlChannel controlChannel; protected Set finishedSessions = new TreeSet(); - protected TCPTransportProvider transportProvider; - - private final Object lock = new Object(); - protected AtomicBoolean postProcessingDone = new AtomicBoolean(false); - - protected final Object ctrlNotifLock = new Object(); - - // keeps the history of the states - private volatile int historyState; - - // current state of the session - private volatile int currentState; - - // control thread started - AtomicBoolean ctrlThreadStarted = new AtomicBoolean(false); - // use fixed block size for network I/O ? protected boolean useFixedBlockSize = config.useFixedBlocks(); - // do not try to write on the writer peer protected boolean localLoop = config.localLoop(); - protected int transferPort; - // is loop ? protected boolean isLoop = config.loop(); - protected String writeMode = config.getWriteMode(); - // rateLimit ? protected AtomicLong rateLimit = new AtomicLong(-1); - protected AtomicLong rateLimitDelay = new AtomicLong(300L); - - final FDTSessionMonitoringTask monitoringTask; - - final ScheduledFuture monitoringTaskFuture; - - protected final boolean customLog; + ExecutorService executor; + // control thread started + AtomicBoolean ctrlThreadStarted = new AtomicBoolean(false); + // keeps the history of the states + private volatile int historyState; + // current state of the session + private volatile int currentState; public FDTSession(short role, int transferPort) throws Exception { super(); @@ -220,17 +166,6 @@ public FDTSession(short role, int transferPort) throws Exception { monitoringTask.startSession(); } - final void startControlThread() { - if (ctrlThreadStarted.compareAndSet(false, true)) { - new Thread(this.controlChannel, "Control channel for [ " + config.getHostName() + ":" + transferPort - + " ]").start(); - } - } - - public String getMonID() { - return monID; - } - public FDTSession(ControlChannel controlChannel, short role) throws Exception { // it is possible to throw a NPE? super(controlChannel.fdtSessionID()); @@ -283,6 +218,57 @@ public FDTSession(ControlChannel controlChannel, short role) throws Exception { monitoringTask.startSession(); } + public static List getListOfFiles() { + File[] filesList = new File(config.getListFilesFrom()).listFiles(); + List listOfFiles = new ArrayList<>(); + + if (filesList != null) { + for (File fileInDir : filesList) { + if (fileInDir.canRead()) { + listOfFiles.add(getFileListEntry(fileInDir)); + } + } + } + return listOfFiles; + } + + private static String getFileListEntry(File fileInDir) { + + StringBuilder sb = new StringBuilder(); + try { + PosixFileAttributes fa = Files.readAttributes(fileInDir.toPath(), PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + sb.append(fa.isDirectory() ? "d" : fa.isSymbolicLink() ? "l" : fa.isRegularFile() ? "f" : "-"); + sb.append(fileInDir.canRead() ? "r" : "-"); + sb.append(fileInDir.canWrite() ? "w" : "-"); + sb.append(fileInDir.canExecute() ? "x" : "-"); + sb.append("\t"); + sb.append(fa.owner()); + sb.append(fa.owner().getName().length() < 4 ? "\t\t" : "\t"); + sb.append(fa.group()); + sb.append(fa.group().getName().length() < 4 ? "\t\t" : "\t"); + sb.append(fa.size()); + sb.append(String.valueOf(fa.size()).length() < 4 ? "\t\t" : "\t"); + sb.append(fa.lastModifiedTime().toString()); + sb.append("\t"); + sb.append(fa.isDirectory() ? fileInDir.getName() + "/" : fileInDir.getName()); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to get file attributes", e); + } + logger.info(sb.toString()); + return sb.toString(); + } + + final void startControlThread() { + if (ctrlThreadStarted.compareAndSet(false, true)) { + new Thread(this.controlChannel, "Control channel for [ " + config.getHostName() + ":" + transferPort + + " ]").start(); + } + } + + public String getMonID() { + return monID; + } + public FDTSessionMonitoringTask getMonitoringTask() { return monitoringTask; } @@ -476,9 +462,7 @@ private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { if (remoteTransferPort > 0) { FDTSession session = FDTSessionManager.getInstance().addFDTClientSession(remoteTransferPort); ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, session.controlChannel.fdtSessionID().toString())); - } - else - { + } else { ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, "-1")); } } catch (Exception ex) { @@ -742,44 +726,4 @@ public boolean isNetTest() { return isNetTest; } - public static List getListOfFiles() { - File[] filesList = new File(config.getListFilesFrom()).listFiles(); - List listOfFiles = new ArrayList<>(); - - if (filesList != null) { - for (File fileInDir : filesList) { - if (fileInDir.canRead()) { - listOfFiles.add(getFileListEntry(fileInDir)); - } - } - } - return listOfFiles; - } - - private static String getFileListEntry(File fileInDir) { - - StringBuilder sb = new StringBuilder(); - try { - PosixFileAttributes fa = Files.readAttributes(fileInDir.toPath(), PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); - sb.append(fa.isDirectory() ? "d" : fa.isSymbolicLink() ? "l" : fa.isRegularFile() ? "f" : "-"); - sb.append(fileInDir.canRead() ? "r" : "-"); - sb.append(fileInDir.canWrite() ? "w" : "-"); - sb.append(fileInDir.canExecute() ? "x" : "-"); - sb.append("\t"); - sb.append(fa.owner()); - sb.append(fa.owner().getName().length() < 4 ? "\t\t" : "\t"); - sb.append(fa.group()); - sb.append(fa.group().getName().length() < 4 ? "\t\t" : "\t"); - sb.append(fa.size()); - sb.append(String.valueOf(fa.size()).length() < 4 ? "\t\t" : "\t"); - sb.append(fa.lastModifiedTime().toString()); - sb.append("\t"); - sb.append(fa.isDirectory() ? fileInDir.getName() + "/" : fileInDir.getName()); - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to get file attributes", e); - } - logger.info(sb.toString()); - return sb.toString(); - } - } diff --git a/src/lia/util/net/copy/FDTSessionManager.java b/src/lia/util/net/copy/FDTSessionManager.java index 97ee1bf..8befdda 100644 --- a/src/lia/util/net/copy/FDTSessionManager.java +++ b/src/lia/util/net/copy/FDTSessionManager.java @@ -49,10 +49,6 @@ public class FDTSessionManager extends AbstractFDTCloseable implements ControlCh private volatile String lastDownMsg; private volatile Throwable lastDownCause; - public static FDTSessionManager getInstance() { - return _thisInstanceManager; - } - private FDTSessionManager() { lock = new ReentrantLock(); isSessionMapEmpty = lock.newCondition(); @@ -60,6 +56,10 @@ private FDTSessionManager() { inited = new AtomicBoolean(false); } + public static FDTSessionManager getInstance() { + return _thisInstanceManager; + } + public void addFDTClientSession(ControlChannel controlChannel) throws Exception { FDTSession fdtSession = null; diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 4f720ef..57cbe07 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -38,17 +38,12 @@ public class FDTWriterSession extends FDTSession implements FileBlockConsumer { private static final Config config = Config.getInstance(); private static final DiskWriterManager dwm = DiskWriterManager.getInstance(); - + private final AtomicBoolean finalCleaupExecuted = new AtomicBoolean(false); + private final AtomicBoolean finishNotifiedExecuted = new AtomicBoolean(false); private String destinationDir; - private String[] fileList; - private ProcessorInfo processorInfo; - private final AtomicBoolean finalCleaupExecuted = new AtomicBoolean(false); - - private final AtomicBoolean finishNotifiedExecuted = new AtomicBoolean(false); - public FDTWriterSession(int transferPort) throws Exception { super(FDTSession.CLIENT, transferPort); Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); diff --git a/src/lia/util/net/copy/FileBlock.java b/src/lia/util/net/copy/FileBlock.java index a75ae5d..3892d4a 100644 --- a/src/lia/util/net/copy/FileBlock.java +++ b/src/lia/util/net/copy/FileBlock.java @@ -8,46 +8,45 @@ /** * Wrapper class for a simple block ( can be an offset in whatever stream ... not only a file ) - * + * * @author ramiro - * */ public class FileBlock { - + //used for signaling between Producers/Consumers //public static final FileBlock EOF_FB = new FileBlock(UUID.randomUUID(), UUID.randomUUID(), -1, ByteBuffer.allocate(0)); - + public final UUID fdtSessionID; public final UUID fileSessionID; public final long fileOffset; public final ByteBuffer buff; private FileBlock(final UUID fdtSessionID, final UUID fileSessionID, final long fileOffset, final ByteBuffer buff) { - if(fdtSessionID == null) { + if (fdtSessionID == null) { throw new NullPointerException(" [ FDT Bug ? ] fdtSessionID cannot be null; fileSessionID: " + fileSessionID); } - - if(fileSessionID == null) { + + if (fileSessionID == null) { throw new NullPointerException(" [ FDT Bug ? ] fileSessionID cannot be null; fdtSessionID: " + fdtSessionID); } - - if(buff == null) { + + if (buff == null) { throw new NullPointerException(" [ FDT Bug ? ] buff cannot be null; fdtSessionID: " + fdtSessionID + " fileSessionID: " + fileSessionID); } - + this.fdtSessionID = fdtSessionID; this.fileSessionID = fileSessionID; this.fileOffset = fileOffset; this.buff = buff; } - + //TODO - Make a simple cache of FileBlock-s objects ... I don't think FDT will gain anything from this "feature" // so I will not implement it, yet public static FileBlock getInstance(UUID fdtSessionID, UUID fileSessionID, long fileOffset, ByteBuffer buff) { return new FileBlock(fdtSessionID, fileSessionID, fileOffset, buff); } - + public String toString() { - return "FileBlock for [ " + fileSessionID + " ] offset: " + fileOffset + " payload: " + buff; + return "FileBlock for [ " + fileSessionID + " ] offset: " + fileOffset + " payload: " + buff; } } diff --git a/src/lia/util/net/copy/FileBlockConsumer.java b/src/lia/util/net/copy/FileBlockConsumer.java index c2746ba..fceefe0 100644 --- a/src/lia/util/net/copy/FileBlockConsumer.java +++ b/src/lia/util/net/copy/FileBlockConsumer.java @@ -7,17 +7,16 @@ import java.util.concurrent.TimeUnit; /** - * * This interface, together with {@link FileBlockProducer} acts as a bridge * between the Readers and the Writers in the FDT App - * - * @see BlockingQueue + * * @author ramiro - * + * @see BlockingQueue */ public interface FileBlockConsumer { - + public boolean offer(final FileBlock fileBlock, long delay, TimeUnit unit) throws InterruptedException; + public void put(FileBlock fileBlock) throws InterruptedException; - + } diff --git a/src/lia/util/net/copy/FileBlockProducer.java b/src/lia/util/net/copy/FileBlockProducer.java index aee50e6..81c1bea 100644 --- a/src/lia/util/net/copy/FileBlockProducer.java +++ b/src/lia/util/net/copy/FileBlockProducer.java @@ -7,18 +7,20 @@ import java.util.concurrent.TimeUnit; /** - * * This interface, together with {@link FileBlockConsumer} acts as a bridge * between the Readers and the Writers in the FDT App - * - * @see BlockingQueue + * * @author ramiro + * @see BlockingQueue */ public interface FileBlockProducer { public FileBlock take() throws InterruptedException; + public FileBlock poll(); + public FileBlock poll(long delay, TimeUnit unit) throws InterruptedException; + public void transportWorkerDown() throws Exception; - + } diff --git a/src/lia/util/net/copy/FileReaderSession.java b/src/lia/util/net/copy/FileReaderSession.java index 2dee9ef..70b8e1a 100644 --- a/src/lia/util/net/copy/FileReaderSession.java +++ b/src/lia/util/net/copy/FileReaderSession.java @@ -3,12 +3,12 @@ */ package lia.util.net.copy; +import lia.util.net.common.FileChannelProvider; + import java.io.IOException; import java.nio.channels.FileChannel; import java.util.UUID; -import lia.util.net.common.FileChannelProvider; - /** * Wrapper class over a current file which is being read * @@ -16,19 +16,13 @@ */ public class FileReaderSession extends FileSession { - @Override - public String toString() { - return "FileReaderSession [file=" + file + ", partitionID=" + partitionID + ", sessionID=" + sessionID - + ", sessionSize=" + sessionSize + "]"; - } - public FileReaderSession(String fileName, FDTSession fdtSession, boolean isLoop, - FileChannelProvider fileChannelProvider) throws IOException { + FileChannelProvider fileChannelProvider) throws IOException { this(UUID.randomUUID(), fdtSession, fileName, isLoop, fileChannelProvider); } public FileReaderSession(UUID uid, FDTSession fdtSession, String fileName, boolean isLoop, - FileChannelProvider fileChannelProvider) throws IOException { + FileChannelProvider fileChannelProvider) throws IOException { super(uid, fdtSession, fileName, isLoop, fileChannelProvider); this.fileName = file.getAbsolutePath(); @@ -58,6 +52,12 @@ public FileReaderSession(UUID uid, FDTSession fdtSession, String fileName, boole this.partitionID = this.fileChannelProvider.getPartitionID(this.file); } + @Override + public String toString() { + return "FileReaderSession [file=" + file + ", partitionID=" + partitionID + ", sessionID=" + sessionID + + ", sessionSize=" + sessionSize + "]"; + } + @Override public FileChannel getChannel() throws Exception { diff --git a/src/lia/util/net/copy/FileSession.java b/src/lia/util/net/copy/FileSession.java index 79df556..52306a3 100644 --- a/src/lia/util/net/copy/FileSession.java +++ b/src/lia/util/net/copy/FileSession.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy; +import lia.util.net.common.FileChannelProvider; + import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; @@ -11,41 +13,30 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.FileChannelProvider; - /** - * * This class is the FDT wrapper over the FileChannel which performs the I/O operations * * @author ramiro */ public abstract class FileSession extends IOSession { - private static final Logger logger = Logger.getLogger(FileSession.class.getName()); - public static final String DEV_NULL_FILENAME = "/dev/null"; public static final String DEV_ZERO_FILENAME = "/dev/zero"; - + private static final Logger logger = Logger.getLogger(FileSession.class.getName()); + public final AtomicLong cProcessedBytes = new AtomicLong(0); protected final boolean isLoop; - + protected final FDTSession fdtSession; + protected final boolean isNull; + protected final boolean isZero; + protected final FileChannelProvider fileChannelProvider; protected volatile String fileName; - protected volatile FileChannel fileChannel; - protected volatile File file; - protected final FDTSession fdtSession; - public final AtomicLong cProcessedBytes = new AtomicLong(0); protected volatile int partitionID; - protected volatile long lastModified; - protected final boolean isNull; - protected final boolean isZero; - - protected final FileChannelProvider fileChannelProvider; - public FileSession(UUID uid, FDTSession fdtSession, String fileName, boolean isLoop, - FileChannelProvider fileChannelProvider) { + FileChannelProvider fileChannelProvider) { super(uid, -1); this.fdtSession = fdtSession; @@ -135,7 +126,6 @@ public String fileName() { } /** - * * @param fileName * @throws IOException */ diff --git a/src/lia/util/net/copy/FileWriterSession.java b/src/lia/util/net/copy/FileWriterSession.java index 179188d..455f65a 100644 --- a/src/lia/util/net/copy/FileWriterSession.java +++ b/src/lia/util/net/copy/FileWriterSession.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy; +import lia.util.net.common.FileChannelProvider; + import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; @@ -11,46 +13,32 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.FileChannelProvider; - /** * Wrapper class over a current file which is being written - * + * * @author ramiro */ public class FileWriterSession extends FileSession { - @Override - public String toString() { - return "FileWriterSession [tmpCopyFile=" + tmpCopyFile + ", file=" + file + ", partitionID=" + partitionID + ", sessionID=" + sessionID - + ", sessionSize=" + sessionSize + "]"; - } - private static final Logger logger = Logger.getLogger(FileSession.class.getName()); - + protected final boolean noLock; + protected final boolean noTmp; + protected volatile FileLock fLock = null; private volatile boolean channelInitialized; - private volatile File tmpCopyFile; - private String openMode = "rw"; - protected volatile FileLock fLock = null; - - protected final boolean noLock; - - protected final boolean noTmp; - public FileWriterSession(UUID uid, - FDTSession fdtSession, - String fileName, - long size, - long lastModified, - boolean isLoop, - String writeMode, - boolean noTmp, - boolean noLock, - FileChannelProvider fcp) throws IOException { - + FDTSession fdtSession, + String fileName, + long size, + long lastModified, + boolean isLoop, + String writeMode, + boolean noTmp, + boolean noLock, + FileChannelProvider fcp) throws IOException { + super(uid, fdtSession, fileName, isLoop, fcp); this.noTmp = noTmp; @@ -99,15 +87,21 @@ public FileWriterSession(UUID uid, public static FileWriterSession fromFileWriterSession(FileWriterSession other) throws IOException { return new FileWriterSession(other.sessionID, - other.fdtSession, - other.fileName, - other.sessionSize(), - other.lastModified, - other.isLoop, - other.openMode, - other.noTmp, - other.noLock, - other.fileChannelProvider); + other.fdtSession, + other.fileName, + other.sessionSize(), + other.lastModified, + other.isLoop, + other.openMode, + other.noTmp, + other.noLock, + other.fileChannelProvider); + } + + @Override + public String toString() { + return "FileWriterSession [tmpCopyFile=" + tmpCopyFile + ", file=" + file + ", partitionID=" + partitionID + ", sessionID=" + sessionID + + ", sessionSize=" + sessionSize + "]"; } public FileChannel getChannel() throws Exception { @@ -147,14 +141,14 @@ public FileChannel getChannel() throws Exception { } } - + try { partitionID = this.fileChannelProvider.getPartitionID(this.tmpCopyFile); } catch (Throwable t) { logger.log(Level.WARNING, " [ FileWriterSession ] cannot determine partition id for: " + this.tmpCopyFile, t); } - - final FileChannel lfc = this.fileChannelProvider.getFileChannel(tmpCopyFile, openMode); + + final FileChannel lfc = this.fileChannelProvider.getFileChannel(tmpCopyFile, openMode); if (!noLock && !isNull) { try { @@ -177,8 +171,8 @@ public FileChannel getChannel() throws Exception { logger.log(Level.FINE, "[ FileWriterSession ] Not using file lock for: " + tmpCopyFile); } } - - fileChannel = lfc; + + fileChannel = lfc; channelInitialized = true; } catch (Exception ex) { close(null, ex); diff --git a/src/lia/util/net/copy/IOSession.java b/src/lia/util/net/copy/IOSession.java index ae42f0a..c70b9e2 100644 --- a/src/lia/util/net/copy/IOSession.java +++ b/src/lia/util/net/copy/IOSession.java @@ -3,31 +3,29 @@ */ package lia.util.net.copy; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; - import lia.util.net.common.AbstractFDTCloseable; +import java.util.UUID; + /** * Base class for all sessions inside FDT which are performing I/O. - * + * * @author ramiro */ public abstract class IOSession extends AbstractFDTCloseable { - /** - * the one and only session identifier - */ - protected final UUID sessionID; /** * starting time in millis since Epoch */ public final long startTimeMillis; /** - * start time in as returned by {@link System#nanoTime()} which usually represents the JVM (or OS) uptime in nanoseconds + * start time in as returned by {@link System#nanoTime()} which usually represents the JVM (or OS) uptime in nanoseconds */ public final long startTimeNanos; - + /** + * the one and only session identifier + */ + protected final UUID sessionID; /** * how many bytes should be transferred * As per JLS -- reads and writes will be atomic; as we offer only get()/set() everything should be atomic @@ -39,10 +37,8 @@ public IOSession() { } /** - * @param sessionID - * UUID representing this session's identifier - * @throws NullPointerException - * if sessionID is null + * @param sessionID UUID representing this session's identifier + * @throws NullPointerException if sessionID is null */ public IOSession(UUID sessionID) { if (sessionID == null) { diff --git a/src/lia/util/net/copy/PartitionMap.java b/src/lia/util/net/copy/PartitionMap.java index c1c9d1d..1cb610e 100644 --- a/src/lia/util/net/copy/PartitionMap.java +++ b/src/lia/util/net/copy/PartitionMap.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy; +import lia.util.net.common.Utils; + import java.io.BufferedReader; import java.io.File; import java.io.InputStream; @@ -12,20 +14,20 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Utils; - /** * Helper class which determines the real partition ID for a file - * + * * @author Lucian Musat * @author ramiro */ public class PartitionMap { - /** Logger used by this class */ - private static final Logger logger = Logger.getLogger(PartitionMap.class.getName()); - public static final String osname = System.getProperty("os.name"); + /** + * Logger used by this class + */ + private static final Logger logger = Logger.getLogger(PartitionMap.class.getName()); + private static ConcurrentHashMap hLocations = new ConcurrentHashMap(); public static final int getPartition(String fileName) { @@ -37,7 +39,7 @@ public static final int getPartition(String fileName) { if (osname.indexOf("Linux") != -1 || osname.indexOf("SunOS") != -1) { // identifies the partition the real file denoted by this path resides on // command="/usr/bin/stat -L -c \"%d\" \""+fileName+"\""; - command = new String[] { + command = new String[]{ "stat", "-L", "-c", "%d", fileName }; } else if (osname.indexOf("Win") != -1) { @@ -46,7 +48,7 @@ public static final int getPartition(String fileName) { // identifies the major number for the drive that has the file // that means, it identifies the disk, not only the partition // command="/usr/bin/stat -L -f \"%Hd\" \""+fileName+"\""; - command = new String[] { + command = new String[]{ "stat", "-L", "-f", "%Hd", fileName }; } @@ -68,11 +70,11 @@ public static final int getPartition(String fileName) { /** * runs a shell command and returns first line if nothing on stderr
    * TODO: replace with cmdExec.java - * - * @author mluc - * @since Aug 31, 2006 + * * @param cmd * @return + * @author mluc + * @since Aug 31, 2006 */ private static String runICommand(final String[] cmd) { BufferedReader br = null; @@ -88,8 +90,8 @@ private static String runICommand(final String[] cmd) { Process pro = null; if (osname.startsWith("Linux") || osname.startsWith("Mac") || osname.startsWith("SunOS")) { - pro = Runtime.getRuntime().exec(cmd, new String[] { - "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin" + pro = Runtime.getRuntime().exec(cmd, new String[]{ + "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin" }); } else if (osname.startsWith("Windows")) { String exehome = System.getProperty("user.home"); @@ -145,24 +147,22 @@ private static String runICommand(final String[] cmd) { return null; } - private static ConcurrentHashMap hLocations = new ConcurrentHashMap(); - /** * stores the directory path to a file and for a second file with the same * directory path will return the last value and will not search for a new one
    * ATTENTION: if one of two files is a link, then the result returned will probably * not be valid! - * - * @author mluc - * @since Sep 18, 2006 + * * @param fileName * @return tag of partition taken from cache if available + * @author mluc + * @since Sep 18, 2006 */ public static int getPartitionFromCache(File file) { - String dirPath=""; + String dirPath = ""; final String fileName = file.getAbsolutePath(); boolean isDir = false; - if(!file.isDirectory()) { + if (!file.isDirectory()) { int lastIndex = fileName.lastIndexOf(File.separatorChar); if (lastIndex != -1) dirPath = fileName.substring(0, lastIndex); @@ -172,8 +172,8 @@ public static int getPartitionFromCache(File file) { isDir = true; dirPath = file.getAbsolutePath(); } - - final Integer value = hLocations.get((isDir)?dirPath:fileName); + + final Integer value = hLocations.get((isDir) ? dirPath : fileName); if (value != null) return value.intValue(); int val = getPartition(fileName); diff --git a/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java b/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java index b6330fa..d70d72a 100644 --- a/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java +++ b/src/lia/util/net/copy/PosixFSFileChannelProviderFactory.java @@ -4,14 +4,13 @@ */ package lia.util.net.copy; -import java.io.*; -import java.nio.channels.FileChannel; - import lia.util.net.common.FileChannelProvider; import lia.util.net.common.FileChannelProviderFactory; +import java.io.*; +import java.nio.channels.FileChannel; + /** - * * @author ramiro */ public class PosixFSFileChannelProviderFactory implements FileChannelProviderFactory { @@ -19,23 +18,23 @@ public class PosixFSFileChannelProviderFactory implements FileChannelProviderFac private final FileChannelProvider readerFileChannelProvider; private final FileChannelProvider writerFileChannelProvider; private final FileChannelProvider coordinatorChannelProvider; - - + + public PosixFSFileChannelProviderFactory() { this.readerFileChannelProvider = new PosixFSReaderFileChannelProvider(); this.writerFileChannelProvider = new PosixFSWriterFileChannelProvider(); this.coordinatorChannelProvider = new PosixFSCoordinatorChannelProvider(); } - + /** - * @param readerSession + * @param readerSession */ public FileChannelProvider newReaderFileChannelProvider(FDTReaderSession readerSession) { return readerFileChannelProvider; } /** - * @param writerSession + * @param writerSession */ public FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerSession) { return writerFileChannelProvider; @@ -51,22 +50,21 @@ public FileChannelProvider newWriterFileChannelProvider(FDTWriterSession writerS private static final class PosixFSReaderFileChannelProvider implements FileChannelProvider, Serializable { /** - * @throws IOException + * @throws IOException */ public File getFile(String fileName) throws IOException { return new File(fileName); } /** - * @throws IOException - * + * @throws IOException */ public int getPartitionID(File file) throws IOException { return PartitionMap.getPartitionFromCache(file); } /** - * @param openMode + * @param openMode */ @SuppressWarnings("resource") public FileChannel getFileChannel(File file, String openMode) throws IOException { @@ -74,21 +72,21 @@ public FileChannel getFileChannel(File file, String openMode) throws IOException } } - + private static final class PosixFSWriterFileChannelProvider implements FileChannelProvider { /** - * @throws IOException + * @throws IOException */ public File getFile(String fileName) throws IOException { return new File(fileName); } /** - * @throws IOException + * @throws IOException */ public int getPartitionID(File file) throws IOException { - if(file.exists()) { + if (file.exists()) { return PartitionMap.getPartitionFromCache(file); } @@ -97,10 +95,10 @@ public int getPartitionID(File file) throws IOException { @SuppressWarnings("resource") public FileChannel getFileChannel(File file, final String openMode) throws IOException { - if(openMode != null) { + if (openMode != null) { return new RandomAccessFile(file, openMode).getChannel(); } - + return new FileOutputStream(file).getChannel(); } @@ -119,7 +117,7 @@ public File getFile(String fileName) throws IOException { * @throws IOException */ public int getPartitionID(File file) throws IOException { - if(file.exists()) { + if (file.exists()) { return PartitionMap.getPartitionFromCache(file); } @@ -128,7 +126,7 @@ public int getPartitionID(File file) throws IOException { @SuppressWarnings("resource") public FileChannel getFileChannel(File file, final String openMode) throws IOException { - if(openMode != null) { + if (openMode != null) { return new RandomAccessFile(file, openMode).getChannel(); } diff --git a/src/lia/util/net/copy/disk/DiskReaderManager.java b/src/lia/util/net/copy/disk/DiskReaderManager.java index 320c11a..99fb92a 100644 --- a/src/lia/util/net/copy/disk/DiskReaderManager.java +++ b/src/lia/util/net/copy/disk/DiskReaderManager.java @@ -3,36 +3,34 @@ */ package lia.util.net.copy.disk; -import java.util.concurrent.TimeUnit; - import lia.util.net.common.Utils; import lia.util.net.copy.monitoring.DiskReaderManagerMonitoringTask; +import java.util.concurrent.TimeUnit; + /** - * * Disk reader Yoda :) - * + * * @author ramiro - * */ public class DiskReaderManager extends GenericDiskManager { private static final DiskReaderManager _theInstance = new DiskReaderManager(); - private DiskReaderManagerMonitoringTask monTask; + private DiskReaderManagerMonitoringTask monTask; private DiskReaderManager() { - monTask = new DiskReaderManagerMonitoringTask(this); + monTask = new DiskReaderManagerMonitoringTask(this); Utils.getMonitoringExecService().scheduleWithFixedDelay(monTask, 5, 5, TimeUnit.SECONDS); } - + public static final DiskReaderManager getInstance() { return _theInstance; } - + protected void internalClose() { - + } - + public long getSize() { return -1; } diff --git a/src/lia/util/net/copy/disk/DiskReaderTask.java b/src/lia/util/net/copy/disk/DiskReaderTask.java index 325678e..f8f02b8 100644 --- a/src/lia/util/net/copy/disk/DiskReaderTask.java +++ b/src/lia/util/net/copy/disk/DiskReaderTask.java @@ -3,6 +3,13 @@ */ package lia.util.net.copy.disk; +import lia.util.net.common.Config; +import lia.util.net.common.DirectByteBufferPool; +import lia.util.net.common.Utils; +import lia.util.net.copy.FDTReaderSession; +import lia.util.net.copy.FileBlock; +import lia.util.net.copy.FileSession; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -13,162 +20,148 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.Utils; -import lia.util.net.copy.FDTReaderSession; -import lia.util.net.copy.FileBlock; -import lia.util.net.copy.FileSession; - /** - * * This class will read the files for a specific partitionID * and FDTReaderSession. The tuple ( partitionID, FDTReaderSession ) identifies * this task. - * */ public class DiskReaderTask extends GenericDiskTask { - + private static final Logger logger = Logger.getLogger(DiskReaderTask.class.getName()); - + private static final DiskReaderManager diskReaderManager = DiskReaderManager.getInstance(); - - List fileSessions; - private final MessageDigest md5Sum; private final boolean computeMD5; - + private final FDTReaderSession fdtSession; + List fileSessions; private AtomicBoolean isFinished = new AtomicBoolean(false); private int addedFBS = 0; - - private final FDTReaderSession fdtSession; - + /** - * * @throws NullPointerException if fdtSession is null or fileSessions list is null **/ public DiskReaderTask(final int partitionID, final int taskIndex, final List fileSessions, final FDTReaderSession fdtSession) { super(partitionID, taskIndex); - boolean bComputeMD5 = Config.getInstance().computeMD5(); + boolean bComputeMD5 = Config.getInstance().computeMD5(); MessageDigest md5SumTMP = null; - - if(fdtSession == null) throw new NullPointerException("FDTSession cannot be null"); - if(fileSessions == null) throw new NullPointerException("FileSessions cannot be null"); - + + if (fdtSession == null) throw new NullPointerException("FDTSession cannot be null"); + if (fileSessions == null) throw new NullPointerException("FileSessions cannot be null"); + this.fileSessions = fileSessions; this.fdtSession = fdtSession; this.myName = new StringBuilder("DiskReaderTask - partitionID: ").append(partitionID).append(" taskID: ").append(taskIndex).append(" - [ ").append(fdtSession.toString()).append(" ]").toString(); - - if(bComputeMD5) { + + if (bComputeMD5) { try { md5SumTMP = MessageDigest.getInstance("MD5"); - } catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, " \n\n\n Cannot compute MD5. Unable to initiate the MessageDigest engine. Cause: ", t); md5SumTMP = null; } } - - if(md5SumTMP != null) { + + if (md5SumTMP != null) { bComputeMD5 = true; } else { bComputeMD5 = false; } - + md5Sum = md5SumTMP; computeMD5 = bComputeMD5; } public void stopIt() { - if(isFinished.compareAndSet(false, true)) { + if (isFinished.compareAndSet(false, true)) { //interrupt it if it's blocked in waiting fdtSession.finishReader(partitionID, this); } } - + public void run() { - - + + String cName = Thread.currentThread().getName(); - + Thread.currentThread().setName(myName); - if(logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, myName + " started" ); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, myName + " started"); } - + Throwable downCause = null; ByteBuffer buff = null; FileBlock fileBlock = null; long cPosition, readBytes; - + FileSession currentFileSession = null; FileChannel cuurentFileChannel = null; final DirectByteBufferPool bufferPool = DiskReaderTask.bufferPool; final boolean computeMD5 = this.computeMD5; final FDTReaderSession fdtSession = this.fdtSession; - if(fdtSession == null) { + if (fdtSession == null) { logger.log(Level.WARNING, "\n\n FDT Session is null in DiskReaderTask !! Will stop reader task\n\n"); } - + try { - while(!fdtSession.isClosed()) { - for(final FileSession fileSession: fileSessions) { + while (!fdtSession.isClosed()) { + for (final FileSession fileSession : fileSessions) { currentFileSession = fileSession; - - if(fileSession.isClosed()) { + + if (fileSession.isClosed()) { fdtSession.finishFileSession(fileSession.sessionID(), null); continue; } - - if(logger.isLoggable(Level.FINE)) { + + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " [ FileReaderTask ] for FileSession (" + fileSession.sessionID() + ") " + fileSession.fileName() + " started"); } - + downCause = null; - - final FileChannel fileChannel = (fileSession.isZero())?null:fileSession.getChannel(); + + final FileChannel fileChannel = (fileSession.isZero()) ? null : fileSession.getChannel(); cuurentFileChannel = fileChannel; - - if(fileChannel != null || fileSession.isZero()) { - if(computeMD5) { + + if (fileChannel != null || fileSession.isZero()) { + if (computeMD5) { md5Sum.reset(); } - cPosition = (fileSession.isZero())?0:fileChannel.position(); + cPosition = (fileSession.isZero()) ? 0 : fileChannel.position(); + + for (; ; ) { + - for(;;) { - - //try to get a new buffer from the pool buff = null; fileBlock = null; - while(buff == null) { - if(fdtSession.isClosed()) { + while (buff == null) { + if (fdtSession.isClosed()) { return; } buff = bufferPool.poll(2, TimeUnit.SECONDS); } - - if(fileSession.isZero()) { + + if (fileSession.isZero()) { //Just play with the buffer markers ... do not even touch /dev/zero readBytes = buff.capacity(); buff.position(0); buff.limit(buff.capacity()); } else { readBytes = fileChannel.read(buff); - if(logger.isLoggable(Level.FINEST)) { + if (logger.isLoggable(Level.FINEST)) { StringBuilder sb = new StringBuilder(1024); sb.append(" [ DiskReaderTask ] FileReaderSession ").append(fileSession.sessionID()).append(": ").append(fileSession.fileName()); sb.append(" fdtSession: ").append(fdtSession.sessionID()).append(" read: ").append(readBytes); logger.log(Level.FINEST, sb.toString()); } } - - if(readBytes == -1) {//EOF - if(fileSession.cProcessedBytes.get() == fileSession.sessionSize()) { + + if (readBytes == -1) {//EOF + if (fileSession.cProcessedBytes.get() == fileSession.sessionSize()) { fdtSession.finishFileSession(fileSession.sessionID(), null); } else { - if(!fdtSession.loop()) { + if (!fdtSession.loop()) { StringBuilder sbEx = new StringBuilder(); sbEx.append("FileSession: ( ").append(fileSession.sessionID()).append(" ): ").append(fileSession.fileName()); sbEx.append(" total length: ").append(fileSession.sessionSize()).append(" != total read until EOF: ").append(fileSession.cProcessedBytes.get()); @@ -180,9 +173,9 @@ public void run() { bufferPool.put(buff); buff = null; fileBlock = null; - if(computeMD5) { + if (computeMD5) { byte[] md5ByteArray = md5Sum.digest(); - if(logger.isLoggable(Level.FINEST)) { + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, Utils.md5ToString(md5ByteArray) + " ---> " + fileSession.fileName() ); } @@ -190,8 +183,8 @@ public void run() { } break; } - - if(computeMD5) { + + if (computeMD5) { buff.flip(); md5Sum.update(buff); } @@ -201,20 +194,20 @@ public void run() { diskReaderManager.addAndGetUtilBytes(readBytes); addAndGetTotalBytes(readBytes); addAndGetUtilBytes(readBytes); - + fdtSession.addAndGetTotalBytes(readBytes); fdtSession.addAndGetUtilBytes(readBytes); - - if(!fileSession.isZero()) { + + if (!fileSession.isZero()) { buff.flip(); } - + fileBlock = FileBlock.getInstance(fdtSession.sessionID(), fileSession.sessionID(), cPosition, buff); cPosition += readBytes; - - if(!fdtSession.isClosed()) { - while(!fdtSession.fileBlockQueue.offer(fileBlock, 2, TimeUnit.SECONDS)) { - if(fdtSession.isClosed()) { + + if (!fdtSession.isClosed()) { + while (!fdtSession.fileBlockQueue.offer(fileBlock, 2, TimeUnit.SECONDS)) { + if (fdtSession.isClosed()) { return; } } @@ -223,113 +216,114 @@ public void run() { addedFBS++; } else { try { - if(fileBlock != null && fileBlock.buff != null) { + if (fileBlock != null && fileBlock.buff != null) { bufferPool.put(fileBlock.buff); buff = null; fileBlock = null; } return; - }catch(Throwable t1) { - if(logger.isLoggable(Level.FINER)) { + } catch (Throwable t1) { + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, " Got exception returning bufer to the buffer pool", t1); } } } - + fileBlock = null; }//while() - + } else { - if(logger.isLoggable(Level.FINER)) { + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, " Null file channel for fileSession" + fileSession); } downCause = new NullPointerException("Null File Channel inside reader worker"); downCause.fillInStackTrace(); fdtSession.finishFileSession(fileSession.sessionID(), downCause); } - + }//for - fileSession - - if(!fdtSession.loop()) { + + if (!fdtSession.loop()) { break; } }//for - - } catch(IOException ioe) { + + } catch (IOException ioe) { //check for down - if(isFinished.get() || fdtSession.isClosed()) {//most likely a normal error - logger.log(Level.FINEST, " [ HANDLED ] Got I/O Exception reading FileSession (" + currentFileSession.sessionID() + ") " + currentFileSession.fileName(), ioe); + if (isFinished.get() || fdtSession.isClosed()) {//most likely a normal error + logger.log(Level.FINEST, " [ HANDLED ] Got I/O Exception reading FileSession (" + currentFileSession.sessionID() + ") " + currentFileSession.fileName(), ioe); return; } - - if(!isFinished.getAndSet(true) && !fdtSession.isClosed()) {//most likely a normal error - logger.log(Level.INFO, " [ HANDLED ] Got I/O Exception reading FileSession (" + currentFileSession.sessionID() + ") / " + currentFileSession.fileName(), ioe); + + if (!isFinished.getAndSet(true) && !fdtSession.isClosed()) {//most likely a normal error + logger.log(Level.INFO, " [ HANDLED ] Got I/O Exception reading FileSession (" + currentFileSession.sessionID() + ") / " + currentFileSession.fileName(), ioe); downCause = ioe; fdtSession.finishFileSession(currentFileSession.sessionID(), downCause); return; } - + downCause = ioe; fdtSession.finishFileSession(currentFileSession.sessionID(), downCause); - + } catch (Throwable t) { - if(isFinished.get() || fdtSession.isClosed()) {//most likely a normal error + if (isFinished.get() || fdtSession.isClosed()) {//most likely a normal error logger.log(Level.FINEST, "Got General Exception reading FileSession (" + currentFileSession.sessionID() + ") " + currentFileSession.fileName(), t); return; } - + downCause = t; fdtSession.finishFileSession(currentFileSession.sessionID(), downCause); } finally { - if(logger.isLoggable(Level.FINE)) { + if (logger.isLoggable(Level.FINE)) { final StringBuilder logMsg = new StringBuilder("DiskReaderTask - partitionID: ").append(partitionID).append(" taskID: ").append(this.taskID); - if(downCause == null) { + if (downCause == null) { logMsg.append(" Normal exit fdtSession.isClosed() = ").append(fdtSession.isClosed()); } else { logMsg.append(" Exit with error: ").append(Utils.getStackTrace(downCause)).append("fdtSession.isClosed() = ").append(fdtSession.isClosed()); } logger.log(Level.FINE, logMsg.toString()); } - - if(cuurentFileChannel != null) { + + if (cuurentFileChannel != null) { try { cuurentFileChannel.close(); - }catch(Throwable ignore){} + } catch (Throwable ignore) { + } } - + try { - if(buff != null) { + if (buff != null) { bufferPool.put(buff); try { - if(fileBlock != null && fileBlock.buff != null && fileBlock.buff != buff) { + if (fileBlock != null && fileBlock.buff != null && fileBlock.buff != buff) { boolean bPut = bufferPool.put(fileBlock.buff); - if(logger.isLoggable(Level.FINEST)) { - logger.log(Level.FINEST, " [ FINALLY ] DiskReaderTask RETURNING FB buff: " + fileBlock.buff + " to pool [ " + bPut + " ]" ); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, " [ FINALLY ] DiskReaderTask RETURNING FB buff: " + fileBlock.buff + " to pool [ " + bPut + " ]"); } } - } catch(Throwable t1) { - if(logger.isLoggable(Level.FINER)) { + } catch (Throwable t1) { + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, " Got exception returning bufer to the buffer pool", t1); } } buff = null; fileBlock = null; } - } catch(Throwable t1) { - if(logger.isLoggable(Level.FINER)) { + } catch (Throwable t1) { + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, " Got exception returning bufer to the buffer pool", t1); } } - - if(logger.isLoggable(Level.FINE)) { + + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "\n\n " + myName + " finishes. " + - "\n fdtSession is " + ((fdtSession.isClosed())?"closed":"open") + "" + - " Processed FBS = " + addedFBS + " \n\n"); + "\n fdtSession is " + ((fdtSession.isClosed()) ? "closed" : "open") + "" + + " Processed FBS = " + addedFBS + " \n\n"); } - + Thread.currentThread().setName(cName); } - + }//run() } diff --git a/src/lia/util/net/copy/disk/DiskWriterManager.java b/src/lia/util/net/copy/disk/DiskWriterManager.java index a409e80..7d87973 100644 --- a/src/lia/util/net/copy/disk/DiskWriterManager.java +++ b/src/lia/util/net/copy/disk/DiskWriterManager.java @@ -3,71 +3,48 @@ */ package lia.util.net.copy.disk; +import lia.util.net.common.Config; +import lia.util.net.common.Utils; +import lia.util.net.copy.FileBlock; +import lia.util.net.copy.monitoring.DiskWriterManagerMonitoringTask; + import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.Utils; -import lia.util.net.copy.FileBlock; -import lia.util.net.copy.monitoring.DiskWriterManagerMonitoringTask; - /** - * * The master of all the disk writer threads - * + * * @author ramiro - * */ public class DiskWriterManager extends GenericDiskManager { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final transient Logger logger = Logger.getLogger(DiskWriterManager.class.getName()); private static final Config config = Config.getInstance(); private static int MAX_PARTITION_COUNT = Integer.getInteger("fdt.MAX_PARTITION_COUNT", 1000).intValue(); private static int WRITER_QUEUE_MULTIPLY_FACTOR = Integer.getInteger("fdt.wQueueM", 20).intValue(); + private static DiskWriterManager _thisInstance; + private static volatile boolean initialized = false; private final ExecutorService execService; - + protected Exception finishException = null; /** * The map of DiskWriterTask-s per partition. The key is the partitionID. */ ConcurrentHashMap> diskWritersMap = new ConcurrentHashMap>(); - /** * The map of the Queues for every partitionID */ ConcurrentHashMap> diskQueuesMap = new ConcurrentHashMap>(); - - protected Exception finishException = null; - private int writersPerPartionCount = 1; - private static DiskWriterManager _thisInstance; - private static volatile boolean initialized = false; - - public static final DiskWriterManager getInstance() { - if (!initialized) { - synchronized (DiskWriterManager.class) { - if (!initialized) { - _thisInstance = new DiskWriterManager(); - initialized = true; - } - } - } - - return _thisInstance; - } - private DiskWriterManager() { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " \n\n --------> DiskWriterManager is instantiating <--------------- \n\n"); @@ -100,6 +77,19 @@ private DiskWriterManager() { } } + public static final DiskWriterManager getInstance() { + if (!initialized) { + synchronized (DiskWriterManager.class) { + if (!initialized) { + _thisInstance = new DiskWriterManager(); + initialized = true; + } + } + } + + return _thisInstance; + } + @Override protected void internalClose() { for (Integer parititonID : diskWritersMap.keySet()) { diff --git a/src/lia/util/net/copy/disk/DiskWriterTask.java b/src/lia/util/net/copy/disk/DiskWriterTask.java index 6fbddf0..eec8407 100644 --- a/src/lia/util/net/copy/disk/DiskWriterTask.java +++ b/src/lia/util/net/copy/disk/DiskWriterTask.java @@ -3,6 +3,12 @@ */ package lia.util.net.copy.disk; +import lia.util.net.common.Utils; +import lia.util.net.copy.FDTSession; +import lia.util.net.copy.FDTSessionManager; +import lia.util.net.copy.FileBlock; +import lia.util.net.copy.FileSession; + import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; @@ -15,12 +21,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Utils; -import lia.util.net.copy.FDTSession; -import lia.util.net.copy.FDTSessionManager; -import lia.util.net.copy.FileBlock; -import lia.util.net.copy.FileSession; - /** * per partition DiskWriterTask .... ( there may be more than one writer per * partition ) @@ -34,32 +34,19 @@ public class DiskWriterTask extends GenericDiskTask { private static final DiskWriterManager dwm = DiskWriterManager.getInstance(); private static final FDTSessionManager fsm = FDTSessionManager.getInstance(); - + final BlockingQueue queue; private final Lock countersRLock; - private final Lock countersWLock; - - long sTime; - - long sTimeWrite; - - long sTimeFinish; - - long finishTime; - + private final AtomicBoolean hasToRun; + private final boolean doNotForceOnClose; public long dtTake; - public long dtWrite; - public long dtFinishSession; - public long dtTotal; - - final BlockingQueue queue; - - private final AtomicBoolean hasToRun; - - private final boolean doNotForceOnClose; + long sTime; + long sTimeWrite; + long sTimeFinish; + long finishTime; DiskWriterTask(int partitionID, int writerID, BlockingQueue queue) { super(partitionID, writerID); @@ -288,7 +275,7 @@ public void run() { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "CLOSE - Not enforcing flush - " + fileSession.getFile() - + " closing without forcing the channel"); + + " closing without forcing the channel"); } } else { fileSession.getChannel().force(true); @@ -358,8 +345,8 @@ public void run() { logger.log( Level.SEVERE, myName - + " ... Got InterruptedException Exception writing to file [ ( fileSession is null ) ] offset: " - + ((fileBlock == null) ? " fileBlock is null" : "" + fileBlock.fileOffset), ie); + + " ... Got InterruptedException Exception writing to file [ ( fileSession is null ) ] offset: " + + ((fileBlock == null) ? " fileBlock is null" : "" + fileBlock.fileOffset), ie); } else { logger.log(Level.SEVERE, myName + " ... Got InterruptedException Exception writing to file [ ( " + fileSession.sessionID() + " ): " + fileSession.fileName() + " ] offset: " @@ -371,8 +358,8 @@ public void run() { logger.log( Level.SEVERE, myName - + " ... Got GeneralException Exception writing to file [ ( fileSession is null ) ] offset: " - + ((fileBlock == null) ? " fileBlock is null" : "" + fileBlock.fileOffset), t); + + " ... Got GeneralException Exception writing to file [ ( fileSession is null ) ] offset: " + + ((fileBlock == null) ? " fileBlock is null" : "" + fileBlock.fileOffset), t); } else { logger.log(Level.SEVERE, myName + " ... Got GeneralException Exception writing to file [ ( " + fileSession.sessionID() + " ): " + fileSession.fileName() + " ] offset: " diff --git a/src/lia/util/net/copy/disk/GenericDiskManager.java b/src/lia/util/net/copy/disk/GenericDiskManager.java index 7975635..1510524 100644 --- a/src/lia/util/net/copy/disk/GenericDiskManager.java +++ b/src/lia/util/net/copy/disk/GenericDiskManager.java @@ -3,46 +3,44 @@ */ package lia.util.net.copy.disk; +import lia.util.net.common.AbstractFDTIOEntity; +import lia.util.net.copy.FDTSession; + import java.util.Collections; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import lia.util.net.common.AbstractFDTIOEntity; -import lia.util.net.copy.FDTSession; - /** - * * Master class for both Read/Write managers - * + * * @author ramiro - * */ abstract class GenericDiskManager extends AbstractFDTIOEntity { protected final SortedSet sessions = Collections.synchronizedSortedSet(new TreeSet()); public boolean removeSession(FDTSession fdtSession, String downMessage, Throwable downCause) { - if(sessions.remove(fdtSession)) { + if (sessions.remove(fdtSession)) { fdtSession.close(downMessage, downCause); return true; } - + return false; } - + public boolean addSession(FDTSession fdtSession) { return sessions.add(fdtSession); } - + public final int sessionsSize() { - return sessions.size(); + return sessions.size(); } - + public Set getSessions() { return sessions; } - + public long getSize() { return -1; } diff --git a/src/lia/util/net/copy/disk/GenericDiskTask.java b/src/lia/util/net/copy/disk/GenericDiskTask.java index a2ad5f7..e59462e 100644 --- a/src/lia/util/net/copy/disk/GenericDiskTask.java +++ b/src/lia/util/net/copy/disk/GenericDiskTask.java @@ -7,24 +7,22 @@ import lia.util.net.copy.AccountableEntity; /** - * * Base class for both Read/Write disk threads - * + * * @author ramiro - * */ public abstract class GenericDiskTask extends AccountableEntity implements Runnable { protected static final DirectByteBufferPool bufferPool = DirectByteBufferPool.getInstance(); - protected String myName; protected final int partitionID; protected final int taskID; + protected String myName; public GenericDiskTask(final int partitionID, final int taskID) { this.partitionID = partitionID; this.taskID = taskID; } - + public long getSize() { return -1; } diff --git a/src/lia/util/net/copy/disk/ResumeManager.java b/src/lia/util/net/copy/disk/ResumeManager.java index 0ab87d2..9b74cfe 100644 --- a/src/lia/util/net/copy/disk/ResumeManager.java +++ b/src/lia/util/net/copy/disk/ResumeManager.java @@ -3,23 +3,25 @@ */ package lia.util.net.copy.disk; +import lia.util.net.copy.FileSession; + import java.io.File; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.FileSession; - /** * This class is used to "check" if a {@link FileSession} is already copied or not * For the moment, two "remote" {@link java.io.File}-s are considered - * equal only if they have the {@link java.io.File.length} and {@link java.io.File.lastModified} are the same for both + * equal only if they have the {@link java.io.File length} and {@link java.io.File lastModified} are the same for both * of them - * + * * @author ramiro */ public class ResumeManager { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final transient Logger logger = Logger.getLogger(ResumeManager.class.getName()); private static final ResumeManager _thisInstance = new ResumeManager(); @@ -54,9 +56,9 @@ public boolean isFinished(FileSession fileSession) { } } catch (Throwable t) { logger.log(Level.WARNING, - " [ ResumeManager ] Got exception checking if fileSession [ " + fileSession.fileName() + " / " + fileSession.sessionID() - + " ] is finished ", - t); + " [ ResumeManager ] Got exception checking if fileSession [ " + fileSession.fileName() + " / " + fileSession.sessionID() + + " ] is finished ", + t); } return false; diff --git a/src/lia/util/net/copy/filters/Postprocessor.java b/src/lia/util/net/copy/filters/Postprocessor.java index cdbeb60..19dcc2e 100644 --- a/src/lia/util/net/copy/filters/Postprocessor.java +++ b/src/lia/util/net/copy/filters/Postprocessor.java @@ -6,10 +6,9 @@ import javax.security.auth.Subject; /** - * * Base class used to implement post filters plugins in FDT. The Postprocessor * is called after a FDT session finishes. - * + * * @author ramiro */ public interface Postprocessor { diff --git a/src/lia/util/net/copy/filters/Preprocessor.java b/src/lia/util/net/copy/filters/Preprocessor.java index 2974bf6..3423477 100644 --- a/src/lia/util/net/copy/filters/Preprocessor.java +++ b/src/lia/util/net/copy/filters/Preprocessor.java @@ -6,12 +6,11 @@ import javax.security.auth.Subject; /** - * * Base class used to implement pre filters plugins in FDT. The Preprocessor * is called before a FDT session starts. - * + * * @author ramiro */ public interface Preprocessor { - public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception; + public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception; } diff --git a/src/lia/util/net/copy/filters/ProcessorInfo.java b/src/lia/util/net/copy/filters/ProcessorInfo.java index e0272af..ef3ea08 100644 --- a/src/lia/util/net/copy/filters/ProcessorInfo.java +++ b/src/lia/util/net/copy/filters/ProcessorInfo.java @@ -3,21 +3,20 @@ */ package lia.util.net.copy.filters; +import lia.util.net.copy.FileSession; + import java.net.InetAddress; import java.util.Arrays; import java.util.Map; -import lia.util.net.copy.FileSession; - /** - * * This class encapsulates the file list which is to be, or has been transfered * and the destination directory. - * + * * @author ramiro */ public class ProcessorInfo { - + public String[] fileList; public String destinationDir; /** @@ -32,17 +31,17 @@ public class ProcessorInfo { * @since 0.9.25 */ public boolean recursive; - + /** * Non-null on writer side ONLY. *
    - * Gives access to the transfer map of an FDT session. + * Gives access to the transfer map of an FDT session. *
    * Key - the final file name (including the destination directory) for a {@link FileSession}
    * Value - the {@link FileSession}
    - * + * * @see FileSession - * @since 0.10.0 + * @since 0.10.0 */ public Map fileSessionMap; @@ -53,19 +52,19 @@ public class ProcessorInfo { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ProcessorInfo [destinationDir=") - .append(destinationDir) - .append(", remoteAddress=") - .append(remoteAddress) - .append(", remotePort=") - .append(remotePort) - .append(", recursive=") - .append(recursive) - .append(", fileList=") - .append(Arrays.toString(fileList)) - .append(", fileSessionMap=") - .append(fileSessionMap) - .append("]"); + .append(destinationDir) + .append(", remoteAddress=") + .append(remoteAddress) + .append(", remotePort=") + .append(remotePort) + .append(", recursive=") + .append(recursive) + .append(", fileList=") + .append(Arrays.toString(fileList)) + .append(", fileSessionMap=") + .append(fileSessionMap) + .append("]"); return builder.toString(); } - + } diff --git a/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java b/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java index 4bdbc2d..7298c17 100644 --- a/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java +++ b/src/lia/util/net/copy/filters/examples/FirewallFileExtension.java @@ -20,9 +20,9 @@ * This filter can be used directly (as it ships with the fdt.jar): *

    *
    java -DFirewallFileExtension.suffix="/some/path/TMP_TEST" -jar fdt.jar ... other params
    - * - * @see Pattern + * * @author Raimondas Sirvinskas + * @see Pattern */ public class FirewallFileExtension implements Preprocessor { @@ -30,30 +30,30 @@ public class FirewallFileExtension implements Preprocessor { * Logger used by this class */ private static final Logger logger = Logger.getLogger(FirewallFileExtension.class.getName()); + /** * @param processorInfo - * @param peerSubject - * - not used + * @param peerSubject - not used */ public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception { final Map fileSessionMap = processorInfo.fileSessionMap; final String firewallSuffix = System.getProperty("FirewallFileExtension.suffix"); - if(firewallSuffix == null || firewallSuffix.trim().isEmpty()) { - logger.log(Level.INFO,"[ FirewallFileNames ] No suffix defined"); + if (firewallSuffix == null || firewallSuffix.trim().isEmpty()) { + logger.log(Level.INFO, "[ FirewallFileNames ] No suffix defined"); return; } - + final String firewallSuffixTrim = firewallSuffix.trim(); - logger.log(Level.INFO," [ FirewallFileNames ] firewall suffix pattern=" + firewallSuffixTrim); + logger.log(Level.INFO, " [ FirewallFileNames ] firewall suffix pattern=" + firewallSuffixTrim); - for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext();) { + for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext(); ) { final Map.Entry entry = iterator.next(); final FileSession fileSession = entry.getValue(); final String fName = fileSession.fileName(); - logger.log(Level.INFO,"[ FirewallFileNames ] fname = " + fName); + logger.log(Level.INFO, "[ FirewallFileNames ] fname = " + fName); if (fName.endsWith(firewallSuffixTrim)) { - logger.log(Level.INFO,"FNAME firewalled: " + fName); + logger.log(Level.INFO, "FNAME firewalled: " + fName); iterator.remove(); } } diff --git a/src/lia/util/net/copy/filters/examples/FirewallFileNames.java b/src/lia/util/net/copy/filters/examples/FirewallFileNames.java index 8fec6fb..1bcc638 100644 --- a/src/lia/util/net/copy/filters/examples/FirewallFileNames.java +++ b/src/lia/util/net/copy/filters/examples/FirewallFileNames.java @@ -1,15 +1,14 @@ package lia.util.net.copy.filters.examples; -import java.util.Iterator; -import java.util.Map; -import java.util.regex.Pattern; - -import javax.security.auth.Subject; - import lia.util.net.copy.FileSession; import lia.util.net.copy.filters.Preprocessor; import lia.util.net.copy.filters.ProcessorInfo; +import javax.security.auth.Subject; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Pattern; + /** *

    * Simple example which filters some files based on a prefix passed as a Java environment variable @@ -18,33 +17,32 @@ *

    * This filter can be used directly (as it ships with the fdt.jar): *

    - *
    java -DFirewallFileNames.prefix="/some/path/TMP_TEST" -jar fdt.jar ... other params
    - * - * @see Pattern + *
    java -DFirewallFileNames.prefix="/some/path/TMP_TEST" -jar fdt.jar ... other params
    + * * @author ramiro + * @see Pattern */ public class FirewallFileNames implements Preprocessor { /** * @param processorInfo - * @param peerSubject - * - not used + * @param peerSubject - not used */ public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception { final Map fileSessionMap = processorInfo.fileSessionMap; final String firewallPrefix = System.getProperty("FirewallFileNames.prefix"); - if(firewallPrefix == null || firewallPrefix.trim().isEmpty()) { + if (firewallPrefix == null || firewallPrefix.trim().isEmpty()) { System.out.println("[ FirewallFileNames ] No prefix defined"); return; } - + final String firewallPrefixTrim = firewallPrefix.trim(); System.out.println(" [ FirewallFileNames ] firewall prefix pattern=" + firewallPrefixTrim); - for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext();) { + for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext(); ) { final Map.Entry entry = iterator.next(); final FileSession fileSession = entry.getValue(); //System.out.println("Key: " + key); diff --git a/src/lia/util/net/copy/filters/examples/FixUserHome.java b/src/lia/util/net/copy/filters/examples/FixUserHome.java index c59e037..eb95a33 100644 --- a/src/lia/util/net/copy/filters/examples/FixUserHome.java +++ b/src/lia/util/net/copy/filters/examples/FixUserHome.java @@ -1,25 +1,24 @@ package lia.util.net.copy.filters.examples; -import java.util.Iterator; -import java.util.Map; - -import javax.security.auth.Subject; import lia.util.net.copy.FileSession; import lia.util.net.copy.filters.Preprocessor; import lia.util.net.copy.filters.ProcessorInfo; +import javax.security.auth.Subject; +import java.util.Iterator; +import java.util.Map; + /** - * * Simple example which replaces the file names on the writer side * * @author ramiro */ public class FixUserHome implements Preprocessor { - + /** - * @param processorInfo - * @param peerSubject - not used + * @param processorInfo + * @param peerSubject - not used */ public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) throws Exception { @@ -32,7 +31,7 @@ public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) System.out.println("FixUserHome for user '" + userName + "' and $HOME '" + userHome + "' "); - for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext();) { + for (Iterator> iterator = fileSessionMap.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); final String key = entry.getKey(); final FileSession fileSession = entry.getValue(); @@ -41,11 +40,11 @@ public void preProcessFileList(ProcessorInfo processorInfo, Subject peerSubject) final String fName = fileSession.fileName(); // final String newFName = destDirName.replace("~", "/home/ramiro") + fName.substring(dLen); - + // // TODO - Check if needs to be replaced. // - + final String newFName = destDirName.replace("~", userHome) + fName.substring(dLen); // file separator + ~ System.out.println(" ----> OLD: " + fName + " <---> NEW: " + newFName + " <--- "); diff --git a/src/lia/util/net/copy/filters/examples/PostRename.java b/src/lia/util/net/copy/filters/examples/PostRename.java index 4cad078..0b11457 100644 --- a/src/lia/util/net/copy/filters/examples/PostRename.java +++ b/src/lia/util/net/copy/filters/examples/PostRename.java @@ -14,12 +14,12 @@ */ public class PostRename implements Postprocessor { + public static final String PREFIX = "prefix"; + public static final String DEFAULT_PREFIX = "RENAMED_"; /** * Logger used by this class */ private static final Logger logger = Logger.getLogger(PostRename.class.getName()); - public static final String PREFIX = "prefix"; - public static final String DEFAULT_PREFIX = "RENAMED_"; public void postProcessFileList(ProcessorInfo processorInfo, Subject peerSubject, Throwable downCause, String downMessage) throws Exception { logger.log(Level.INFO, " [ PostRename ] Subject: " + peerSubject); diff --git a/src/lia/util/net/copy/filters/examples/PostZipFilter.java b/src/lia/util/net/copy/filters/examples/PostZipFilter.java index bc53dd0..7260949 100644 --- a/src/lia/util/net/copy/filters/examples/PostZipFilter.java +++ b/src/lia/util/net/copy/filters/examples/PostZipFilter.java @@ -2,6 +2,11 @@ * $Id$ */ package lia.util.net.copy.filters.examples; + +import lia.util.net.copy.filters.Postprocessor; +import lia.util.net.copy.filters.ProcessorInfo; + +import javax.security.auth.Subject; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -9,15 +14,10 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import javax.security.auth.Subject; - -import lia.util.net.copy.filters.Postprocessor; -import lia.util.net.copy.filters.ProcessorInfo; - /** * Simple postProcess FDT Filter. It decompresses the zip files received * from the remote peer and then deletes the archives files. - * + * * @author ramiro */ public class PostZipFilter implements Postprocessor { @@ -26,7 +26,7 @@ public void postProcessFileList(ProcessorInfo processorInfo, Subject peerSubject System.out.println(" [ PostZipFilter ] Subject: " + peerSubject); - for(int i=0; i

    FDT - Fast Data Transfer

    ", JLabel.CENTER); - JPanel p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - p1.add(l1, BorderLayout.CENTER); - getContentPane().add(p1); - - l1 = new JLabel("

    http://monalisa.cern.ch/FDT

    ", JLabel.CENTER); - l1.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - try { - URL u = new URL("http://monalisa.cern.ch/FDT"); - showDocument(u); - } catch (Throwable t) { - t.printStackTrace(); - } - } - }); - p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - p1.add(l1, BorderLayout.CENTER); - getContentPane().add(p1); - + + private static final Object basicServiceObject = getBasicServiceObject(); + private static final Class basicServiceClass = getBasicServiceClass(); + private ImageIcon caltechIcon; + + public AboutDialog(JFrame parent) { + super(parent, "About...", true); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + + JLabel l1 = new JLabel("

    FDT - Fast Data Transfer

    ", JLabel.CENTER); + JPanel p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + p1.add(l1, BorderLayout.CENTER); + getContentPane().add(p1); + + l1 = new JLabel("

    http://monalisa.cern.ch/FDT

    ", JLabel.CENTER); + l1.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + try { + URL u = new URL("http://monalisa.cern.ch/FDT"); + showDocument(u); + } catch (Throwable t) { + t.printStackTrace(); + } + } + }); + p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + p1.add(l1, BorderLayout.CENTER); + getContentPane().add(p1); + // String version = "

    Version "+FDT.FDT_FULL_VERSION+"

    "; - l1 = new JLabel("

    Version "+FDT.FDT_FULL_VERSION+"

    ", JLabel.CENTER); - p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - p1.add(l1, BorderLayout.CENTER); - getContentPane().add(p1); - - l1 = new JLabel(getCaltechIcon()); - p1 = new JPanel(); - p1.add(l1, BorderLayout.CENTER); - getContentPane().add(p1); - - setVisible(false); - setSize(200,100); - setBackground(new Color(15724527)); + l1 = new JLabel("

    Version " + FDT.FDT_FULL_VERSION + "

    ", JLabel.CENTER); + p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + p1.add(l1, BorderLayout.CENTER); + getContentPane().add(p1); + + l1 = new JLabel(getCaltechIcon()); + p1 = new JPanel(); + p1.add(l1, BorderLayout.CENTER); + getContentPane().add(p1); + + setVisible(false); + setSize(200, 100); + setBackground(new Color(15724527)); // setTitle("About..."); - setResizable(false); - - JButton okButton = new JButton("OK"); - okButton.setMinimumSize(new Dimension(190, 22)); - okButton.setPreferredSize(new Dimension(190, 22)); - p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - p1.add(okButton, BorderLayout.CENTER); - getContentPane().add(p1); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); - pack(); - } - - /** - * Shows or hides the component depending on the Boolean flag b. - * @param b if true, show the component; otherwise, hide the - * component. - * @See java.awt.Component#isVisible - */ - public void setVisible(boolean b) { - if(b) { - Dimension bounds = Toolkit.getDefaultToolkit().getScreenSize(); - Dimension abounds = getSize(); - setLocation((bounds.width - abounds.width) / 2, (bounds.height - abounds.height) / 3); - } - super.setVisible(b); - } - - private ImageIcon caltechIcon; - public Icon getCaltechIcon() { - if (caltechIcon != null) return caltechIcon; - try { - URL url = getClass().getResource("icons/caltech.gif"); - caltechIcon = new ImageIcon(url); - } catch (Throwable t) { } - return caltechIcon; - } - - private static final Object basicServiceObject = getBasicServiceObject ( ); - - private static final Class basicServiceClass = getBasicServiceClass ( ); - - public static boolean showDocument(URL url) { - if (basicServiceObject == null) { - return false; - } - try { - Method method = basicServiceClass.getMethod("showDocument", new Class [ ] { URL.class } ); - Boolean resultBoolean = (Boolean)method.invoke ( basicServiceObject, new Object [ ] { url } ); - return resultBoolean.booleanValue ( ); - } catch (Exception ex) { - ex.printStackTrace ( ); - throw new RuntimeException ( ex.getMessage ( ) ); - } - } - - private static Object getBasicServiceObject ( ) { - try { - Class serviceManagerClass = Class.forName ( "javax.jnlp.ServiceManager" ); - Method lookupMethod = serviceManagerClass.getMethod ( "lookup", new Class [ ] { String.class } ); - return lookupMethod.invoke(null, new Object [ ] { "javax.jnlp.BasicService" } ); - } catch (Exception ex) { - return null; - } - } - - private static Class getBasicServiceClass ( ) { - try { - return Class.forName ("javax.jnlp.BasicService"); - } catch (Exception ex) { - return null; - } - } + setResizable(false); + + JButton okButton = new JButton("OK"); + okButton.setMinimumSize(new Dimension(190, 22)); + okButton.setPreferredSize(new Dimension(190, 22)); + p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + p1.add(okButton, BorderLayout.CENTER); + getContentPane().add(p1); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + pack(); + } + + public static boolean showDocument(URL url) { + if (basicServiceObject == null) { + return false; + } + try { + Method method = basicServiceClass.getMethod("showDocument", new Class[]{URL.class}); + Boolean resultBoolean = (Boolean) method.invoke(basicServiceObject, new Object[]{url}); + return resultBoolean.booleanValue(); + } catch (Exception ex) { + ex.printStackTrace(); + throw new RuntimeException(ex.getMessage()); + } + } + + private static Object getBasicServiceObject() { + try { + Class serviceManagerClass = Class.forName("javax.jnlp.ServiceManager"); + Method lookupMethod = serviceManagerClass.getMethod("lookup", new Class[]{String.class}); + return lookupMethod.invoke(null, new Object[]{"javax.jnlp.BasicService"}); + } catch (Exception ex) { + return null; + } + } + + private static Class getBasicServiceClass() { + try { + return Class.forName("javax.jnlp.BasicService"); + } catch (Exception ex) { + return null; + } + } + + /** + * Shows or hides the component depending on the Boolean flag b. + * + * @param b if true, show the component; otherwise, hide the + * component. + * @See java.awt.Component#isVisible + */ + public void setVisible(boolean b) { + if (b) { + Dimension bounds = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension abounds = getSize(); + setLocation((bounds.width - abounds.width) / 2, (bounds.height - abounds.height) / 3); + } + super.setVisible(b); + } + + public Icon getCaltechIcon() { + if (caltechIcon != null) return caltechIcon; + try { + URL url = getClass().getResource("icons/caltech.gif"); + caltechIcon = new ImageIcon(url); + } catch (Throwable t) { + } + return caltechIcon; + } } diff --git a/src/lia/util/net/copy/gui/ClientSessionManager.java b/src/lia/util/net/copy/gui/ClientSessionManager.java index e160a34..bc9e193 100644 --- a/src/lia/util/net/copy/gui/ClientSessionManager.java +++ b/src/lia/util/net/copy/gui/ClientSessionManager.java @@ -3,14 +3,6 @@ */ package lia.util.net.copy.gui; -import java.lang.reflect.Field; -import java.text.NumberFormat; -import java.util.HashMap; -import java.util.concurrent.RunnableScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - import lia.util.net.common.Config; import lia.util.net.common.HeaderBufferPool; import lia.util.net.common.Utils; @@ -21,252 +13,274 @@ import lia.util.net.copy.monitoring.FDTSessionMonitoringTask; import lia.util.net.copy.transport.TCPTransportProvider; +import java.lang.reflect.Field; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.concurrent.RunnableScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * @author Ciprian Dobre */ public class ClientSessionManager { - static final transient Logger logger = Logger.getLogger(ClientSessionManager.class.getCanonicalName()); + static final transient Logger logger = Logger.getLogger(ClientSessionManager.class.getCanonicalName()); + private static final long KILO_BIT = 1000; + private static final long MEGA_BIT = KILO_BIT * 1000; + private static final long GIGA_BIT = MEGA_BIT * 1000; + private static final long TERA_BIT = GIGA_BIT * 1000; + private static final long PETA_BIT = TERA_BIT * 1000; + private final static NumberFormat nf = NumberFormat.getInstance(); - private FDTSession currentSession; - private FDTSessionMonitoringTask fdtSessionMTask; - - private static final long KILO_BIT = 1000; - private static final long MEGA_BIT = KILO_BIT * 1000; - private static final long GIGA_BIT = MEGA_BIT * 1000; - private static final long TERA_BIT = GIGA_BIT * 1000; - private static final long PETA_BIT = TERA_BIT * 1000; + static { + nf.setMaximumFractionDigits(2); + } + private FDTSession currentSession; + private FDTSessionMonitoringTask fdtSessionMTask; private RunnableScheduledFuture fdtInternalMonitoringTask = null; private RunnableScheduledFuture consoleReporting = null; - - /** - * Called in order to initialize a connection with a remote port... - * @param host - * @param port - */ - public String initTransfer(final String host, final int port, final boolean isPullMode, - final String[] fileList, final String destDir, final FDTPropsDialog d, final boolean isRecursive) { - // start by constructing a dummy config - constructConfig(host, port, isPullMode, fileList, destDir, d, isRecursive); - HeaderBufferPool.initInstance(); - fdtInternalMonitoringTask = (RunnableScheduledFuture)Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, TimeUnit.SECONDS); - consoleReporting = (RunnableScheduledFuture)Utils.getMonitoringExecService().scheduleWithFixedDelay(ConsoleReportingTask.getInstance(), 1, 2, TimeUnit.SECONDS); - // the session manager will check the "pull/push" mode and start the FDTSession - try { - currentSession = FDTSessionManager.getInstance().addFDTClientSession(port); - fdtSessionMTask = currentSession.getMonitoringTask(); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception when initiating transfer", t); - return t.getLocalizedMessage(); - } - return null; - } - - public FDTSession currentSession() { - return currentSession; - } - - public void cancelTransfer() { - if (currentSession == null) return; + + //do it nicer - TODO make same arrays and use for() ... it's not the 5th grade + private static final String formatNetSpeed(final double number, final String append) { + String appendUM; + double fNo = number; + + if (number > PETA_BIT) { + fNo /= PETA_BIT; + appendUM = "P" + append; + } else if (number > TERA_BIT) { + fNo /= TERA_BIT; + appendUM = "T" + append; + } else if (number > GIGA_BIT) { + fNo /= GIGA_BIT; + appendUM = "G" + append; + } else if (number > MEGA_BIT) { + fNo /= MEGA_BIT; + appendUM = "M" + append; + } else if (number > KILO_BIT) { + fNo /= KILO_BIT; + appendUM = "K" + append; + } else { + appendUM = append; + } + + return nf.format(fNo) + " " + appendUM; + } + + /** + * Called in order to initialize a connection with a remote port... + * + * @param host + * @param port + */ + public String initTransfer(final String host, final int port, final boolean isPullMode, + final String[] fileList, final String destDir, final FDTPropsDialog d, final boolean isRecursive) { + // start by constructing a dummy config + constructConfig(host, port, isPullMode, fileList, destDir, d, isRecursive); + HeaderBufferPool.initInstance(); + fdtInternalMonitoringTask = (RunnableScheduledFuture) Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, TimeUnit.SECONDS); + consoleReporting = (RunnableScheduledFuture) Utils.getMonitoringExecService().scheduleWithFixedDelay(ConsoleReportingTask.getInstance(), 1, 2, TimeUnit.SECONDS); + // the session manager will check the "pull/push" mode and start the FDTSession + try { + currentSession = FDTSessionManager.getInstance().addFDTClientSession(port); + fdtSessionMTask = currentSession.getMonitoringTask(); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception when initiating transfer", t); + return t.getLocalizedMessage(); + } + return null; + } + + public FDTSession currentSession() { + return currentSession; + } + + public void cancelTransfer() { + if (currentSession == null) return; currentSession.close("User pressed cancel", new Exception("User pressed cancel")); - currentSession = null; - fdtSessionMTask = null; - } - - public void end() { - if (fdtInternalMonitoringTask != null) { - Utils.getMonitoringExecService().remove(fdtInternalMonitoringTask); - fdtInternalMonitoringTask = null; - } - if (consoleReporting != null) { - Utils.getMonitoringExecService().remove(consoleReporting); - consoleReporting = null; - } - Utils.getMonitoringExecService().purge(); - } - - private final static NumberFormat nf = NumberFormat.getInstance(); - static { - nf.setMaximumFractionDigits(2); - } - - public double transferProgress() { - if (currentSession == null) { - return 100.0; - } - + currentSession = null; + fdtSessionMTask = null; + } + + public void end() { + if (fdtInternalMonitoringTask != null) { + Utils.getMonitoringExecService().remove(fdtInternalMonitoringTask); + fdtInternalMonitoringTask = null; + } + if (consoleReporting != null) { + Utils.getMonitoringExecService().remove(consoleReporting); + consoleReporting = null; + } + Utils.getMonitoringExecService().purge(); + } + + public double transferProgress() { + if (currentSession == null) { + return 100.0; + } + TCPTransportProvider tcpTransportProvider = currentSession.getTransportProvider(); if (tcpTransportProvider == null) { return 0.0; } - - if (tcpTransportProvider.isClosed()) { - logger.warning("Transport is closed"); - return 100.0; - } + + if (tcpTransportProvider.isClosed()) { + logger.warning("Transport is closed"); + return 100.0; + } final double tSize = currentSession.getSize(); - + final long tcpSize = tcpTransportProvider.getUtilBytes(); final double cSize = (tcpSize <= 0L) ? 0D : tcpSize; - + double percent = 100.0; try { - percent = Math.min((cSize*100.0)/(double)tSize, 100.0); - } catch (Exception e) { } + percent = Math.min((cSize * 100.0) / (double) tSize, 100.0); + } catch (Exception e) { + } if (!Double.isNaN(percent) && !Double.isInfinite(percent) && percent >= 100.0) { - try { + try { int state = currentSession.currentState(); - boolean endRcv = ((state & FDTSession.END_RCV) == FDTSession.END_RCV); - boolean endSnt = ((state & FDTSession.END_SENT) == FDTSession.END_SENT); - if (((state & FDTSession.TRANSFERING) == FDTSession.TRANSFERING) || (!endRcv && !endSnt)) { - return 99.99; - } - } catch (Throwable t) { - t.printStackTrace(); - } + boolean endRcv = ((state & FDTSession.END_RCV) == FDTSession.END_RCV); + boolean endSnt = ((state & FDTSession.END_SENT) == FDTSession.END_SENT); + if (((state & FDTSession.TRANSFERING) == FDTSession.TRANSFERING) || (!endRcv && !endSnt)) { + return 99.99; + } + } catch (Throwable t) { + t.printStackTrace(); + } } return percent; - } - - public String currentSpeed() { - if (currentSession == null) return "0.0 b/s"; - try { - if (currentSession.getTransportProvider() != null && currentSession.getTransportProvider().isClosed()) { - logger.warning("Transport is closed"); - return "0.0 b/s"; - } - double rate = currentSession.getTransportProvider().monitoringTask.getTotalRate() * 8; - return formatNetSpeed(rate, "b/s"); - } catch (Throwable t) { - return "0.0 b/s"; - } - } - - //do it nicer - TODO make same arrays and use for() ... it's not the 5th grade - private static final String formatNetSpeed(final double number, final String append) { - String appendUM; - double fNo = number; - - if(number > PETA_BIT) { - fNo /= PETA_BIT; - appendUM = "P" + append; - } else if(number > TERA_BIT) { - fNo /= TERA_BIT; - appendUM = "T" + append; - } else if(number > GIGA_BIT) { - fNo /= GIGA_BIT; - appendUM = "G" + append; - } else if(number > MEGA_BIT) { - fNo /= MEGA_BIT; - appendUM = "M" + append; - } else if(number > KILO_BIT) { - fNo /= KILO_BIT; - appendUM = "K" + append; - } else { - appendUM = append; - } - - return nf.format(fNo) + " " + appendUM; - } + } + + public String currentSpeed() { + if (currentSession == null) return "0.0 b/s"; + try { + if (currentSession.getTransportProvider() != null && currentSession.getTransportProvider().isClosed()) { + logger.warning("Transport is closed"); + return "0.0 b/s"; + } + double rate = currentSession.getTransportProvider().monitoringTask.getTotalRate() * 8; + return formatNetSpeed(rate, "b/s"); + } catch (Throwable t) { + return "0.0 b/s"; + } + } + + /** + * Constructs a Config object based on provided arguments.. + */ + private final void constructConfig(final String host, final int port, final boolean isPullMode, + final String[] fileList, final String destDir, final FDTPropsDialog d, final boolean isRecursive) { + // first set the initialized flag on false.... + Class c = Config.class; + // construct the hashmap + try { + Config.initInstance(new HashMap()); + } catch (Throwable t1) { + t1.printStackTrace(); + } + Config conf = Config.getInstance(); + // shall I get the data from server? - used only by the client + try { + conf.setHostName(host); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("portNo"); + f.setAccessible(true); + f.set(conf, port); + } catch (Throwable t) { + } + try { + conf.setPullMode(isPullMode); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + Field f = c.getDeclaredField("fileList"); + f.setAccessible(true); + f.set(conf, fileList); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("destDir"); + f.setAccessible(true); + f.set(conf, destDir); + } catch (Throwable t) { + } + System.out.println("hostname = " + conf.getHostName()); + System.out.println("port = " + conf.getPort()); + String files[] = conf.getFileList(); + for (int i = 0; i < files.length; i++) + System.out.println(files[i]); + System.out.println("dest = " + conf.getDestinationDir()); + System.out.println("isPull=" + conf.isPullMode()); + if (d == null) return; + try { + Field f = c.getDeclaredField("sockBufSize"); + f.setAccessible(true); + f.set(conf, d.getSockBufSize()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("sockNum"); + f.setAccessible(true); + f.set(conf, d.getSockNum()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("rateLimit"); + f.setAccessible(true); + f.set(conf, d.getRateLimit()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("readersCount"); + f.setAccessible(true); + f.set(conf, d.getReadersCount()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("writersCount"); + f.setAccessible(true); + f.set(conf, d.getWritersCount()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("maxPartitionsCount"); + f.setAccessible(true); + f.set(conf, d.getMaxPartitionsCount()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("bComputeMD5"); + f.setAccessible(true); + f.set(conf, d.isBComputeMD5()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("bRecursive"); + f.setAccessible(true); + f.set(conf, isRecursive); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("bUseFixedBlocks"); + f.setAccessible(true); + f.set(conf, d.isBUseFixedBlocks()); + } catch (Throwable t) { + } + try { + Field f = c.getDeclaredField("transferLimit"); + f.setAccessible(true); + f.set(conf, d.getTransferLimit()); + } catch (Throwable t) { + } + } - /** - * Constructs a Config object based on provided arguments.. - */ - private final void constructConfig(final String host, final int port, final boolean isPullMode, - final String[] fileList, final String destDir, final FDTPropsDialog d, final boolean isRecursive) { - // first set the initialized flag on false.... - Class c = Config.class; - // construct the hashmap - try { - Config.initInstance(new HashMap()); - } catch (Throwable t1) { - t1.printStackTrace(); - } - Config conf = Config.getInstance(); - // shall I get the data from server? - used only by the client - try { - conf.setHostName(host); - } catch (Throwable t) { } - try { - Field f = c.getDeclaredField("portNo"); - f.setAccessible(true); - f.set(conf, port); - } catch (Throwable t) { } - try { - conf.setPullMode(isPullMode); - } catch (Throwable t) { - t.printStackTrace(); - } - try { - Field f = c.getDeclaredField("fileList"); - f.setAccessible(true); - f.set(conf, fileList); - } catch (Throwable t) { } - try { - Field f = c.getDeclaredField("destDir"); - f.setAccessible(true); - f.set(conf, destDir); - } catch (Throwable t) { } - System.out.println("hostname = "+conf.getHostName()); - System.out.println("port = "+conf.getPort()); - String files[] = conf.getFileList(); - for (int i=0; i"); - - // Get the date from the LogRecord and add it to the buffer - Date date = new Date(record.getMillis()); - sb.append(date.toString()); - sb.append(" "); - - // get the name of the class - sb.append(record.getSourceClassName()); - sb.append(" "); - - // get the name of the method - sb.append(record.getSourceMethodName().replace("<", "[").replace(">", "]")); - sb.append("\n"); - - // Get the level name and add it to the buffer - sb.append(record.getLevel().getName()); - sb.append(": "); - - // Get the formatted message (includes localization - // and substitution of paramters) and add it to the buffer - sb.append(formatMessage(record).replace("<", "[").replace(">", "]")); - sb.append("\n"); - - sb.append(""); - - return sb.toString(); - } - } - + + /* (non-API documentation) + * @see java.util.logging.Handler#flush() + */ + public void flush() { + } + + /* (non-API documentation) + * @see java.util.logging.Handler#close() + */ + public void close() throws SecurityException { + } + + class MyCustomFormatter extends Formatter { + + public MyCustomFormatter() { + super(); + } + + public String format(LogRecord record) { + + // Create a StringBuffer to contain the formatted record + // start with the date. + StringBuffer sb = new StringBuffer(); + + sb.append(""); + + // Get the date from the LogRecord and add it to the buffer + Date date = new Date(record.getMillis()); + sb.append(date.toString()); + sb.append(" "); + + // get the name of the class + sb.append(record.getSourceClassName()); + sb.append(" "); + + // get the name of the method + sb.append(record.getSourceMethodName().replace("<", "[").replace(">", "]")); + sb.append("\n"); + + // Get the level name and add it to the buffer + sb.append(record.getLevel().getName()); + sb.append(": "); + + // Get the formatted message (includes localization + // and substitution of paramters) and add it to the buffer + sb.append(formatMessage(record).replace("<", "[").replace(">", "]")); + sb.append("\n"); + + sb.append(""); + + return sb.toString(); + } + } + } // end of class CustomLogHandler diff --git a/src/lia/util/net/copy/gui/CustomPrintStream.java b/src/lia/util/net/copy/gui/CustomPrintStream.java index 9eee44c..cb03c7f 100644 --- a/src/lia/util/net/copy/gui/CustomPrintStream.java +++ b/src/lia/util/net/copy/gui/CustomPrintStream.java @@ -5,41 +5,41 @@ /** * A custom PrintStream that is designed to capture println and output it into the status panel as well - * @author cipsm * + * @author cipsm */ public class CustomPrintStream extends PrintStream { - private final StatusBar status; - private final String color; - - public CustomPrintStream(final StatusBar status, OutputStream out, String color) { - super(out); - this.status = status; - this.color = color; - } - - private void setStatus(String status) { - if (this.status == null) return; - status = status.replace("<", "[").replace(">", "]"); - this.status.addText(""+status+""); + private final StatusBar status; + private final String color; + + public CustomPrintStream(final StatusBar status, OutputStream out, String color) { + super(out); + this.status = status; + this.color = color; } - - public void println(String string) { - super.print(string.toCharArray()); - setStatus(string); - print("\n"); - } - - public void println() { - super.println(); - setStatus("\n"); - } - + + private void setStatus(String status) { + if (this.status == null) return; + status = status.replace("<", "[").replace(">", "]"); + this.status.addText("" + status + ""); + } + + public void println(String string) { + super.print(string.toCharArray()); + setStatus(string); + print("\n"); + } + + public void println() { + super.println(); + setStatus("\n"); + } + public void print(String string) { - super.print(string); - setStatus(string); + super.print(string); + setStatus(string); } - + } // end of class CustomPrintStream diff --git a/src/lia/util/net/copy/gui/DummyRemoteSessionManager.java b/src/lia/util/net/copy/gui/DummyRemoteSessionManager.java index 6a4f94f..2ca9fee 100644 --- a/src/lia/util/net/copy/gui/DummyRemoteSessionManager.java +++ b/src/lia/util/net/copy/gui/DummyRemoteSessionManager.java @@ -3,93 +3,115 @@ */ package lia.util.net.copy.gui; -import java.util.Vector; - import lia.util.net.copy.transport.gui.FileHandler; +import java.util.Vector; + /** - * * Dummy class to test the remote session manager class - * + * * @author Ciprian Dobre */ public class DummyRemoteSessionManager extends RemoteSessionManager { - public DummyRemoteSessionManager(FDTPropsDialog props) { - super(props, null); - } - - /** Receives the current directory of the session.. (default user home) */ - public String getWorkingDirectory() { - return System.getProperty("user.dir"); - } - - /** Receives the name of the operating system */ - public String getOSName() { - return "Linux"; - } - - /** Return true if the current working dirctory is a root (no upper level) */ - public boolean isRoot() { - return true; - } - - /** Receives the list of current files and folder in the current directory of the session */ - public Vector getFileList() { - Vector v = new Vector(); - // add two folders... - v.add(new FileHandler("dir1", System.currentTimeMillis(), -1, false, true)); - v.add(new FileHandler("dir2", System.currentTimeMillis(), -1, true, true)); - // now add some files - v.add(new FileHandler("image.png", System.currentTimeMillis(), (int)(Math.random() * 100000), true, false)); - v.add(new FileHandler("image.gif", System.currentTimeMillis(), (int)(Math.random() * 100000), false, false)); - v.add(new FileHandler("image.jpeg", System.currentTimeMillis(), (int)(Math.random() * 100000), true, true)); - v.add(new FileHandler("cd.iso", System.currentTimeMillis(), (int)(Math.random() * 100000), true, false)); - v.add(new FileHandler("file.doc", System.currentTimeMillis(), (int)(Math.random() * 100000), false, true)); - v.add(new FileHandler("file.ppt", System.currentTimeMillis(), (int)(Math.random() * 100000), true, false)); - v.add(new FileHandler("file.txt", System.currentTimeMillis(), (int)(Math.random() * 100000), true, true)); - v.add(new FileHandler("dummy", System.currentTimeMillis(), (int)(Math.random() * 100000), true, false)); - return v; - } - - /** Send the command to the other end to set the current working directory to an absolute pathname */ - public void setAbsoluteDir(String dir) { - } - - /** Send the command to the other end to set the current working directory to a relative to the current dir pathname */ - public void setRelativeDir(String dir) { - try { - Thread.sleep(10000); - } catch (Exception e) { } - } - - /** Send the command to the other end to set the current working directory up one level */ - public void setUpDir() { - } - - /** Receives the known root pathes of the remote FS */ - public String[] getRoots() { - return new String[] { "C:", "D:" }; - } - - /** Receives the pathname denoted by a root folder (for the special case of links...) */ - public String getShortRootName(String rootFolder) { - return rootFolder; - } - - int count = 0; - - /** Called to initiatilize a file transfer */ - public String initiateTransfer(String files[], boolean push) { - count = 0; - return null; - } - - /** Called in order to interrogate on the status of the current transfer */ - public double getTransferPercent() { - count += 10; - return count; - } - + int count = 0; + + public DummyRemoteSessionManager(FDTPropsDialog props) { + super(props, null); + } + + /** + * Receives the current directory of the session.. (default user home) + */ + public String getWorkingDirectory() { + return System.getProperty("user.dir"); + } + + /** + * Receives the name of the operating system + */ + public String getOSName() { + return "Linux"; + } + + /** + * Return true if the current working dirctory is a root (no upper level) + */ + public boolean isRoot() { + return true; + } + + /** + * Receives the list of current files and folder in the current directory of the session + */ + public Vector getFileList() { + Vector v = new Vector(); + // add two folders... + v.add(new FileHandler("dir1", System.currentTimeMillis(), -1, false, true)); + v.add(new FileHandler("dir2", System.currentTimeMillis(), -1, true, true)); + // now add some files + v.add(new FileHandler("image.png", System.currentTimeMillis(), (int) (Math.random() * 100000), true, false)); + v.add(new FileHandler("image.gif", System.currentTimeMillis(), (int) (Math.random() * 100000), false, false)); + v.add(new FileHandler("image.jpeg", System.currentTimeMillis(), (int) (Math.random() * 100000), true, true)); + v.add(new FileHandler("cd.iso", System.currentTimeMillis(), (int) (Math.random() * 100000), true, false)); + v.add(new FileHandler("file.doc", System.currentTimeMillis(), (int) (Math.random() * 100000), false, true)); + v.add(new FileHandler("file.ppt", System.currentTimeMillis(), (int) (Math.random() * 100000), true, false)); + v.add(new FileHandler("file.txt", System.currentTimeMillis(), (int) (Math.random() * 100000), true, true)); + v.add(new FileHandler("dummy", System.currentTimeMillis(), (int) (Math.random() * 100000), true, false)); + return v; + } + + /** + * Send the command to the other end to set the current working directory to an absolute pathname + */ + public void setAbsoluteDir(String dir) { + } + + /** + * Send the command to the other end to set the current working directory to a relative to the current dir pathname + */ + public void setRelativeDir(String dir) { + try { + Thread.sleep(10000); + } catch (Exception e) { + } + } + + /** + * Send the command to the other end to set the current working directory up one level + */ + public void setUpDir() { + } + + /** + * Receives the known root pathes of the remote FS + */ + public String[] getRoots() { + return new String[]{"C:", "D:"}; + } + + /** + * Receives the pathname denoted by a root folder (for the special case of links...) + */ + public String getShortRootName(String rootFolder) { + return rootFolder; + } + + /** + * Called to initiatilize a file transfer + */ + public String initiateTransfer(String files[], boolean push) { + count = 0; + return null; + } + + /** + * Called in order to interrogate on the status of the current transfer + */ + public double getTransferPercent() { + count += 10; + return count; + } + } // end of class DummyRemoteSessionManager diff --git a/src/lia/util/net/copy/gui/EnhancedJPanel.java b/src/lia/util/net/copy/gui/EnhancedJPanel.java index d038da0..21efaac 100644 --- a/src/lia/util/net/copy/gui/EnhancedJPanel.java +++ b/src/lia/util/net/copy/gui/EnhancedJPanel.java @@ -3,45 +3,39 @@ */ package lia.util.net.copy.gui; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GradientPaint; -import java.awt.Graphics; -import java.awt.Graphics2D; - -import javax.swing.JPanel; +import javax.swing.*; +import java.awt.*; /** - * * @author Ciprian Dobre */ public class EnhancedJPanel extends JPanel { - private static final Color transparent = new Color(255, 255, 255); - -// private static final Color lightBlue = new Color(130, 200, 250); - private static final Color lightBlue = new Color(210, 237, 255); - - private static final Color selectedColor = new Color(184, 208, 224); - - public void paintComponent(Graphics g) { - - GradientPaint leftGradient; - GradientPaint rightGradient; - - Dimension d = getSize(); - - if (d == null) return; - - leftGradient = new GradientPaint(0, 0, selectedColor, - (int)d.getWidth()/2, 0, lightBlue); - - rightGradient = new GradientPaint((int)d.getWidth()/2, 0, lightBlue, (int)d.getWidth(), 0, transparent); - - Graphics2D g2 = (Graphics2D) g; - g2.setPaint(leftGradient); - g2.fillRect(0, 0, (int)d.getWidth()/2, (int)d.getHeight()); - g2.setPaint(rightGradient); - g2.fillRect((int)d.getWidth()/2, 0, (int)d.getWidth(), (int)d.getHeight()); - } + private static final Color transparent = new Color(255, 255, 255); + + // private static final Color lightBlue = new Color(130, 200, 250); + private static final Color lightBlue = new Color(210, 237, 255); + + private static final Color selectedColor = new Color(184, 208, 224); + + public void paintComponent(Graphics g) { + + GradientPaint leftGradient; + GradientPaint rightGradient; + + Dimension d = getSize(); + + if (d == null) return; + + leftGradient = new GradientPaint(0, 0, selectedColor, + (int) d.getWidth() / 2, 0, lightBlue); + + rightGradient = new GradientPaint((int) d.getWidth() / 2, 0, lightBlue, (int) d.getWidth(), 0, transparent); + + Graphics2D g2 = (Graphics2D) g; + g2.setPaint(leftGradient); + g2.fillRect(0, 0, (int) d.getWidth() / 2, (int) d.getHeight()); + g2.setPaint(rightGradient); + g2.fillRect((int) d.getWidth() / 2, 0, (int) d.getWidth(), (int) d.getHeight()); + } } diff --git a/src/lia/util/net/copy/gui/FDTPropsDialog.java b/src/lia/util/net/copy/gui/FDTPropsDialog.java index acf1bab..4b04b77 100644 --- a/src/lia/util/net/copy/gui/FDTPropsDialog.java +++ b/src/lia/util/net/copy/gui/FDTPropsDialog.java @@ -3,31 +3,29 @@ */ package lia.util.net.copy.gui; -import java.awt.BorderLayout; -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; import java.text.NumberFormat; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; - /** - * * @author Ciprian Dobre */ public class FDTPropsDialog extends JDialog implements KeyListener { - /** The possible values that can be set in this dialog.. */ - + private static final NumberFormat nf = NumberFormat.getInstance(); + + static { + nf.setMaximumFractionDigits(2); + } + + final JFrame parent; + final JDialog dialog; + public boolean bDialogOK = false; + /** + * The possible values that can be set in this dialog.. + */ + private int sockBufSize = -1; private int sockNum = 4; private long rateLimit = -1; @@ -37,7 +35,6 @@ public class FDTPropsDialog extends JDialog implements KeyListener { private boolean bComputeMD5 = false; private boolean bUseFixedBlocks = false; private double transferLimit = -1; - private JTextField textSockBufSize = new JTextField(); private JTextField textSockNum = new JTextField(); private JTextField textRateLimit = new JTextField(); @@ -47,46 +44,38 @@ public class FDTPropsDialog extends JDialog implements KeyListener { private JTextField textComputeMD5 = new JTextField(); private JTextField textTransferLimit = new JTextField(); - final JFrame parent; + public FDTPropsDialog(JFrame f) { + super(f, "Connection preferences", true); + + dialog = this; - public boolean bDialogOK = false; - - private static final NumberFormat nf = NumberFormat.getInstance(); - static { - nf.setMaximumFractionDigits(2); - } - - final JDialog dialog; - - public FDTPropsDialog(JFrame f) { - super(f, "Connection preferences", true); - - dialog = this; - final JPanel mainPanel = new EnhancedJPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - - try { loadPrefs(); } catch (Throwable t) { } - + + try { + loadPrefs(); + } catch (Throwable t) { + } + this.parent = f; final JPanel sockBufSizePanel = new JPanel(); sockBufSizePanel.setOpaque(false); sockBufSizePanel.setLayout(new BoxLayout(sockBufSizePanel, BoxLayout.X_AXIS)); mainPanel.add(sockBufSizePanel); sockBufSizePanel.add(new JLabel("SockBufSize: ")); - textSockBufSize.setText(""+sockBufSize); + textSockBufSize.setText("" + sockBufSize); textSockBufSize.addKeyListener(this); sockBufSizePanel.add(textSockBufSize); - + final JPanel sockNumPanel = new JPanel(); sockNumPanel.setOpaque(false); sockNumPanel.setLayout(new BoxLayout(sockNumPanel, BoxLayout.X_AXIS)); mainPanel.add(sockNumPanel); sockNumPanel.add(new JLabel("NoOfStreams: ")); - textSockNum.setText(""+sockNum); + textSockNum.setText("" + sockNum); textSockNum.addKeyListener(this); sockNumPanel.add(textSockNum); - + final JPanel transferLimitPanel = new JPanel(); transferLimitPanel.setOpaque(false); transferLimitPanel.setLayout(new BoxLayout(transferLimitPanel, BoxLayout.X_AXIS)); @@ -95,12 +84,12 @@ public FDTPropsDialog(JFrame f) { textTransferLimit.setText(nf.format(transferLimit)); textTransferLimit.addKeyListener(this); transferLimitPanel.add(textTransferLimit); - + final JPanel rateLimitPanel = new JPanel(); rateLimitPanel.setOpaque(false); rateLimitPanel.setLayout(new BoxLayout(rateLimitPanel, BoxLayout.X_AXIS)); rateLimitPanel.add(new JLabel("RateLimit: ")); - textRateLimit.setText(""+rateLimit); + textRateLimit.setText("" + rateLimit); textRateLimit.addKeyListener(this); rateLimitPanel.add(textRateLimit); @@ -108,7 +97,7 @@ public FDTPropsDialog(JFrame f) { readersCountPanel.setOpaque(false); readersCountPanel.setLayout(new BoxLayout(readersCountPanel, BoxLayout.X_AXIS)); readersCountPanel.add(new JLabel("ReadersCount:")); - textReadersCount.setText(""+readersCount); + textReadersCount.setText("" + readersCount); textReadersCount.addKeyListener(this); readersCountPanel.add(textReadersCount); @@ -116,7 +105,7 @@ public FDTPropsDialog(JFrame f) { writersCountPanel.setOpaque(false); writersCountPanel.setLayout(new BoxLayout(writersCountPanel, BoxLayout.X_AXIS)); writersCountPanel.add(new JLabel("WritersCount:")); - textWritersCount.setText(""+writersCount); + textWritersCount.setText("" + writersCount); textWritersCount.addKeyListener(this); writersCountPanel.add(textWritersCount); @@ -124,7 +113,7 @@ public FDTPropsDialog(JFrame f) { maxPartitionsPanel.setOpaque(false); maxPartitionsPanel.setLayout(new BoxLayout(maxPartitionsPanel, BoxLayout.X_AXIS)); maxPartitionsPanel.add(new JLabel("MaxPartitionsCount:")); - textMaxPartitionsCount.setText(""+maxPartitionsCount); + textMaxPartitionsCount.setText("" + maxPartitionsCount); textMaxPartitionsCount.addKeyListener(this); maxPartitionsPanel.add(textMaxPartitionsCount); @@ -132,7 +121,7 @@ public FDTPropsDialog(JFrame f) { computeMD5Panel.setOpaque(false); computeMD5Panel.setLayout(new BoxLayout(computeMD5Panel, BoxLayout.X_AXIS)); computeMD5Panel.add(new JLabel("ComputeMD5:")); - textComputeMD5.setText(""+bComputeMD5); + textComputeMD5.setText("" + bComputeMD5); textComputeMD5.addKeyListener(this); computeMD5Panel.add(textComputeMD5); @@ -142,7 +131,7 @@ public FDTPropsDialog(JFrame f) { mainPanel.add(advancedPanel); final JButton advanced = new JButton("Advanced options"); advancedPanel.add(advanced); - + final JPanel buttonPanel = new JPanel(); buttonPanel.setOpaque(false); buttonPanel.setLayout(new GridLayout(0, 2)); @@ -151,8 +140,8 @@ public FDTPropsDialog(JFrame f) { bOK.addKeyListener(this); bOK.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { - if (!checkParams()) - return; + if (!checkParams()) + return; bDialogOK = true; setVisible(false); } @@ -162,250 +151,257 @@ public void actionPerformed(ActionEvent arg0) { bCancel.addKeyListener(this); bCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { - bDialogOK = false; + bDialogOK = false; setVisible(false); } }); buttonPanel.add(bCancel); - + advanced.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (advanced.getText().equals("Advanced options")) { - advanced.setText("Basic options"); - mainPanel.remove(advancedPanel); - mainPanel.remove(buttonPanel); - mainPanel.add(rateLimitPanel); - mainPanel.add(readersCountPanel); - mainPanel.add(writersCountPanel); - mainPanel.add(maxPartitionsPanel); - mainPanel.add(computeMD5Panel); - mainPanel.add(advancedPanel); - mainPanel.add(buttonPanel); - dialog.pack(); - } else { - advanced.setText("Advanced options"); - mainPanel.remove(rateLimitPanel); - mainPanel.remove(readersCountPanel); - mainPanel.remove(writersCountPanel); - mainPanel.remove(maxPartitionsPanel); - mainPanel.remove(computeMD5Panel); - dialog.pack(); - } - } + public void actionPerformed(ActionEvent e) { + if (advanced.getText().equals("Advanced options")) { + advanced.setText("Basic options"); + mainPanel.remove(advancedPanel); + mainPanel.remove(buttonPanel); + mainPanel.add(rateLimitPanel); + mainPanel.add(readersCountPanel); + mainPanel.add(writersCountPanel); + mainPanel.add(maxPartitionsPanel); + mainPanel.add(computeMD5Panel); + mainPanel.add(advancedPanel); + mainPanel.add(buttonPanel); + dialog.pack(); + } else { + advanced.setText("Advanced options"); + mainPanel.remove(rateLimitPanel); + mainPanel.remove(readersCountPanel); + mainPanel.remove(writersCountPanel); + mainPanel.remove(maxPartitionsPanel); + mainPanel.remove(computeMD5Panel); + dialog.pack(); + } + } }); - + getContentPane().setLayout(new BorderLayout(2, 2)); getContentPane().add(mainPanel, BorderLayout.CENTER); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); setSize(330, 330); setLocationRelativeTo(parent); pack(); - } - - private final boolean checkParams() { - - int tmpSockBufSize = sockBufSize; - int tmpSockNum = sockNum; - long tmpRateLimit = rateLimit; - int tmpReadersCount = readersCount; - int tmpWritersCount = writersCount; - int tmpMaxPartitionsCount = maxPartitionsCount; - boolean tmpBComputeMD5 = bComputeMD5; - double tmpTransferLimit = transferLimit; - - String s = textSockBufSize.getText(); - if (s != null && s.length() != 0) { - try { - tmpSockBufSize = Integer.parseInt(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for SockBufSize"); - return false; - } - } - s = textSockNum.getText(); - if (s != null && s.length() != 0) { - try { - tmpSockNum = Integer.parseInt(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for NoOfStreams"); - return false; - } - } - s = textRateLimit.getText(); - if (s != null && s.length() != 0) { - try { - tmpRateLimit = Long.parseLong(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for RateLimit"); - return false; - } - } - s = textReadersCount.getText(); - if (s != null && s.length() != 0) { - try { - tmpReadersCount = Integer.parseInt(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for ReadersCount"); - return false; - } - } - s = textWritersCount.getText(); - if (s != null && s.length() != 0) { - try { - tmpWritersCount = Integer.parseInt(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for WritersCount"); - return false; - } - } - s = textMaxPartitionsCount.getText(); - if (s != null && s.length() != 0) { - try { - tmpMaxPartitionsCount = Integer.parseInt(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for MaxPartitionsCount"); - return false; - } - } - s = textComputeMD5.getText(); - if (s != null && s.length() != 0) { - try { - tmpBComputeMD5 = Boolean.valueOf(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must entera vald value for ComputeMD5"); - return false; - } - } - s = textTransferLimit.getText(); - if (s != null && s.length() != 0) { - try { - tmpTransferLimit = Double.valueOf(s); - } catch (Exception e) { - JOptionPane.showMessageDialog(parent, "You must enter a valid value for TransferLimit"); - return false; - } - } - - sockBufSize = tmpSockBufSize; - sockNum = tmpSockNum; - rateLimit = tmpRateLimit; - readersCount = tmpReadersCount; - writersCount = tmpWritersCount; - maxPartitionsCount = tmpMaxPartitionsCount; - bComputeMD5 = tmpBComputeMD5; - transferLimit = tmpTransferLimit; - savePrefs(); - return true; - } - - private final void loadPrefs() { - String s = PreferencesHandler.get("sockBufSize", ""+sockBufSize); - try { - sockBufSize = Integer.parseInt(s); - } catch (Exception e) { } - s = PreferencesHandler.get("sockNum", ""+sockNum); - try { - sockNum = Integer.parseInt(s); - } catch (Exception e) { } - s = PreferencesHandler.get("rateLimit", ""+rateLimit); - try { - rateLimit = Long.parseLong(s); - } catch (Exception e) { } - s = PreferencesHandler.get("readersCount", ""+readersCount); - try { - readersCount = Integer.parseInt(s); - } catch (Exception e) { } - s = PreferencesHandler.get("writersCount", ""+writersCount); - try { - writersCount = Integer.parseInt(s); - } catch (Exception e) { } - s = PreferencesHandler.get("maxPartitionsCount", ""+maxPartitionsCount); - try { - maxPartitionsCount = Integer.parseInt(s); - } catch (Exception e) { } - bComputeMD5 = PreferencesHandler.getBoolean("computeMD5", bComputeMD5); - s = PreferencesHandler.get("transferLimit", nf.format(transferLimit)); - try { - transferLimit = Double.parseDouble(s); - } catch (Exception e) { } - } - - private final void savePrefs() { - PreferencesHandler.put("sockBufSize", ""+sockBufSize); - PreferencesHandler.put("sockNum", ""+sockNum); - PreferencesHandler.put("rateLimit", ""+rateLimit); - PreferencesHandler.put("readersCount", ""+readersCount); - PreferencesHandler.put("writersCount", ""+writersCount); - PreferencesHandler.put("maxPartitionsCount", ""+maxPartitionsCount); - PreferencesHandler.putBoolean("computeMD5", bComputeMD5); - PreferencesHandler.put("transferLimit", nf.format(transferLimit)); - PreferencesHandler.save(); - } - - public void keyTyped(KeyEvent e) { - } - - public void keyPressed(KeyEvent e) { - } - - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - bDialogOK = false; + } + + private final boolean checkParams() { + + int tmpSockBufSize = sockBufSize; + int tmpSockNum = sockNum; + long tmpRateLimit = rateLimit; + int tmpReadersCount = readersCount; + int tmpWritersCount = writersCount; + int tmpMaxPartitionsCount = maxPartitionsCount; + boolean tmpBComputeMD5 = bComputeMD5; + double tmpTransferLimit = transferLimit; + + String s = textSockBufSize.getText(); + if (s != null && s.length() != 0) { + try { + tmpSockBufSize = Integer.parseInt(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for SockBufSize"); + return false; + } + } + s = textSockNum.getText(); + if (s != null && s.length() != 0) { + try { + tmpSockNum = Integer.parseInt(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for NoOfStreams"); + return false; + } + } + s = textRateLimit.getText(); + if (s != null && s.length() != 0) { + try { + tmpRateLimit = Long.parseLong(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for RateLimit"); + return false; + } + } + s = textReadersCount.getText(); + if (s != null && s.length() != 0) { + try { + tmpReadersCount = Integer.parseInt(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for ReadersCount"); + return false; + } + } + s = textWritersCount.getText(); + if (s != null && s.length() != 0) { + try { + tmpWritersCount = Integer.parseInt(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for WritersCount"); + return false; + } + } + s = textMaxPartitionsCount.getText(); + if (s != null && s.length() != 0) { + try { + tmpMaxPartitionsCount = Integer.parseInt(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for MaxPartitionsCount"); + return false; + } + } + s = textComputeMD5.getText(); + if (s != null && s.length() != 0) { + try { + tmpBComputeMD5 = Boolean.valueOf(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must entera vald value for ComputeMD5"); + return false; + } + } + s = textTransferLimit.getText(); + if (s != null && s.length() != 0) { + try { + tmpTransferLimit = Double.valueOf(s); + } catch (Exception e) { + JOptionPane.showMessageDialog(parent, "You must enter a valid value for TransferLimit"); + return false; + } + } + + sockBufSize = tmpSockBufSize; + sockNum = tmpSockNum; + rateLimit = tmpRateLimit; + readersCount = tmpReadersCount; + writersCount = tmpWritersCount; + maxPartitionsCount = tmpMaxPartitionsCount; + bComputeMD5 = tmpBComputeMD5; + transferLimit = tmpTransferLimit; + savePrefs(); + return true; + } + + private final void loadPrefs() { + String s = PreferencesHandler.get("sockBufSize", "" + sockBufSize); + try { + sockBufSize = Integer.parseInt(s); + } catch (Exception e) { + } + s = PreferencesHandler.get("sockNum", "" + sockNum); + try { + sockNum = Integer.parseInt(s); + } catch (Exception e) { + } + s = PreferencesHandler.get("rateLimit", "" + rateLimit); + try { + rateLimit = Long.parseLong(s); + } catch (Exception e) { + } + s = PreferencesHandler.get("readersCount", "" + readersCount); + try { + readersCount = Integer.parseInt(s); + } catch (Exception e) { + } + s = PreferencesHandler.get("writersCount", "" + writersCount); + try { + writersCount = Integer.parseInt(s); + } catch (Exception e) { + } + s = PreferencesHandler.get("maxPartitionsCount", "" + maxPartitionsCount); + try { + maxPartitionsCount = Integer.parseInt(s); + } catch (Exception e) { + } + bComputeMD5 = PreferencesHandler.getBoolean("computeMD5", bComputeMD5); + s = PreferencesHandler.get("transferLimit", nf.format(transferLimit)); + try { + transferLimit = Double.parseDouble(s); + } catch (Exception e) { + } + } + + private final void savePrefs() { + PreferencesHandler.put("sockBufSize", "" + sockBufSize); + PreferencesHandler.put("sockNum", "" + sockNum); + PreferencesHandler.put("rateLimit", "" + rateLimit); + PreferencesHandler.put("readersCount", "" + readersCount); + PreferencesHandler.put("writersCount", "" + writersCount); + PreferencesHandler.put("maxPartitionsCount", "" + maxPartitionsCount); + PreferencesHandler.putBoolean("computeMD5", bComputeMD5); + PreferencesHandler.put("transferLimit", nf.format(transferLimit)); + PreferencesHandler.save(); + } + + public void keyTyped(KeyEvent e) { + } + + public void keyPressed(KeyEvent e) { + } + + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + bDialogOK = false; setVisible(false); - return; - } - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - if (!checkParams()) - return; + return; + } + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (!checkParams()) + return; bDialogOK = true; setVisible(false); return; - } - } - - public void setVisible(boolean v) { - if (v) { - setLocationRelativeTo(parent); - pack(); - } - super.setVisible(v); - } - - public boolean isBComputeMD5() { - return bComputeMD5; - } - - public boolean isBUseFixedBlocks() { - return bUseFixedBlocks; - } - - public int getMaxPartitionsCount() { - return maxPartitionsCount; - } - - public long getRateLimit() { - return rateLimit; - } - - public int getReadersCount() { - return readersCount; - } - - public int getSockNum() { - return sockNum; - } - - public int getSockBufSize() { - return sockBufSize; - } - - public double getTransferLimit() { - return transferLimit; - } - - public int getWritersCount() { - return writersCount; - } - + } + } + + public void setVisible(boolean v) { + if (v) { + setLocationRelativeTo(parent); + pack(); + } + super.setVisible(v); + } + + public boolean isBComputeMD5() { + return bComputeMD5; + } + + public boolean isBUseFixedBlocks() { + return bUseFixedBlocks; + } + + public int getMaxPartitionsCount() { + return maxPartitionsCount; + } + + public long getRateLimit() { + return rateLimit; + } + + public int getReadersCount() { + return readersCount; + } + + public int getSockNum() { + return sockNum; + } + + public int getSockBufSize() { + return sockBufSize; + } + + public double getTransferLimit() { + return transferLimit; + } + + public int getWritersCount() { + return writersCount; + } + } // end of class FDTPropsDialog diff --git a/src/lia/util/net/copy/gui/FolderFrame.java b/src/lia/util/net/copy/gui/FolderFrame.java index 77e779a..b577c25 100644 --- a/src/lia/util/net/copy/gui/FolderFrame.java +++ b/src/lia/util/net/copy/gui/FolderFrame.java @@ -3,387 +3,374 @@ */ package lia.util.net.copy.gui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GridLayout; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; +import lia.util.net.copy.gui.session.LocalSession; +import lia.util.net.copy.gui.session.RemoteSession; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; import java.net.URL; import java.util.Enumeration; import java.util.logging.LogManager; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; - -import lia.util.net.copy.gui.session.LocalSession; -import lia.util.net.copy.gui.session.RemoteSession; - /** - * The Panel showing files and folders + * The Panel showing files and folders * It is dirrectly linked to a Session (can be local or remote) + * * @author Ciprian Dobre */ public class FolderFrame extends JFrame implements ActionListener, FocusListener { - public FolderTable local; - public FolderTable remote; - - public JMenu localMenu; - public JMenu remoteMenu; - protected JMenuItem aboutItem; - protected JMenuItem helpItem; - protected JMenuItem connectItem; - protected JMenuItem propsItem; - - public JMenu command; - - protected StatusBar statusBar; - private AboutDialog about; - private HelpDialog help; - private FDTPropsDialog props; - - ConnectDialog connect; - RemoteSessionManager manager; - - private JButton copyLeft; - private JButton copyRight; - - public FolderFrame() { - super("Fast Data Transfer"); - - props = new FDTPropsDialog(this); - propsItem = new JMenuItem("Preferences"); - propsItem.setIcon(getPrefsIcon()); - propsItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - props.setVisible(true); - props.toFront(); - } - }); - JPanel panel = new JPanel(); - this.manager = new RemoteSessionManager(props, panel); - - connect = new ConnectDialog(this); - manager.initiated = true; - - panel.setOpaque(false); - panel.setLayout(new BorderLayout()); - JPanel pp = new JPanel(); - JScrollPane p1 = new JScrollPane(pp); - statusBar = new StatusBar(p1); - pp.setOpaque(true); - pp.setBackground(Color.white); - pp.setLayout(new BorderLayout()); - pp.add(statusBar, BorderLayout.CENTER); - p1.setOpaque(false); - p1.setMinimumSize(new Dimension(5, 20)); - p1.setPreferredSize(new Dimension(5, 20)); - - CustomPrintStream out = new CustomPrintStream(statusBar, System.out, "#000000"); - System.setOut(out); - CustomPrintStream err = new CustomPrintStream(statusBar, System.err, "#ff0000"); - System.setErr(err); - - CustomLogHandler logHandler = new CustomLogHandler(statusBar); - for (Enumeration en = LogManager.getLogManager().getLoggerNames(); en.hasMoreElements(); ) { - String logName = en.nextElement(); - LogManager.getLogManager().getLogger(logName).addHandler(logHandler); - } - - JPanel p = new JPanel(); - p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); - - LocalSession localSession = new LocalSession(); - local = new FolderTable(localSession, manager, statusBar, this); - JPanel left = createPanel(local); - localMenu = new JMenu("Local"); - localMenu.add(local.visibleColumns); - command = local.command; - command.add(propsItem, 1); - - final RemoteSession session = new RemoteSession(manager); - remote = new FolderTable(session, manager, statusBar, this); - JPanel right = createPanel(remote); - remoteMenu = new JMenu("Remote"); - remoteMenu.add(remote.visibleColumns); - connectItem = new JMenuItem("Connect"); - connectItem.setIcon(getConnIcon()); - connectItem.addActionListener(this); - remoteMenu.add(connectItem); - manager.setCorrespondingPanel(local, remote); - - JPanel middle = new JPanel(); - middle.setLayout(new GridLayout(0, 1)); - middle.setOpaque(false); - copyLeft = new JButton(getLeftIcon()); - copyLeft.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - Runnable r = new Runnable() { - public void run() { - remote.copy(); - } - }; - local.exec.execute(r); - } - }); - copyLeft.setFocusable(false); - copyLeft.setMaximumSize(new Dimension(24, 24)); - copyLeft.setPreferredSize(new Dimension(24, 24)); - copyLeft.setToolTipText("Copy"); - copyLeft.setEnabled(false); - middle.add(copyLeft); - - copyRight = new JButton(getRightIcon()); - copyRight.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - Runnable r = new Runnable() { - public void run() { - local.copy(); - } - }; - local.exec.execute(r); - } - }); - copyRight.setFocusable(false); - copyRight.setMaximumSize(new Dimension(24, 24)); - copyRight.setPreferredSize(new Dimension(24, 24)); - copyRight.setToolTipText("Copy"); - copyRight.setEnabled(false); - middle.add(copyRight); - - middle.setMaximumSize(new Dimension(24, 72)); - middle.setPreferredSize(new Dimension(24, 72)); - - JPanel pp1 = new JPanel(); - pp1.setOpaque(false); - pp1.setLayout(new BoxLayout(pp1, BoxLayout.X_AXIS)); - pp1.setMaximumSize(new Dimension(24, 72)); - pp1.setPreferredSize(new Dimension(24, 72)); - - pp1.add(middle); - - p.add(left); - p.add(pp1); - p.add(right); - - manager.setSessions(localSession, session); - - JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, p, p1); - splitPane.setOneTouchExpandable(true); - splitPane.setContinuousLayout(true); - splitPane.setResizeWeight(1.0); - panel.add(splitPane, BorderLayout.CENTER); - - Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); - setSize((int)dim.getWidth(), (int)dim.getHeight() - 30); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - getContentPane().setLayout(new BorderLayout()); - getContentPane().add(panel, BorderLayout.CENTER); - JMenuBar menu = new JMenuBar(); - menu.add(localMenu); - menu.add(command); - menu.add(remoteMenu); - setJMenuBar(menu); - setIconImage(getFDTIcon().getImage()); - about = new AboutDialog(this); - JMenu h = new JMenu("Help"); - aboutItem = new JMenuItem("About"); - aboutItem.addActionListener(new ActionListener(){ - public void actionPerformed(ActionEvent e) { - about.setVisible(true); - } - }); - h.add(aboutItem); - help = new HelpDialog(this); - helpItem = new JMenuItem("Key mappings"); - helpItem.addActionListener(new ActionListener(){ - public void actionPerformed(ActionEvent e) { - help.setVisible(true); - } - }); - h.add(helpItem); - menu.add(h); - local.getTable().requestFocusInWindow(); - } - - ImageIcon prefsIcon; - private Icon getPrefsIcon() { - if (prefsIcon != null) return prefsIcon; - try { - URL r = getClass().getResource("icons/preferences.png"); - prefsIcon = new ImageIcon(r); - } catch (Exception e) { } - return prefsIcon; - } - ImageIcon connIcon; - private Icon getConnIcon() { - if (connIcon != null) return connIcon; - try { - URL r = getClass().getResource("icons/connect.gif"); - connIcon = new ImageIcon(r); - } catch (Exception e) { } - return connIcon; - } - - private final JPanel createPanel(FolderTable table) { - JPanel left = new JPanel(); - left.setOpaque(false); - left.setLayout(new BorderLayout()); - JPanel p = new JPanel(); - p.setOpaque(false); - p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); - p.add(table.roots); - p.add(Box.createHorizontalStrut(5)); - p.add(table.refreshDir); - p.add(Box.createHorizontalStrut(5)); - p.add(table.parentDir); - p.add(Box.createHorizontalStrut(5)); - p.add(table.rootDir); - p.add(Box.createHorizontalStrut(5)); - p.add(table.homeDir); - p.add(Box.createHorizontalStrut(5)); - p.add(table.openDir); - p.add(Box.createHorizontalStrut(5)); - p.add(table.separator); - p.add(Box.createHorizontalStrut(5)); - p.add(table.mkdir); - p.add(Box.createHorizontalStrut(5)); - p.add(table.remove); - p.add(Box.createHorizontalStrut(5)); - p.add(table.freeSpace); - - p.add(Box.createHorizontalGlue()); - left.add(p, BorderLayout.NORTH); - left.add(table, BorderLayout.CENTER); - left.setBorder(BorderFactory.createLoweredBevelBorder()); - return left; - } - - private ImageIcon fdtIcon; - private ImageIcon getFDTIcon() { - if (fdtIcon != null) return fdtIcon; - try { - URL url = getClass().getResource("icons/fdt.png"); - fdtIcon = new ImageIcon(url); - } catch (Exception e) { } - return fdtIcon; - } - - public static void main(String args[]) { - FolderFrame panel = new FolderFrame(); - panel.setVisible(true); - panel.toFront(); - + public FolderTable local; + public FolderTable remote; + + public JMenu localMenu; + public JMenu remoteMenu; + public JMenu command; + protected JMenuItem aboutItem; + protected JMenuItem helpItem; + protected JMenuItem connectItem; + protected JMenuItem propsItem; + protected StatusBar statusBar; + ConnectDialog connect; + RemoteSessionManager manager; + ImageIcon prefsIcon; + ImageIcon connIcon; + ImageIcon leftIcon; + ImageIcon rightIcon; + ImageIcon mkdirIcon; + ImageIcon removeIcon; + ImageIcon indexIcon; + private AboutDialog about; + private HelpDialog help; + private FDTPropsDialog props; + private JButton copyLeft; + private JButton copyRight; + private ImageIcon fdtIcon; + + public FolderFrame() { + super("Fast Data Transfer"); + + props = new FDTPropsDialog(this); + propsItem = new JMenuItem("Preferences"); + propsItem.setIcon(getPrefsIcon()); + propsItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + props.setVisible(true); + props.toFront(); + } + }); + JPanel panel = new JPanel(); + this.manager = new RemoteSessionManager(props, panel); + + connect = new ConnectDialog(this); + manager.initiated = true; + + panel.setOpaque(false); + panel.setLayout(new BorderLayout()); + JPanel pp = new JPanel(); + JScrollPane p1 = new JScrollPane(pp); + statusBar = new StatusBar(p1); + pp.setOpaque(true); + pp.setBackground(Color.white); + pp.setLayout(new BorderLayout()); + pp.add(statusBar, BorderLayout.CENTER); + p1.setOpaque(false); + p1.setMinimumSize(new Dimension(5, 20)); + p1.setPreferredSize(new Dimension(5, 20)); + + CustomPrintStream out = new CustomPrintStream(statusBar, System.out, "#000000"); + System.setOut(out); + CustomPrintStream err = new CustomPrintStream(statusBar, System.err, "#ff0000"); + System.setErr(err); + + CustomLogHandler logHandler = new CustomLogHandler(statusBar); + for (Enumeration en = LogManager.getLogManager().getLoggerNames(); en.hasMoreElements(); ) { + String logName = en.nextElement(); + LogManager.getLogManager().getLogger(logName).addHandler(logHandler); + } + + JPanel p = new JPanel(); + p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); + + LocalSession localSession = new LocalSession(); + local = new FolderTable(localSession, manager, statusBar, this); + JPanel left = createPanel(local); + localMenu = new JMenu("Local"); + localMenu.add(local.visibleColumns); + command = local.command; + command.add(propsItem, 1); + + final RemoteSession session = new RemoteSession(manager); + remote = new FolderTable(session, manager, statusBar, this); + JPanel right = createPanel(remote); + remoteMenu = new JMenu("Remote"); + remoteMenu.add(remote.visibleColumns); + connectItem = new JMenuItem("Connect"); + connectItem.setIcon(getConnIcon()); + connectItem.addActionListener(this); + remoteMenu.add(connectItem); + manager.setCorrespondingPanel(local, remote); + + JPanel middle = new JPanel(); + middle.setLayout(new GridLayout(0, 1)); + middle.setOpaque(false); + copyLeft = new JButton(getLeftIcon()); + copyLeft.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + remote.copy(); + } + }; + local.exec.execute(r); + } + }); + copyLeft.setFocusable(false); + copyLeft.setMaximumSize(new Dimension(24, 24)); + copyLeft.setPreferredSize(new Dimension(24, 24)); + copyLeft.setToolTipText("Copy"); + copyLeft.setEnabled(false); + middle.add(copyLeft); + + copyRight = new JButton(getRightIcon()); + copyRight.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + local.copy(); + } + }; + local.exec.execute(r); + } + }); + copyRight.setFocusable(false); + copyRight.setMaximumSize(new Dimension(24, 24)); + copyRight.setPreferredSize(new Dimension(24, 24)); + copyRight.setToolTipText("Copy"); + copyRight.setEnabled(false); + middle.add(copyRight); + + middle.setMaximumSize(new Dimension(24, 72)); + middle.setPreferredSize(new Dimension(24, 72)); + + JPanel pp1 = new JPanel(); + pp1.setOpaque(false); + pp1.setLayout(new BoxLayout(pp1, BoxLayout.X_AXIS)); + pp1.setMaximumSize(new Dimension(24, 72)); + pp1.setPreferredSize(new Dimension(24, 72)); + + pp1.add(middle); + + p.add(left); + p.add(pp1); + p.add(right); + + manager.setSessions(localSession, session); + + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, p, p1); + splitPane.setOneTouchExpandable(true); + splitPane.setContinuousLayout(true); + splitPane.setResizeWeight(1.0); + panel.add(splitPane, BorderLayout.CENTER); + + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + setSize((int) dim.getWidth(), (int) dim.getHeight() - 30); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(panel, BorderLayout.CENTER); + JMenuBar menu = new JMenuBar(); + menu.add(localMenu); + menu.add(command); + menu.add(remoteMenu); + setJMenuBar(menu); + setIconImage(getFDTIcon().getImage()); + about = new AboutDialog(this); + JMenu h = new JMenu("Help"); + aboutItem = new JMenuItem("About"); + aboutItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + about.setVisible(true); + } + }); + h.add(aboutItem); + help = new HelpDialog(this); + helpItem = new JMenuItem("Key mappings"); + helpItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + help.setVisible(true); + } + }); + h.add(helpItem); + menu.add(h); + local.getTable().requestFocusInWindow(); + } + + public static void main(String args[]) { + FolderFrame panel = new FolderFrame(); + panel.setVisible(true); + panel.toFront(); + // for (int i=0; i<10000; i++) { // panel.statusBar.addText("ttttttr\n"); // try { Thread.sleep(100); } catch (Throwable t) { } // } - } - - public void actionPerformed(ActionEvent e) { // connect called... - connect.setVisible(true); - connect.toFront(); - if (connect.bDialogOK) { - manager.initiated = false; - int port = 54321; - try { - port = Integer.valueOf(connect.sPort); - } catch (Throwable t) { } - manager.connect(connect.sHost, connect.sUser, port); - } else { - manager.initiated = true; - } - } - - public void focusGained(FocusEvent e) { - if (local.getTable().hasFocus()) { - local.showSelection(); - remote.hideSelection(); - copyRight.setEnabled(true); - copyLeft.setEnabled(false); - local.scroll.getViewport().setBackground(FolderTable.cm); - remote.scroll.getViewport().setBackground(FolderTable.cg); - } else if (remote.getTable().hasFocus()){ - remote.showSelection(); - local.hideSelection(); - copyRight.setEnabled(false); - copyLeft.setEnabled(true); - local.scroll.getViewport().setBackground(FolderTable.cg); - remote.scroll.getViewport().setBackground(FolderTable.cm); - } - } - - public void focusLost(FocusEvent e) { - copyLeft.setEnabled(false); - copyRight.setEnabled(false); + } + + private Icon getPrefsIcon() { + if (prefsIcon != null) return prefsIcon; + try { + URL r = getClass().getResource("icons/preferences.png"); + prefsIcon = new ImageIcon(r); + } catch (Exception e) { + } + return prefsIcon; + } + + private Icon getConnIcon() { + if (connIcon != null) return connIcon; + try { + URL r = getClass().getResource("icons/connect.gif"); + connIcon = new ImageIcon(r); + } catch (Exception e) { + } + return connIcon; + } + + private final JPanel createPanel(FolderTable table) { + JPanel left = new JPanel(); + left.setOpaque(false); + left.setLayout(new BorderLayout()); + JPanel p = new JPanel(); + p.setOpaque(false); + p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); + p.add(table.roots); + p.add(Box.createHorizontalStrut(5)); + p.add(table.refreshDir); + p.add(Box.createHorizontalStrut(5)); + p.add(table.parentDir); + p.add(Box.createHorizontalStrut(5)); + p.add(table.rootDir); + p.add(Box.createHorizontalStrut(5)); + p.add(table.homeDir); + p.add(Box.createHorizontalStrut(5)); + p.add(table.openDir); + p.add(Box.createHorizontalStrut(5)); + p.add(table.separator); + p.add(Box.createHorizontalStrut(5)); + p.add(table.mkdir); + p.add(Box.createHorizontalStrut(5)); + p.add(table.remove); + p.add(Box.createHorizontalStrut(5)); + p.add(table.freeSpace); + + p.add(Box.createHorizontalGlue()); + left.add(p, BorderLayout.NORTH); + left.add(table, BorderLayout.CENTER); + left.setBorder(BorderFactory.createLoweredBevelBorder()); + return left; + } + + private ImageIcon getFDTIcon() { + if (fdtIcon != null) return fdtIcon; + try { + URL url = getClass().getResource("icons/fdt.png"); + fdtIcon = new ImageIcon(url); + } catch (Exception e) { + } + return fdtIcon; + } + + public void actionPerformed(ActionEvent e) { // connect called... + connect.setVisible(true); + connect.toFront(); + if (connect.bDialogOK) { + manager.initiated = false; + int port = 54321; + try { + port = Integer.valueOf(connect.sPort); + } catch (Throwable t) { + } + manager.connect(connect.sHost, connect.sUser, port); + } else { + manager.initiated = true; + } + } + + public void focusGained(FocusEvent e) { + if (local.getTable().hasFocus()) { + local.showSelection(); + remote.hideSelection(); + copyRight.setEnabled(true); + copyLeft.setEnabled(false); + local.scroll.getViewport().setBackground(FolderTable.cm); + remote.scroll.getViewport().setBackground(FolderTable.cg); + } else if (remote.getTable().hasFocus()) { + remote.showSelection(); + local.hideSelection(); + copyRight.setEnabled(false); + copyLeft.setEnabled(true); + local.scroll.getViewport().setBackground(FolderTable.cg); + remote.scroll.getViewport().setBackground(FolderTable.cm); + } + } + + public void focusLost(FocusEvent e) { + copyLeft.setEnabled(false); + copyRight.setEnabled(false); // local.scroll.getViewport().setBackground(FolderTable.cg); // remote.scroll.getViewport().setBackground(FolderTable.cg); - } - - ImageIcon leftIcon; - private Icon getLeftIcon() { - if (leftIcon != null) return leftIcon; - try { - URL r = getClass().getResource("icons/left.png"); - leftIcon = new ImageIcon(r); - } catch (Exception e) { } - return leftIcon; - } - - ImageIcon rightIcon; - private Icon getRightIcon() { - if (rightIcon != null) return rightIcon; - try { - URL r = getClass().getResource("icons/right.png"); - rightIcon = new ImageIcon(r); - } catch (Exception e) { } - return rightIcon; - } - - ImageIcon mkdirIcon; - private Icon getMkDirIcon() { - if (mkdirIcon != null) return mkdirIcon; - try { - URL r = getClass().getResource("icons/mkdir.png"); - mkdirIcon = new ImageIcon(r); - } catch (Exception e) { } - return mkdirIcon; - } - - ImageIcon removeIcon; - private Icon getRemoveIcon() { - if (removeIcon != null) return removeIcon; - try { - URL r = getClass().getResource("icons/delete.png"); - removeIcon = new ImageIcon(r); - } catch (Exception e) { } - return removeIcon; - } - - ImageIcon indexIcon; - private Icon getIndexIcon() { - if (indexIcon != null) return indexIcon; - try { - URL r = getClass().getResource("icons/index.png"); - indexIcon = new ImageIcon(r); - } catch (Exception e) { } - return indexIcon; - } - + } + + private Icon getLeftIcon() { + if (leftIcon != null) return leftIcon; + try { + URL r = getClass().getResource("icons/left.png"); + leftIcon = new ImageIcon(r); + } catch (Exception e) { + } + return leftIcon; + } + + private Icon getRightIcon() { + if (rightIcon != null) return rightIcon; + try { + URL r = getClass().getResource("icons/right.png"); + rightIcon = new ImageIcon(r); + } catch (Exception e) { + } + return rightIcon; + } + + private Icon getMkDirIcon() { + if (mkdirIcon != null) return mkdirIcon; + try { + URL r = getClass().getResource("icons/mkdir.png"); + mkdirIcon = new ImageIcon(r); + } catch (Exception e) { + } + return mkdirIcon; + } + + private Icon getRemoveIcon() { + if (removeIcon != null) return removeIcon; + try { + URL r = getClass().getResource("icons/delete.png"); + removeIcon = new ImageIcon(r); + } catch (Exception e) { + } + return removeIcon; + } + + private Icon getIndexIcon() { + if (indexIcon != null) return indexIcon; + try { + URL r = getClass().getResource("icons/index.png"); + indexIcon = new ImageIcon(r); + } catch (Exception e) { + } + return indexIcon; + } + } diff --git a/src/lia/util/net/copy/gui/FolderTable.java b/src/lia/util/net/copy/gui/FolderTable.java index f53d7fb..6e545e8 100644 --- a/src/lia/util/net/copy/gui/FolderTable.java +++ b/src/lia/util/net/copy/gui/FolderTable.java @@ -3,1262 +3,1167 @@ */ package lia.util.net.copy.gui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.GridLayout; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; +import lia.util.net.copy.gui.session.LocalSession; +import lia.util.net.copy.gui.session.Session; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.table.*; +import java.awt.*; +import java.awt.event.*; import java.net.URL; import java.text.NumberFormat; import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Locale; -import java.util.Vector; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JMenu; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.JViewport; -import javax.swing.ListSelectionModel; -import javax.swing.border.Border; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.JTableHeader; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; - -import lia.util.net.copy.gui.session.LocalSession; -import lia.util.net.copy.gui.session.Session; - /** * Represents the folder data into a JTable + * * @author Ciprian Dobre */ public class FolderTable extends JPanel { - private JTable table; - - private JPopupMenu popup; - - private final static String colNames[] = new String[] { "Icon", "Name", "Modified", "Size", "##" }; - - private boolean showIcon = true; - private boolean showLength = true; - private boolean showModif = true; - private boolean showAttrib = true; - - private final Vector columns = new Vector(); - - private final String pack = "/"+getClass().getPackage().getName().replace(".", "/"); - - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy HH:mm:ss"); - private static final NumberFormat nf = NumberFormat.getInstance(Locale.US); - static { - nf.setMaximumFractionDigits(2); - } - - private String sortColumn = "Name"; - private boolean sortAsc = true; - - public JComboBox roots; - public JLabel freeSpace; - public JLabel parentDir; - public JLabel rootDir; - public JLabel homeDir; - public JLabel refreshDir; - public JLabel openDir; - - public JLabel separator; - public JLabel mkdir; - public JLabel remove; - - public JMenu visibleColumns; - private JMenuItem showIconItem; - private JMenuItem showLengthItem; - private JMenuItem showModifItem; - private JMenuItem showAttribItem; - - public JMenu command; - private JMenuItem copy; - private JMenuItem exit; - - private final StatusBar statusBar; - - private JPanel connectPanel; - private JPanel tablePanel; - - final static ExecutorService exec = Executors.newCachedThreadPool(); - - public static TransferMonitor transferMonitor = null; - private boolean showSelection = true; - - public JScrollPane scroll; - - private String currentDir = null; - - private String lastDir = null; - - private String selectedDir = null; - - private final class FileHandler { - private final String name; - private final Icon icon; - private final long modif; - private final long size; - private final String attrib; - private final boolean isDir; - public FileHandler(String name, Icon icon, long modif, long size, String attrib, boolean isDir) { - this.name = name; - this.icon = icon; - this.modif = modif; - this.size = size; - this.attrib = attrib; - this.isDir = isDir; - } - public final String getName() { - if (name == null) return " "; - return name; - } - public final Icon getIcon() { - return icon; - } - public final String getModif() { - if (modif <= 0) return " "; - return " "+dateFormat.format(new Date(modif)); - } - public final long getModifL() { - return modif; - } - public final String getSize() { - if (size <= 0) return " "; - double s = size; - if (s > 1024) s /= 1024; - else return " "+nf.format(s)+" B"; - if (s > 1024) s /= 1024; - else return " "+nf.format(s)+" KB"; - if (s > 1024) s /= 1024; - else return " "+nf.format(s)+" MB"; - if (s > 1024) s /= 1024; - else return " "+nf.format(s)+" GB"; - return " "+nf.format(s)+" PB"; - } - public final long getSizeL() { - return size; - } - public final String getAttrib() { - if (attrib == null) return " "; - return " "+attrib; - } - public final boolean isDir() { - return isDir; - } - } - - private final Vector rows = new Vector(); - - public Session session; - - private final MyTableHeaderRenderer headerRenderer = new MyTableHeaderRenderer(); - - private final MyTableCellRenderer cellRenderer = new MyTableCellRenderer(); - - private final RemoteSessionManager manager; - - final FolderTable ft; - - long lastTimeKeyPressed = System.currentTimeMillis(); - String currentKeys = ""; - - private FolderFrame frame; - private long lastClick = 0L; - private int lastRow = -1; - - public FolderTable(final Session session, final RemoteSessionManager manager, final StatusBar statusBar, final FolderFrame focusListener) { - super(); - ft = this; - this.frame = focusListener; - this.statusBar = statusBar; - this.manager = manager; - this.session = session; - - popup = createPopup(); - - tablePanel = new JPanel(); - tablePanel.setLayout(new BorderLayout()); - tablePanel.setOpaque(false); - - setLayout(new BorderLayout()); - MyTableModel model = new MyTableModel(); - table = new JTable(model); - table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS); - table.setIntercellSpacing(new Dimension(0, 0)); - JTableHeader header = table.getTableHeader(); - header.addMouseListener(new ColumnHeaderListener()); - MouseListener mouseListener = new MouseAdapter(){ - public void mouseClicked(MouseEvent e){ - if (((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)){ - table.requestFocusInWindow(); - if (!isDoubleClick(e)) return; - int row = table.getSelectedRow(); - if (row < 0 || row >= rows.size()) return; - // get the corresponding row in the table - final FileHandler fh = rows.get(row); - if (fh.getSizeL() > 0) // clicked on a normal file - return; - Runnable r = new Runnable() { - public void run() { - ft.setEnabled(false); + public static final Color cg = new Color(217, 217, 217); + public static final Color cm = (new JPanel()).getBackground(); + final static ExecutorService exec = Executors.newCachedThreadPool(); + private final static String colNames[] = new String[]{"Icon", "Name", "Modified", "Size", "##"}; + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy HH:mm:ss"); + private static final NumberFormat nf = NumberFormat.getInstance(Locale.US); + private static final Color c1 = new Color(128, 179, 230); + private static final Color c2 = new Color(220, 234, 248); + private static final Color c22 = new Color(226, 241, 255); + private static final Color c3 = new Color(200, 200, 200); + private static final Color c4 = new Color(245, 255, 255); + private static final Color c44 = new Color(255, 255, 255); + public static TransferMonitor transferMonitor = null; + + static { + nf.setMaximumFractionDigits(2); + } + + final FolderTable ft; + private final Vector columns = new Vector(); + private final String pack = "/" + getClass().getPackage().getName().replace(".", "/"); + private final StatusBar statusBar; + private final Vector rows = new Vector(); + private final MyTableHeaderRenderer headerRenderer = new MyTableHeaderRenderer(); + private final MyTableCellRenderer cellRenderer = new MyTableCellRenderer(); + private final RemoteSessionManager manager; + public JComboBox roots; + public JLabel freeSpace; + public JLabel parentDir; + public JLabel rootDir; + public JLabel homeDir; + public JLabel refreshDir; + public JLabel openDir; + public JLabel separator; + public JLabel mkdir; + public JLabel remove; + public JMenu visibleColumns; + public JMenu command; + public JScrollPane scroll; + public Session session; + long lastTimeKeyPressed = System.currentTimeMillis(); + String currentKeys = ""; + ImageIcon upDirIcon; + ImageIcon rootIcon; + ImageIcon copyIcon; + ImageIcon exitIcon; + ImageIcon openDirIcon; + ImageIcon refreshIcon; + ImageIcon openHomeIcon; + ImageIcon selectIcon; + ImageIcon sortAIcon; + ImageIcon sortDIcon; + ImageIcon mkdirIcon; + ImageIcon removeIcon; + ImageIcon connIcon; + ImageIcon verticalIcon; + private JTable table; + private JPopupMenu popup; + private boolean showIcon = true; + private boolean showLength = true; + private boolean showModif = true; + private boolean showAttrib = true; + private String sortColumn = "Name"; + private boolean sortAsc = true; + private JMenuItem showIconItem; + private JMenuItem showLengthItem; + private JMenuItem showModifItem; + private JMenuItem showAttribItem; + private JMenuItem copy; + private JMenuItem exit; + private JPanel connectPanel; + private JPanel tablePanel; + private boolean showSelection = true; + private String currentDir = null; + private String lastDir = null; + private String selectedDir = null; + private FolderFrame frame; + private long lastClick = 0L; + private int lastRow = -1; + + public FolderTable(final Session session, final RemoteSessionManager manager, final StatusBar statusBar, final FolderFrame focusListener) { + super(); + ft = this; + this.frame = focusListener; + this.statusBar = statusBar; + this.manager = manager; + this.session = session; + + popup = createPopup(); + + tablePanel = new JPanel(); + tablePanel.setLayout(new BorderLayout()); + tablePanel.setOpaque(false); + + setLayout(new BorderLayout()); + MyTableModel model = new MyTableModel(); + table = new JTable(model); + table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS); + table.setIntercellSpacing(new Dimension(0, 0)); + JTableHeader header = table.getTableHeader(); + header.addMouseListener(new ColumnHeaderListener()); + MouseListener mouseListener = new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)) { + table.requestFocusInWindow(); + if (!isDoubleClick(e)) return; + int row = table.getSelectedRow(); + if (row < 0 || row >= rows.size()) return; + // get the corresponding row in the table + final FileHandler fh = rows.get(row); + if (fh.getSizeL() > 0) // clicked on a normal file + return; + Runnable r = new Runnable() { + public void run() { + ft.setEnabled(false); // removeCurrentDir(); - try { - if (fh.getName().equals("..")) { - lastDir = session.getWorkingDir(); - if (lastDir.contains(session.getFileSeparator())) - lastDir = lastDir.substring(lastDir.lastIndexOf(session.getFileSeparator())+1); - session.setUpDir(); - } else - session.setRelativeDir(fh.getName()); - } catch (Throwable t) { - // unhandled exception - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - e.consume(); - return; - } - if (((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0)) { - table.requestFocusInWindow(); - popup.show(e.getComponent(), e.getX(), e.getY()); - table.requestFocusInWindow(); - return; - } - } - }; - table.addMouseListener(mouseListener); - table.addFocusListener(focusListener); - table.addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent e) { - int key = e.getKeyCode(); - if (key == KeyEvent.VK_ENTER) { - if (table.getSelectedRowCount() > 1) { - setStatus("Unable to cd... more than one files selected"); - return; - } - int row = table.getSelectedRow(); - if (row < 0 || row >= rows.size()) return; - // get the corresponding row in the table - final FileHandler fh = rows.get(row); - if (fh.getSizeL() > 0) { // clicked on a normal file - setStatus("Unable to cd... normal file selected"); - return; - } - Runnable r = new Runnable() { - public void run() { - ft.setEnabled(false); + try { + if (fh.getName().equals("..")) { + lastDir = session.getWorkingDir(); + if (lastDir.contains(session.getFileSeparator())) + lastDir = lastDir.substring(lastDir.lastIndexOf(session.getFileSeparator()) + 1); + session.setUpDir(); + } else + session.setRelativeDir(fh.getName()); + } catch (Throwable t) { + // unhandled exception + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + e.consume(); + return; + } + if (((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0)) { + table.requestFocusInWindow(); + popup.show(e.getComponent(), e.getX(), e.getY()); + table.requestFocusInWindow(); + return; + } + } + }; + table.addMouseListener(mouseListener); + table.addFocusListener(focusListener); + table.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + int key = e.getKeyCode(); + if (key == KeyEvent.VK_ENTER) { + if (table.getSelectedRowCount() > 1) { + setStatus("Unable to cd... more than one files selected"); + return; + } + int row = table.getSelectedRow(); + if (row < 0 || row >= rows.size()) return; + // get the corresponding row in the table + final FileHandler fh = rows.get(row); + if (fh.getSizeL() > 0) { // clicked on a normal file + setStatus("Unable to cd... normal file selected"); + return; + } + Runnable r = new Runnable() { + public void run() { + ft.setEnabled(false); // removeCurrentDir(); - if (fh.getName().equals("..")) { - lastDir = session.getWorkingDir(); - if (lastDir.contains(session.getFileSeparator())) - lastDir = lastDir.substring(lastDir.lastIndexOf(session.getFileSeparator())+1); - try { - session.setUpDir(); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - } else { - try { - session.setRelativeDir(fh.getName()); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - e.consume(); - return; - } - if (key == KeyEvent.VK_F5) { - Runnable r = new Runnable() { - public void run() { - copy(); - } - }; - exec.execute(r); - e.consume(); - return; - } - if (key == KeyEvent.VK_F7) { - Runnable r = new Runnable() { - public void run() { - makeDir(); - } - }; - exec.execute(r); - e.consume(); - return; - } - if (key == KeyEvent.VK_F8 || key == KeyEvent.VK_DELETE) { - Runnable r = new Runnable() { - public void run() { - removeFiles(); - } - }; - exec.execute(r); - e.consume(); - return; - } - if (key == KeyEvent.VK_F10) { - System.exit(0); - } - if (key == KeyEvent.VK_TAB) { - if (command == null) { // remote session - if (manager.getLocalTable().table != null) - manager.getLocalTable().table.requestFocusInWindow(); - } else { - if (manager.getRemoteTable().table != null) - manager.getRemoteTable().table.requestFocusInWindow(); - } - e.consume(); - return; - } - char c = e.getKeyChar(); - if (Character.isDefined(c)) { - long now = System.currentTimeMillis(); - if ((now - lastTimeKeyPressed) < 1300) { - currentKeys += c; - } else { - currentKeys = ""+c; - } - lastTimeKeyPressed = now; - selectStartWith(currentKeys); - } - } - }); - table.setShowGrid(false); - table.setShowVerticalLines(false); - tablePanel.addMouseListener(mouseListener); - tablePanel.add(table.getTableHeader(), BorderLayout.NORTH); - scroll = new JScrollPane(table); - scroll.addMouseListener(mouseListener); - tablePanel.add(scroll, BorderLayout.CENTER); - String[] rs = null; - try { - rs = session.getRoots(); - } catch (Exception e) { - rs = new String[0]; - JOptionPane.showMessageDialog(ft,e.toString()); - } - roots = new JComboBox(rs); - roots.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - final String root = (String)roots.getSelectedItem(); - Runnable r = new Runnable() { - public void run() { - ft.setEnabled(false); - table.requestFocusInWindow(); - try { - session.setAbsoluteDir(root); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - } - }); - - parentDir = new JLabel(""); - parentDir.setIcon(getUpDirIcon()); - parentDir.setToolTipText("Parent directory"); - parentDir.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - Runnable r = new Runnable() { - public void run() { - ft.setEnabled(false); - table.requestFocusInWindow(); - lastDir = session.getWorkingDir(); - if (lastDir.contains(session.getFileSeparator())) - lastDir = lastDir.substring(lastDir.lastIndexOf(session.getFileSeparator())+1); - try { - session.setUpDir(); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - } - }); - - freeSpace = new JLabel(""); - freeSpace.setToolTipText("Amount of available space on the current partition"); - - rootDir = new JLabel(""); - rootDir.setIcon(getRootIcon()); - rootDir.setToolTipText("Root directory"); - rootDir.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - Runnable r = new Runnable() { - public void run() { - table.requestFocusInWindow(); - String item = ((String)roots.getSelectedItem()); - if (item == null) return; - ft.setEnabled(false); - String r = null; - if (session.getOSName().toLowerCase(Locale.US).contains("linux")) - r = "/"; // in linux only something like this could be qualified as root... - else - r = session.getShortRootName(item); - if (r == null) { - ft.setEnabled(true); - return; - } - try { - session.setAbsoluteDir(r); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - } - }); - - homeDir = new JLabel(""); - homeDir.setIcon(getHomeIcon()); - homeDir.setToolTipText("Open home directory"); - homeDir.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - Runnable r = new Runnable() { - public void run() { - table.requestFocusInWindow(); - String userDir = session.getUserDir(); - if (userDir == null) return; - ft.setEnabled(false); - try { - session.setAbsoluteDir(userDir); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - } - }); - - openDir = new JLabel(""); - openDir.setIcon(getOpenDirIcon()); - openDir.setToolTipText("Open directory"); - openDir.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - final String root = JOptionPane.showInputDialog(roots, "Open folder:", session.getWorkingDir()); - if (root == null) return; - Runnable r = new Runnable() { - public void run() { - table.requestFocusInWindow(); - ft.setEnabled(false); - try { - session.setAbsoluteDir(root); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - updateTable(); - ft.setEnabled(true); - } - }; - exec.execute(r); - } - }); - - refreshDir = new JLabel(""); - refreshDir.setIcon(getRefreshIcon()); - refreshDir.setToolTipText("Refresh"); - refreshDir.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - Runnable r = new Runnable() { - public void run() { - table.requestFocusInWindow(); - ft.setEnabled(false); - String[] str = null; - try { - str = session.getRoots(); - } catch (Exception e) { - str = new String[0]; - JOptionPane.showMessageDialog(ft,e.toString()); - } - ActionListener listener = null; - if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { - listener = roots.getActionListeners()[0]; - roots.removeActionListener(listener); - } - roots.removeAllItems(); - if (str != null) - for (int i=0; i 1) return false; - if (e.getClickCount() >= 2) { - lastClick = 0L; - lastRow = row; - return true; - } - if (lastClick > 0L) { - long diff = System.currentTimeMillis() - lastClick; - if (diff < 500L) { - lastClick = 0L; - boolean ret = row == lastRow; - lastRow = row; - return ret; - } - } - - lastClick = System.currentTimeMillis(); - lastRow = row; - return false; - } - - public boolean focus() { - return table.hasFocus(); - } - - private void createConnectPanel() { - connectPanel = new JPanel(); - connectPanel.setLayout(new BoxLayout(connectPanel, BoxLayout.X_AXIS)); - - JPanel pp1 = new JPanel(); - pp1.setLayout(new GridLayout(0, 1)); - connectPanel.add(pp1); - - JPanel p = new JPanel(); - p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); - JLabel l = new JLabel("

    Please specify connection preferences

    "); - p.add(Box.createVerticalGlue()); - JPanel p1 = new JPanel(); - p1.setLayout(new FlowLayout()); - p1.add(l); - p.add(p1); - pp1.add(p); - - JButton connect = new JButton("Connect"); - connect.setIcon(getConnIcon()); - connect.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - frame.connect.setVisible(true); - frame.connect.toFront(); - if (frame.connect.bDialogOK) { - frame.manager.initiated = false; - int port = 54321; - try { - port = Integer.valueOf(frame.connect.sPort); - } catch (Throwable t) { } - frame.manager.connect(frame.connect.sHost, frame.connect.sUser, port); - } else { - frame.manager.initiated = true; - } - } - }); - p = new JPanel(); - p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); - p1 = new JPanel(); - p1.setLayout(new FlowLayout()); - p1.add(connect); - p.add(p1); - p.add(Box.createVerticalGlue()); - pp1.add(p); - } - - private JPopupMenu createPopup() { - popup = new JPopupMenu(); - - JMenuItem copy = new JMenuItem("Copy"); - copy.setIcon(getCopyIcon()); - copy.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - Runnable r = new Runnable() { - public void run() { - copy(); - } - }; - exec.execute(r); - } - }); - copy.setToolTipText("Copy"); - popup.add(copy); - - JMenuItem mkdir = new JMenuItem("Make dir"); - mkdir.setIcon(getMkDirIcon()); - mkdir.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - Runnable r = new Runnable() { - public void run() { - makeDir(); - } - }; - exec.execute(r); - } - }); - mkdir.setToolTipText("Create directory"); - popup.add(mkdir); - - JMenuItem remove = new JMenuItem("Remove"); - remove.setIcon(getRemoveIcon()); - remove.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - Runnable r = new Runnable() { - public void run() { - removeFiles(); - } - }; - exec.execute(r); - } - }); - remove.setToolTipText("Delete"); - popup.add(remove); - - popup.addSeparator(); - - JMenuItem quit = new JMenuItem("Quit"); - quit.setIcon(getExitIcon()); - quit.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - Runnable r = new Runnable() { - public void run() { - System.exit(0); - } - }; - exec.execute(r); - } - }); - quit.setToolTipText("Exit"); - popup.add(quit); - - return popup; - } - - protected void removeFiles() { - int[] rs = table.getSelectedRows(); - if (rs == null || rs.length == 0) { - return; - } - ft.setEnabled(false); - Vector v = new Vector(); - for (int i=0; i= rows.size()) continue; - // get the corresponding row in the table - final FileHandler fh = rows.get(rs[i]); - v.add(fh); - } - if (v.size() == 0) { - ft.setEnabled(true); - return; - } - for (int i=0; iDelete file").append(files.length > 1 ? "s" : "").append("
    "); - for (int i=0; i").append(v.get(i).getName()).append(""); - if (i < files.length - 1 && i < 6) buf.append("
    "); - } - } - if (files.length > 7) { - System.out.println("... "+(files.length - 7)+" more"); - buf.append("
    ...").append(files.length - 7).append(" more ?"); - } else { - buf.append(" ?"); - } + if (fh.getName().equals("..")) { + lastDir = session.getWorkingDir(); + if (lastDir.contains(session.getFileSeparator())) + lastDir = lastDir.substring(lastDir.lastIndexOf(session.getFileSeparator()) + 1); + try { + session.setUpDir(); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + } else { + try { + session.setRelativeDir(fh.getName()); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + e.consume(); + return; + } + if (key == KeyEvent.VK_F5) { + Runnable r = new Runnable() { + public void run() { + copy(); + } + }; + exec.execute(r); + e.consume(); + return; + } + if (key == KeyEvent.VK_F7) { + Runnable r = new Runnable() { + public void run() { + makeDir(); + } + }; + exec.execute(r); + e.consume(); + return; + } + if (key == KeyEvent.VK_F8 || key == KeyEvent.VK_DELETE) { + Runnable r = new Runnable() { + public void run() { + removeFiles(); + } + }; + exec.execute(r); + e.consume(); + return; + } + if (key == KeyEvent.VK_F10) { + System.exit(0); + } + if (key == KeyEvent.VK_TAB) { + if (command == null) { // remote session + if (manager.getLocalTable().table != null) + manager.getLocalTable().table.requestFocusInWindow(); + } else { + if (manager.getRemoteTable().table != null) + manager.getRemoteTable().table.requestFocusInWindow(); + } + e.consume(); + return; + } + char c = e.getKeyChar(); + if (Character.isDefined(c)) { + long now = System.currentTimeMillis(); + if ((now - lastTimeKeyPressed) < 1300) { + currentKeys += c; + } else { + currentKeys = "" + c; + } + lastTimeKeyPressed = now; + selectStartWith(currentKeys); + } + } + }); + table.setShowGrid(false); + table.setShowVerticalLines(false); + tablePanel.addMouseListener(mouseListener); + tablePanel.add(table.getTableHeader(), BorderLayout.NORTH); + scroll = new JScrollPane(table); + scroll.addMouseListener(mouseListener); + tablePanel.add(scroll, BorderLayout.CENTER); + String[] rs = null; + try { + rs = session.getRoots(); + } catch (Exception e) { + rs = new String[0]; + JOptionPane.showMessageDialog(ft, e.toString()); + } + roots = new JComboBox(rs); + roots.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + final String root = (String) roots.getSelectedItem(); + Runnable r = new Runnable() { + public void run() { + ft.setEnabled(false); + table.requestFocusInWindow(); + try { + session.setAbsoluteDir(root); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + } + }); + + parentDir = new JLabel(""); + parentDir.setIcon(getUpDirIcon()); + parentDir.setToolTipText("Parent directory"); + parentDir.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Runnable r = new Runnable() { + public void run() { + ft.setEnabled(false); + table.requestFocusInWindow(); + lastDir = session.getWorkingDir(); + if (lastDir.contains(session.getFileSeparator())) + lastDir = lastDir.substring(lastDir.lastIndexOf(session.getFileSeparator()) + 1); + try { + session.setUpDir(); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + } + }); + + freeSpace = new JLabel(""); + freeSpace.setToolTipText("Amount of available space on the current partition"); + + rootDir = new JLabel(""); + rootDir.setIcon(getRootIcon()); + rootDir.setToolTipText("Root directory"); + rootDir.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + String item = ((String) roots.getSelectedItem()); + if (item == null) return; + ft.setEnabled(false); + String r = null; + if (session.getOSName().toLowerCase(Locale.US).contains("linux")) + r = "/"; // in linux only something like this could be qualified as root... + else + r = session.getShortRootName(item); + if (r == null) { + ft.setEnabled(true); + return; + } + try { + session.setAbsoluteDir(r); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + } + }); + + homeDir = new JLabel(""); + homeDir.setIcon(getHomeIcon()); + homeDir.setToolTipText("Open home directory"); + homeDir.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + String userDir = session.getUserDir(); + if (userDir == null) return; + ft.setEnabled(false); + try { + session.setAbsoluteDir(userDir); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + } + }); + + openDir = new JLabel(""); + openDir.setIcon(getOpenDirIcon()); + openDir.setToolTipText("Open directory"); + openDir.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + final String root = JOptionPane.showInputDialog(roots, "Open folder:", session.getWorkingDir()); + if (root == null) return; + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + ft.setEnabled(false); + try { + session.setAbsoluteDir(root); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + } + }); + + refreshDir = new JLabel(""); + refreshDir.setIcon(getRefreshIcon()); + refreshDir.setToolTipText("Refresh"); + refreshDir.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + ft.setEnabled(false); + String[] str = null; + try { + str = session.getRoots(); + } catch (Exception e) { + str = new String[0]; + JOptionPane.showMessageDialog(ft, e.toString()); + } + ActionListener listener = null; + if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { + listener = roots.getActionListeners()[0]; + roots.removeActionListener(listener); + } + roots.removeAllItems(); + if (str != null) + for (int i = 0; i < str.length; i++) + roots.addItem(str[i]); + if (listener != null) + roots.addActionListener(listener); + try { + session.setAbsoluteDir(session.getWorkingDir()); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + } + }; + exec.execute(r); + } + }); + + separator = new JLabel(""); + separator.setIcon(getVertIcon()); + + mkdir = new JLabel(""); + mkdir.setIcon(getMkDirIcon()); + mkdir.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + makeDir(); + } + }; + exec.execute(r); + } + }); + mkdir.setToolTipText("Create directory"); + + remove = new JLabel(""); + remove.setIcon(getRemoveIcon()); + remove.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + removeFiles(); + } + }; + exec.execute(r); + } + }); + remove.setToolTipText("Delete"); + + visibleColumns = new JMenu("Display columns"); + showIconItem = new JMenuItem("Icon"); + showIconItem.setIcon(getSelectIcon()); + setSelectedItem(showIconItem, true); + showIconItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + setShowIcon(!showIcon); + if (!showIcon) { + showIconItem.setIcon(null); + setSelectedItem(showIconItem, false); + } else { + showIconItem.setIcon(getSelectIcon()); + setSelectedItem(showIconItem, true); + } + } + }; + exec.execute(r); + } + }); + visibleColumns.add(showIconItem); + showLengthItem = new JMenuItem("Size"); + showLengthItem.setIcon(getSelectIcon()); + setSelectedItem(showLengthItem, true); + showLengthItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + setShowLength(!showLength); + if (!showLength) { + showLengthItem.setIcon(null); + setSelectedItem(showLengthItem, false); + } else { + showLengthItem.setIcon(getSelectIcon()); + setSelectedItem(showLengthItem, true); + } + } + }; + exec.execute(r); + } + }); + visibleColumns.add(showLengthItem); + showModifItem = new JMenuItem("Modified"); + showModifItem.setIcon(getSelectIcon()); + setSelectedItem(showModifItem, true); + showModifItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + setShowModif(!showModif); + if (!showModif) { + showModifItem.setIcon(null); + setSelectedItem(showModifItem, false); + } else { + showModifItem.setIcon(getSelectIcon()); + setSelectedItem(showModifItem, true); + } + } + }; + exec.execute(r); + } + }); + visibleColumns.add(showModifItem); + showAttribItem = new JMenuItem("Attributes"); + showAttribItem.setIcon(getSelectIcon()); + setSelectedItem(showAttribItem, true); + showAttribItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + table.requestFocusInWindow(); + setShowAttrib(!showAttrib); + if (!showAttrib) { + showAttribItem.setIcon(null); + setSelectedItem(showAttribItem, false); + } else { + showAttribItem.setIcon(getSelectIcon()); + setSelectedItem(showAttribItem, true); + } + } + }; + exec.execute(r); + } + }); + visibleColumns.add(showAttribItem); + + if (session instanceof LocalSession) { + command = new JMenu("Command"); + copy = new JMenuItem("F5 Copy"); + copy.setIcon(getCopyIcon()); + copy.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + copy(); + } + }; + exec.execute(r); + + } + }); + command.add(copy); + exit = new JMenuItem("F10 Exit"); + exit.setIcon(getExitIcon()); + exit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + System.exit(0); + } + }); + command.addSeparator(); + command.add(exit); + updateTable(); + } + + updateColumns(); + + createConnectPanel(); + + if (session instanceof LocalSession) { + add(tablePanel, BorderLayout.CENTER); + } else { + setConnected(false); + } + } + + private void removeCurrentDir() { + if (currentDir != null) { + ActionListener listener = null; + if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { + listener = roots.getActionListeners()[0]; + roots.removeActionListener(listener); + } + for (int i = 0; i < roots.getItemCount(); i++) { + if (roots.getItemAt(i).toString().equals(currentDir)) { + roots.removeItemAt(i); + break; + } + } + if (listener != null) + roots.addActionListener(listener); + currentDir = null; + } + } + + private final boolean isDoubleClick(MouseEvent e) { + int row = table.getSelectedRow(); + if (table.getSelectedRowCount() > 1) return false; + if (e.getClickCount() >= 2) { + lastClick = 0L; + lastRow = row; + return true; + } + if (lastClick > 0L) { + long diff = System.currentTimeMillis() - lastClick; + if (diff < 500L) { + lastClick = 0L; + boolean ret = row == lastRow; + lastRow = row; + return ret; + } + } + + lastClick = System.currentTimeMillis(); + lastRow = row; + return false; + } + + public boolean focus() { + return table.hasFocus(); + } + + private void createConnectPanel() { + connectPanel = new JPanel(); + connectPanel.setLayout(new BoxLayout(connectPanel, BoxLayout.X_AXIS)); + + JPanel pp1 = new JPanel(); + pp1.setLayout(new GridLayout(0, 1)); + connectPanel.add(pp1); + + JPanel p = new JPanel(); + p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); + JLabel l = new JLabel("

    Please specify connection preferences

    "); + p.add(Box.createVerticalGlue()); + JPanel p1 = new JPanel(); + p1.setLayout(new FlowLayout()); + p1.add(l); + p.add(p1); + pp1.add(p); + + JButton connect = new JButton("Connect"); + connect.setIcon(getConnIcon()); + connect.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + frame.connect.setVisible(true); + frame.connect.toFront(); + if (frame.connect.bDialogOK) { + frame.manager.initiated = false; + int port = 54321; + try { + port = Integer.valueOf(frame.connect.sPort); + } catch (Throwable t) { + } + frame.manager.connect(frame.connect.sHost, frame.connect.sUser, port); + } else { + frame.manager.initiated = true; + } + } + }); + p = new JPanel(); + p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); + p1 = new JPanel(); + p1.setLayout(new FlowLayout()); + p1.add(connect); + p.add(p1); + p.add(Box.createVerticalGlue()); + pp1.add(p); + } + + private JPopupMenu createPopup() { + popup = new JPopupMenu(); + + JMenuItem copy = new JMenuItem("Copy"); + copy.setIcon(getCopyIcon()); + copy.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + copy(); + } + }; + exec.execute(r); + } + }); + copy.setToolTipText("Copy"); + popup.add(copy); + + JMenuItem mkdir = new JMenuItem("Make dir"); + mkdir.setIcon(getMkDirIcon()); + mkdir.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + makeDir(); + } + }; + exec.execute(r); + } + }); + mkdir.setToolTipText("Create directory"); + popup.add(mkdir); + + JMenuItem remove = new JMenuItem("Remove"); + remove.setIcon(getRemoveIcon()); + remove.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + removeFiles(); + } + }; + exec.execute(r); + } + }); + remove.setToolTipText("Delete"); + popup.add(remove); + + popup.addSeparator(); + + JMenuItem quit = new JMenuItem("Quit"); + quit.setIcon(getExitIcon()); + quit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + Runnable r = new Runnable() { + public void run() { + System.exit(0); + } + }; + exec.execute(r); + } + }); + quit.setToolTipText("Exit"); + popup.add(quit); + + return popup; + } + + protected void removeFiles() { + int[] rs = table.getSelectedRows(); + if (rs == null || rs.length == 0) { + return; + } + ft.setEnabled(false); + Vector v = new Vector(); + for (int i = 0; i < rs.length; i++) { + if (rs[i] < 0 || rs[i] >= rows.size()) continue; + // get the corresponding row in the table + final FileHandler fh = rows.get(rs[i]); + v.add(fh); + } + if (v.size() == 0) { + ft.setEnabled(true); + return; + } + for (int i = 0; i < v.size(); i++) { + final String fName = v.get(i).getName(); + if (fName.equals("..") || fName.equals(".")) { + v.remove(i); + i--; + } + } + if (v.size() == 0) { + ft.setEnabled(true); + return; + } + String[] files = new String[v.size()]; + final String delim = session.getFileSeparator(); // here we are reffering to the local file system... + if (delim == null) return; + String cp = session.getWorkingDir(); + if (cp == null) return; + if (!cp.endsWith(delim)) cp += delim; + StringBuffer buf = new StringBuffer(); + System.out.println("About to delete:"); + buf.append("Delete file").append(files.length > 1 ? "s" : "").append("
    "); + for (int i = 0; i < files.length; i++) { + files[i] = cp + v.get(i).getName(); + if (i < 7) { + System.out.println("\t" + files[i]); + buf.append("  ").append(v.get(i).getName()).append(""); + if (i < files.length - 1 && i < 6) buf.append("
    "); + } + } + if (files.length > 7) { + System.out.println("... " + (files.length - 7) + " more"); + buf.append("
    ...").append(files.length - 7).append(" more ?"); + } else { + buf.append(" ?"); + } // int ret = JOptionPane.showConfirmDialog(ft, "Delete "+files.length+" file"+(files.length > 1 ? "s" : "") + "?", "Confirm", JOptionPane.YES_NO_OPTION); - int ret = JOptionPane.showConfirmDialog(ft, buf.toString(), "Confirm", JOptionPane.YES_NO_OPTION); - if (ret == JOptionPane.YES_OPTION) { + int ret = JOptionPane.showConfirmDialog(ft, buf.toString(), "Confirm", JOptionPane.YES_NO_OPTION); + if (ret == JOptionPane.YES_OPTION) { // JOptionPane.showMessageDialog(ft, "This functionality requires higher privileges"); - try { - session.removeFiles(files); - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - } - setConnected(true); - ft.setEnabled(true); - } - - /** Method used to create a new directory */ - protected void makeDir() { - ft.setEnabled(false); - String name = JOptionPane.showInputDialog(ft, "Enter the name of the directory"); - if (name != null && name.length() > 0 && session != null) { - final String delim = session.getFileSeparator(); - if (delim == null) return; - String cp = session.getWorkingDir(); - if (cp == null) return; - if (!cp.endsWith(delim)) cp += delim; - try { - session.createDir(cp + name); - selectedDir = name; - } catch (Exception e) { - JOptionPane.showMessageDialog(ft,e.toString()); - } - setConnected(true); - } - ft.setEnabled(true); - selectedDir = null; - } - - protected void copy() { - synchronized (nf) { - if (transferMonitor == null) - transferMonitor = new TransferMonitor(ft, command != null, manager); - } - if (manager == null) return; - // is connected ? - if (!manager.isConnected()) { - JOptionPane.showMessageDialog(ft, "Please connect first to the remote server"); - return; - } - if (!transferMonitor.finished()) - return; - // get the list of selected files - int[] rs = table.getSelectedRows(); - if (rs == null || rs.length == 0) { - setStatus("Unable to cp... no file is selected"); - return; - } - ft.setEnabled(false); - Vector v = new Vector(); - for (int i=0; i= rows.size()) continue; - // get the corresponding row in the table - final FileHandler fh = rows.get(rs[i]); - v.add(fh); - } - if (v.size() == 0) { - ft.setEnabled(true); - setStatus("Unable to cp... no file is selected"); - return; - } - String[] files = new String[v.size()]; - boolean isRecursive = false; - setStatus("Copying..."); - final String delim = session.getFileSeparator(); // here we are reffering to the local file system... - String cp = session.getWorkingDir(); - if (!cp.endsWith(delim)) cp += delim; - for (int i=0; i 0 && session != null) { + final String delim = session.getFileSeparator(); + if (delim == null) return; + String cp = session.getWorkingDir(); + if (cp == null) return; + if (!cp.endsWith(delim)) cp += delim; + try { + session.createDir(cp + name); + selectedDir = name; + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + setConnected(true); + } + ft.setEnabled(true); + selectedDir = null; + } + + protected void copy() { + synchronized (nf) { + if (transferMonitor == null) + transferMonitor = new TransferMonitor(ft, command != null, manager); + } + if (manager == null) return; + // is connected ? + if (!manager.isConnected()) { + JOptionPane.showMessageDialog(ft, "Please connect first to the remote server"); + return; + } + if (!transferMonitor.finished()) + return; + // get the list of selected files + int[] rs = table.getSelectedRows(); + if (rs == null || rs.length == 0) { + setStatus("Unable to cp... no file is selected"); + return; + } + ft.setEnabled(false); + Vector v = new Vector(); + for (int i = 0; i < rs.length; i++) { + if (rs[i] < 0 || rs[i] >= rows.size()) continue; + // get the corresponding row in the table + final FileHandler fh = rows.get(rs[i]); + v.add(fh); + } + if (v.size() == 0) { + ft.setEnabled(true); + setStatus("Unable to cp... no file is selected"); + return; + } + String[] files = new String[v.size()]; + boolean isRecursive = false; + setStatus("Copying..."); + final String delim = session.getFileSeparator(); // here we are reffering to the local file system... + String cp = session.getWorkingDir(); + if (!cp.endsWith(delim)) cp += delim; + for (int i = 0; i < files.length; i++) { + files[i] = cp + v.get(i).getName(); + setStatus(files[i]); + isRecursive = isRecursive | v.get(i).isDir(); + } + setStatus("."); + String ret = null; // if (transferMonitor == null) // transferMonitor = new TransferMonitor(ft, command != null, manager); // else - transferMonitor.restart(ft, command != null); - if (( ret = manager.initiateTransfer(files, command != null, isRecursive)) != null) { // inform the manager to start transferring... - transferMonitor.start(); - ft.setEnabled(true); - JOptionPane.showMessageDialog(ft, "Cannot copy: "+ret); - return; - } - transferMonitor.start(); - ft.setEnabled(true); - } - - protected void selectStartWith(String prefix) { - synchronized (getTreeLock()) { - ListSelectionModel model = table.getSelectionModel(); - for (int i=0; i en = header.getColumnModel().getColumns(); - if (en != null) - while (en.hasMoreElements()) { - TableColumn column = en.nextElement(); - if (column != null) { - String colName = column.getHeaderValue().toString(); - if (colName.equals(colNames[0]) || colName.length() == 0) { - column.setPreferredWidth(25); - } - if (colName.equals(colNames[1])) { - column.setPreferredWidth(1000); - } - if (colName.equals(colNames[2])) { - column.setPreferredWidth(160); - } - if (colName.equals(colNames[3])) { - column.setPreferredWidth(100); - } - if (colName.equals(colNames[4])) { - column.setPreferredWidth(30); - } - } - } - } - - private void updateTable() { - - synchronized (this.getTreeLock()) { - removeCurrentDir(); - rows.clear(); - // add the rows... - if (!session.isRoot()) { - FileHandler h = new FileHandler("..", getUpDirIcon(), -1, -1, "", true); - rows.add(h); - parentDir.setEnabled(true); - rootDir.setEnabled(true); - } else { - parentDir.setEnabled(false); - rootDir.setEnabled(false); - } - // add the directories - for (Iterator it = session.dirs.iterator(); it.hasNext(); ) { - String d = it.next(); - FileHandler h = new FileHandler(d, session.icons.get(d), session.modif.get(d), -1, constructAttrib(d), true); - rows.add(h); - } - // add the rest of the files - for (Iterator it = session.length.keySet().iterator(); it.hasNext(); ) { - String f = it.next(); - try { - FileHandler h = new FileHandler(f, session.icons.get(f), session.modif.get(f), session.length.get(f), constructAttrib(f), false); - rows.add(h); - } catch (Throwable t) { + showIconItem.setEnabled(enabled); + showLengthItem.setEnabled(enabled); + showModifItem.setEnabled(enabled); + showAttribItem.setEnabled(enabled); + if (command != null) { + copy.setEnabled(enabled); + exit.setEnabled(enabled); + } + } + + public void setShowIcon(boolean show) { + showIcon = show; + updateColumns(); + } + + public void setShowLength(boolean show) { + showLength = show; + updateColumns(); + } + + public void setShowModif(boolean show) { + showModif = show; + updateColumns(); + } + + public void setShowAttrib(boolean show) { + showAttrib = show; + updateColumns(); + } + + private void updateColumns() { + synchronized (this.getTreeLock()) { + columns.clear(); + if (showIcon) { + columns.add(colNames[0]); + } + columns.add(colNames[1]); + if (showModif) { + columns.add(colNames[2]); + } + if (showLength) { + columns.add(colNames[3]); + } + if (showAttrib) { + columns.add(colNames[4]); + } + } + ((AbstractTableModel) table.getModel()).fireTableStructureChanged(); + JTableHeader header = table.getTableHeader(); + Enumeration en = header.getColumnModel().getColumns(); + if (en != null) + while (en.hasMoreElements()) { + TableColumn column = en.nextElement(); + if (column != null) { + String colName = column.getHeaderValue().toString(); + if (colName.equals(colNames[0]) || colName.length() == 0) { + column.setPreferredWidth(25); + } + if (colName.equals(colNames[1])) { + column.setPreferredWidth(1000); + } + if (colName.equals(colNames[2])) { + column.setPreferredWidth(160); + } + if (colName.equals(colNames[3])) { + column.setPreferredWidth(100); + } + if (colName.equals(colNames[4])) { + column.setPreferredWidth(30); + } + } + } + } + + private void updateTable() { + + synchronized (this.getTreeLock()) { + removeCurrentDir(); + rows.clear(); + // add the rows... + if (!session.isRoot()) { + FileHandler h = new FileHandler("..", getUpDirIcon(), -1, -1, "", true); + rows.add(h); + parentDir.setEnabled(true); + rootDir.setEnabled(true); + } else { + parentDir.setEnabled(false); + rootDir.setEnabled(false); + } + // add the directories + for (Iterator it = session.dirs.iterator(); it.hasNext(); ) { + String d = it.next(); + FileHandler h = new FileHandler(d, session.icons.get(d), session.modif.get(d), -1, constructAttrib(d), true); + rows.add(h); + } + // add the rest of the files + for (Iterator it = session.length.keySet().iterator(); it.hasNext(); ) { + String f = it.next(); + try { + FileHandler h = new FileHandler(f, session.icons.get(f), session.modif.get(f), session.length.get(f), constructAttrib(f), false); + rows.add(h); + } catch (Throwable t) { // t.printStackTrace(); - } - } - if (sortColumn != null) - sortTable(); - else - ((AbstractTableModel)table.getModel()).fireTableDataChanged(); - } - // update the amount of free space available - String freeSpace = session.freeSpace(); - if (freeSpace != null && freeSpace.length() != 0) { - this.freeSpace.setText("Free space: "+freeSpace+" 0 && roots.getSelectedIndex() != poz) { - ActionListener listener = null; - if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { - listener = roots.getActionListeners()[0]; - roots.removeActionListener(listener); - } - roots.setSelectedIndex(poz); - roots.repaint(); - roots.revalidate(); - if (listener != null) - roots.addActionListener(listener); - } - if (max < 0 && found) rootDir.setEnabled(false); - if (command == null) { - setStatus("Current remote dir = "+session.getWorkingDir()); + } + } + if (sortColumn != null) + sortTable(); + else + ((AbstractTableModel) table.getModel()).fireTableDataChanged(); + } + // update the amount of free space available + String freeSpace = session.freeSpace(); + if (freeSpace != null && freeSpace.length() != 0) { + this.freeSpace.setText("Free space: " + freeSpace + " 0 && roots.getSelectedIndex() != poz) { + ActionListener listener = null; + if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { + listener = roots.getActionListeners()[0]; + roots.removeActionListener(listener); + } + roots.setSelectedIndex(poz); + roots.repaint(); + roots.revalidate(); + if (listener != null) + roots.addActionListener(listener); + } + if (max < 0 && found) rootDir.setEnabled(false); + if (command == null) { + setStatus("Current remote dir = " + session.getWorkingDir()); // String[] str = session.getRoots(); // ActionListener listener = roots.getActionListeners()[0]; // roots.removeActionListener(listener); @@ -1267,258 +1172,418 @@ private void updateTable() { // for (int i=0; i= columns.size()) return ""; - final String colName = columns.get(column); - if (colName.equals(colNames[0])) return ""; - return colName; - } - - public boolean isCellEditable(int arg0, int arg1) { - return false; - } - - /* + } else + setStatus("Current local dir = " + session.getWorkingDir()); + table.revalidate(); + table.repaint(); + table.getTableHeader().repaint(); + table.getTableHeader().revalidate(); + + if (lastDir != null) { + try { + Thread.sleep(200); + } catch (Exception e) { + } + synchronized (getTreeLock()) { + for (int i = 0; i < rows.size(); i++) { + FileHandler h = rows.get(i); + if (h.getName().equals(lastDir)) { + table.setRowSelectionInterval(i, i); + if (table.getParent() instanceof JViewport) { + JViewport view = (JViewport) table.getParent(); + Rectangle rect = table.getCellRect(i, 0, true); + Point pt = view.getViewPosition(); + rect.setLocation(rect.x - pt.x, rect.y - pt.y); + view.scrollRectToVisible(rect); + table.scrollRectToVisible(rect); + } + break; + } + } + } + lastDir = null; + } else if (selectedDir != null) { + synchronized (getTreeLock()) { + for (int i = 0; i < rows.size(); i++) { + FileHandler h = rows.get(i); + if (h.getName().equals(selectedDir)) { + table.setRowSelectionInterval(i, i); + if (table.getParent() instanceof JViewport) { + JViewport view = (JViewport) table.getParent(); + Rectangle rect = table.getCellRect(i, 0, true); + Point pt = view.getViewPosition(); + rect.setLocation(rect.x - pt.x, rect.y - pt.y); + view.scrollRectToVisible(rect); + table.scrollRectToVisible(rect); + } + break; + } + } + } + selectedDir = null; + } else { + table.setRowSelectionInterval(0, 0); + } + } + + private void sortTable() { + Collections.sort(rows, new ColumnSorter()); + ((AbstractTableModel) table.getModel()).fireTableDataChanged(); + table.getTableHeader().repaint(); + } + + private String constructAttrib(final String fileName) { + final StringBuffer buf = new StringBuffer(); + if (session.read.containsKey(fileName) && session.read.get(fileName)) + buf.append("r"); + else buf.append("-"); + if (session.write.containsKey(fileName) && session.write.get(fileName)) + buf.append("w"); + else buf.append("-"); + return buf.toString(); + } + + private Icon getUpDirIcon() { + if (upDirIcon != null) return upDirIcon; + try { + URL r = getClass().getResource(pack + "/icons/up_dir.png"); + upDirIcon = new ImageIcon(r); + } catch (Exception e) { + } + return upDirIcon; + } + + private Icon getRootIcon() { + if (rootIcon != null) return rootIcon; + try { + URL r = getClass().getResource(pack + "/icons/rootIcon.png"); + rootIcon = new ImageIcon(r); + } catch (Exception e) { + } + return rootIcon; + } + + private Icon getCopyIcon() { + if (copyIcon != null) return copyIcon; + try { + URL r = getClass().getResource(pack + "/icons/copy.png"); + copyIcon = new ImageIcon(r); + } catch (Exception e) { + } + return copyIcon; + } + + private Icon getExitIcon() { + if (exitIcon != null) return exitIcon; + try { + URL r = getClass().getResource(pack + "/icons/exit.jpg"); + exitIcon = new ImageIcon(r); + } catch (Exception e) { + } + return exitIcon; + } + + private Icon getOpenDirIcon() { + if (openDirIcon != null) return openDirIcon; + try { + URL url = getClass().getResource(pack + "/icons/openfolder.png"); + openDirIcon = new ImageIcon(url); + } catch (Exception e) { + } + return openDirIcon; + } + + private Icon getRefreshIcon() { + if (refreshIcon != null) return refreshIcon; + try { + URL url = getClass().getResource(pack + "/icons/refresh.gif"); + refreshIcon = new ImageIcon(url); + } catch (Exception e) { + } + return refreshIcon; + } + + private Icon getHomeIcon() { + if (openHomeIcon != null) return openHomeIcon; + try { + URL url = getClass().getResource(pack + "/icons/home_icon.png"); + openHomeIcon = new ImageIcon(url); + } catch (Exception e) { + } + return openHomeIcon; + } + + private Icon getSelectIcon() { + if (selectIcon != null) return selectIcon; + try { + URL url = getClass().getResource(pack + "/icons/select.gif"); + selectIcon = new ImageIcon(url); + } catch (Exception e) { + } + return selectIcon; + } + + private Icon getSortAIcon() { + if (sortAIcon != null) return sortAIcon; + try { + URL url = getClass().getResource(pack + "/icons/sort_up.gif"); + sortAIcon = new ImageIcon(url); + } catch (Exception e) { + } + return sortAIcon; + } + + private Icon getSortDIcon() { + if (sortDIcon != null) return sortDIcon; + try { + URL url = getClass().getResource(pack + "/icons/sort_down.gif"); + sortDIcon = new ImageIcon(url); + } catch (Exception e) { + } + return sortDIcon; + } + + public void setConnected(boolean connected) { + if (!connected) { + removeAll(); + add(connectPanel, BorderLayout.CENTER); + setEnabled(false); + revalidate(); + repaint(); + } else { + removeAll(); + add(tablePanel, BorderLayout.CENTER); + setEnabled(true); + revalidate(); + repaint(); + ft.setEnabled(false); + String[] str = null; + try { + str = session.getRoots(); + } catch (Exception e) { + str = new String[0]; + JOptionPane.showMessageDialog(ft, e.toString()); + } + ActionListener listener = null; + if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { + listener = roots.getActionListeners()[0]; + roots.removeActionListener(listener); + } + roots.removeAllItems(); + if (str != null) + for (int i = 0; i < str.length; i++) + roots.addItem(str[i]); + if (listener != null) + roots.addActionListener(listener); + try { + session.setAbsoluteDir(session.getWorkingDir()); + } catch (Exception e) { + JOptionPane.showMessageDialog(ft, e.toString()); + } + updateTable(); + ft.setEnabled(true); + table.requestFocusInWindow(); + } + } + + public void setStatus(String status) { + if (statusBar == null) return; + status = status.replace("<", "[").replace(">", "]"); + statusBar.addText(status + "\n"); + } + + public final JTable getTable() { + return table; + } + + private Icon getMkDirIcon() { + if (mkdirIcon != null) return mkdirIcon; + try { + URL r = getClass().getResource("icons/mkdir.png"); + mkdirIcon = new ImageIcon(r); + } catch (Exception e) { + } + return mkdirIcon; + } + + private Icon getRemoveIcon() { + if (removeIcon != null) return removeIcon; + try { + URL r = getClass().getResource("icons/delete.png"); + removeIcon = new ImageIcon(r); + } catch (Exception e) { + } + return removeIcon; + } + + private Icon getConnIcon() { + if (connIcon != null) return connIcon; + try { + URL r = getClass().getResource("icons/connect2.gif"); + connIcon = new ImageIcon(r); + } catch (Exception e) { + } + return connIcon; + } + + private Icon getVertIcon() { + if (verticalIcon != null) return verticalIcon; + try { + URL r = getClass().getResource("icons/vertical.jpg"); + verticalIcon = new ImageIcon(r); + } catch (Exception e) { + } + return verticalIcon; + } + + private final class FileHandler { + private final String name; + private final Icon icon; + private final long modif; + private final long size; + private final String attrib; + private final boolean isDir; + + public FileHandler(String name, Icon icon, long modif, long size, String attrib, boolean isDir) { + this.name = name; + this.icon = icon; + this.modif = modif; + this.size = size; + this.attrib = attrib; + this.isDir = isDir; + } + + public final String getName() { + if (name == null) return " "; + return name; + } + + public final Icon getIcon() { + return icon; + } + + public final String getModif() { + if (modif <= 0) return " "; + return " " + dateFormat.format(new Date(modif)); + } + + public final long getModifL() { + return modif; + } + + public final String getSize() { + if (size <= 0) return " "; + double s = size; + if (s > 1024) s /= 1024; + else return " " + nf.format(s) + " B"; + if (s > 1024) s /= 1024; + else return " " + nf.format(s) + " KB"; + if (s > 1024) s /= 1024; + else return " " + nf.format(s) + " MB"; + if (s > 1024) s /= 1024; + else return " " + nf.format(s) + " GB"; + return " " + nf.format(s) + " PB"; + } + + public final long getSizeL() { + return size; + } + + public final String getAttrib() { + if (attrib == null) return " "; + return " " + attrib; + } + + public final boolean isDir() { + return isDir; + } + } + + private class MyTableModel extends AbstractTableModel { + + public int getRowCount() { + return rows.size(); + } + + public int getColumnCount() { + return columns.size(); + } + + public String getColumnName(int column) { + if (column < 0 || column >= columns.size()) return ""; + final String colName = columns.get(column); + if (colName.equals(colNames[0])) return ""; + return colName; + } + + public boolean isCellEditable(int arg0, int arg1) { + return false; + } + + /* * JTable uses this method to determine the default renderer/ * editor for each cell. If we didn't implement this method, * then the last column would contain text ("true"/"false"), * rather than a check box. */ public Class getColumnClass(int c) { - final String col = columns.get(c); - if (col.equals(colNames[0])) - return Icon.class; - return String.class; + final String col = columns.get(c); + if (col.equals(colNames[0])) + return Icon.class; + return String.class; // return getValueAt(0, c).getClass(); } - - public Object getValueAt(int row, int col) { - FileHandler h = null; - synchronized (rows) { - if (row < 0 || row >= rows.size()) return null; - h = rows.get(row); - } - String colName = null; - synchronized (columns) { - if (col < 0 || col >= columns.size()) return null; - colName = columns.get(col); - } - table.getColumnModel().getColumn(col).setResizable(true); - - if (colName.equals(colNames[0])) { - table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); - table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); - return h.getIcon(); - } - if (colName.equals(colNames[1])) { - table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); - table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); - return h.getName(); - } - if (colName.equals(colNames[2])) { - table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); - table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); - return h.getModif(); - } - if (colName.equals(colNames[3])) { - table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); - table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); - return h.getSize(); - } - if (colName.equals(colNames[4])) { - table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); - table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); - return h.getAttrib(); - } - return null; - } - } - - public class MyTableHeaderRenderer extends JLabel implements TableCellRenderer { - - private Border border = BorderFactory.createRaisedBevelBorder(); - + + public Object getValueAt(int row, int col) { + FileHandler h = null; + synchronized (rows) { + if (row < 0 || row >= rows.size()) return null; + h = rows.get(row); + } + String colName = null; + synchronized (columns) { + if (col < 0 || col >= columns.size()) return null; + colName = columns.get(col); + } + table.getColumnModel().getColumn(col).setResizable(true); + + if (colName.equals(colNames[0])) { + table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); + table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); + return h.getIcon(); + } + if (colName.equals(colNames[1])) { + table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); + table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); + return h.getName(); + } + if (colName.equals(colNames[2])) { + table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); + table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); + return h.getModif(); + } + if (colName.equals(colNames[3])) { + table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); + table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); + return h.getSize(); + } + if (colName.equals(colNames[4])) { + table.getColumnModel().getColumn(col).setHeaderRenderer(headerRenderer); + table.getColumnModel().getColumn(col).setCellRenderer(cellRenderer); + return h.getAttrib(); + } + return null; + } + } + + public class MyTableHeaderRenderer extends JLabel implements TableCellRenderer { + + private Border border = BorderFactory.createRaisedBevelBorder(); + // This method is called each time a column header // using this renderer needs to be rendered. public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) { + boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) { // 'value' is column header value of column 'vColIndex' // rowIndex is always -1 // isSelected is always false @@ -1527,96 +1592,104 @@ public Component getTableCellRendererComponent(JTable table, Object value, setText(value.toString()); setForeground(Color.red); // Set tool tip if desired - setToolTipText((String)value); + setToolTipText((String) value); setBorder(border); if (sortColumn != null && sortColumn.equals(value.toString())) { - if (sortAsc) setIcon(getSortAIcon()); - else setIcon(getSortDIcon()); + if (sortAsc) setIcon(getSortAIcon()); + else setIcon(getSortDIcon()); } else if (sortColumn != null && sortColumn.equals(colNames[0]) && vColIndex == 0) { - if (sortAsc) setIcon(getSortAIcon()); - else setIcon(getSortDIcon()); + if (sortAsc) setIcon(getSortAIcon()); + else setIcon(getSortDIcon()); } else setIcon(null); // Since the renderer is a component, return itself return this; } - + // The following methods override the defaults for performance reasons - public void validate() {} - public void revalidate() {} - protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {} - public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {} + public void validate() { + } + + public void revalidate() { + } + + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + } + + public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { + } + } + + private class ColumnHeaderListener extends MouseAdapter { + public void mouseClicked(MouseEvent evt) { + JTable table = ((JTableHeader) evt.getSource()).getTable(); + TableColumnModel colModel = table.getColumnModel(); + // The index of the column whose header was clicked + int vColIndex = colModel.getColumnIndexAtX(evt.getX()); + // Return if not clicked on any column header + if (vColIndex == -1) { + return; + } + // get the name of the column + String col = columns.get(vColIndex); + if (sortColumn == null) { + sortColumn = col; + sortAsc = true; + } else { + if (sortColumn.equals(col)) { + sortAsc = !sortAsc; + } else { + sortColumn = col; + sortAsc = true; + } + } + sortTable(); + } } - - private class ColumnHeaderListener extends MouseAdapter { - public void mouseClicked(MouseEvent evt) { - JTable table = ((JTableHeader)evt.getSource()).getTable(); - TableColumnModel colModel = table.getColumnModel(); - // The index of the column whose header was clicked - int vColIndex = colModel.getColumnIndexAtX(evt.getX()); - // Return if not clicked on any column header - if (vColIndex == -1) { - return; - } - // get the name of the column - String col = columns.get(vColIndex); - if (sortColumn == null) { - sortColumn = col; - sortAsc = true; - } else { - if (sortColumn.equals(col)) { - sortAsc = !sortAsc; - } else { - sortColumn = col; - sortAsc = true; - } - } - sortTable(); - } - } - - // This comparator is used to sort vectors of data - public class ColumnSorter implements Comparator { - ColumnSorter() { - } - public int compare(Object a, Object b) { - FileHandler fh1 = (FileHandler)a; - FileHandler fh2 = (FileHandler)b; - if (fh1.getName().equals("..")) return -1; - if (fh2.getName().equals("..")) return 1; - if (fh1.isDir() && !fh2.isDir()) - return -1; - if (!fh1.isDir() && fh2.isDir()) - return 1; - if (sortColumn.equals(colNames[0])) { - if (fh1.getIcon() == null && fh2.getIcon() == null) - return 0; - if (fh1.getIcon() == null) - return 1; - if (fh2.getIcon() == null) - return -1; - if (sortAsc) - return fh1.getIcon().toString().compareTo(fh2.getIcon().toString()); - return fh2.getIcon().toString().compareTo(fh1.getIcon().toString()); - } - if (sortColumn.equals(colNames[2])) { - if (fh1.getModifL() < fh2.getModifL()) return (sortAsc ? 1 : -1); - else if (fh1.getModifL() == fh2.getModifL()) return 0; - return (sortAsc ? -1 : 1); - } - if (sortColumn.equals(colNames[3])) { - if (fh1.getSizeL() < fh2.getSizeL()) return (sortAsc ? 1 : -1); - else if (fh1.getSizeL() == fh2.getSizeL()) return 0; - return (sortAsc ? -1 : 1); - } - if (sortColumn.equals(colNames[4])) { - if (sortAsc) - return fh1.getAttrib().compareTo(fh2.getAttrib()); - return fh2.getAttrib().compareTo(fh1.getAttrib()); - } - if (sortAsc) - return fh1.getName().compareTo(fh2.getName()); - return fh2.getName().compareTo(fh1.getName()); - } + + // This comparator is used to sort vectors of data + public class ColumnSorter implements Comparator { + ColumnSorter() { + } + + public int compare(Object a, Object b) { + FileHandler fh1 = (FileHandler) a; + FileHandler fh2 = (FileHandler) b; + if (fh1.getName().equals("..")) return -1; + if (fh2.getName().equals("..")) return 1; + if (fh1.isDir() && !fh2.isDir()) + return -1; + if (!fh1.isDir() && fh2.isDir()) + return 1; + if (sortColumn.equals(colNames[0])) { + if (fh1.getIcon() == null && fh2.getIcon() == null) + return 0; + if (fh1.getIcon() == null) + return 1; + if (fh2.getIcon() == null) + return -1; + if (sortAsc) + return fh1.getIcon().toString().compareTo(fh2.getIcon().toString()); + return fh2.getIcon().toString().compareTo(fh1.getIcon().toString()); + } + if (sortColumn.equals(colNames[2])) { + if (fh1.getModifL() < fh2.getModifL()) return (sortAsc ? 1 : -1); + else if (fh1.getModifL() == fh2.getModifL()) return 0; + return (sortAsc ? -1 : 1); + } + if (sortColumn.equals(colNames[3])) { + if (fh1.getSizeL() < fh2.getSizeL()) return (sortAsc ? 1 : -1); + else if (fh1.getSizeL() == fh2.getSizeL()) return 0; + return (sortAsc ? -1 : 1); + } + if (sortColumn.equals(colNames[4])) { + if (sortAsc) + return fh1.getAttrib().compareTo(fh2.getAttrib()); + return fh2.getAttrib().compareTo(fh1.getAttrib()); + } + if (sortAsc) + return fh1.getName().compareTo(fh2.getName()); + return fh2.getName().compareTo(fh1.getName()); + } // public int compare(Object a, Object b) { // FileHandler fh1 = (FileHandler)a; // FileHandler fh2 = (FileHandler)b; @@ -1631,7 +1704,7 @@ public int compare(Object a, Object b) { // return -1; // if (fh1.getIcon().toString().toLowerCase(Locale.US).contains("folder")) return -1; // if (fh2.getIcon().toString().toLowerCase(Locale.US).contains("folder")) return 1; -// if (sortAsc) +// if (sortAsc) // return fh1.getIcon().toString().compareTo(fh2.getIcon().toString()); // return fh2.getIcon().toString().compareTo(fh1.getIcon().toString()); // } @@ -1666,44 +1739,35 @@ public int compare(Object a, Object b) { // return fh1.getName().compareTo(fh2.getName()); // return fh2.getName().compareTo(fh1.getName()); // } - } - - private static final Color c1 = new Color(128, 179, 230); - private static final Color c2= new Color(220, 234, 248); - private static final Color c22 = new Color(226, 241, 255); - private static final Color c3 = new Color(200, 200, 200); - private static final Color c4 = new Color(245, 255, 255); - private static final Color c44 = new Color(255, 255, 255); - public static final Color cg = new Color(217, 217, 217); - public static final Color cm = (new JPanel()).getBackground(); - + } + public class MyTableCellRenderer extends DefaultTableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, rowIndex, vColIndex); - final String col = columns.get(vColIndex); + boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, rowIndex, vColIndex); + final String col = columns.get(vColIndex); if (isSelected && showSelection) { - if (col.equals(colNames[0]) || col.equals(colNames[2]) || col.equals(colNames[4])) { - c.setBackground(c3); - } else - c.setBackground(c1); + if (col.equals(colNames[0]) || col.equals(colNames[2]) || col.equals(colNames[4])) { + c.setBackground(c3); + } else + c.setBackground(c1); } else { - if (col.equals(colNames[0]) || col.equals(colNames[2]) || col.equals(colNames[4])) - c.setBackground(focus() ? c44 : cg); - else - c.setBackground(focus() ? c2 : cg); + if (col.equals(colNames[0]) || col.equals(colNames[2]) || col.equals(colNames[4])) + c.setBackground(focus() ? c44 : cg); + else + c.setBackground(focus() ? c2 : cg); } if (value instanceof Icon) { - ((JLabel)c).setIcon((Icon)value); - ((JLabel)c).setText(null); + ((JLabel) c).setIcon((Icon) value); + ((JLabel) c).setText(null); } else { - ((JLabel)c).setText((String)value); - ((JLabel)c).setIcon(null); + ((JLabel) c).setText((String) value); + ((JLabel) c).setIcon(null); // int availableWidth = table.getColumnModel().getColumn(vColIndex).getWidth(); // availableWidth -= table.getIntercellSpacing().getWidth(); // String cellText = getText(); // FontMetrics fm = getFontMetrics( getFont() ); -// +// // if (fm.stringWidth(cellText) > availableWidth) { // String dots = "..."; // int textWidth = fm.stringWidth( dots ); @@ -1717,101 +1781,10 @@ public Component getTableCellRendererComponent(JTable table, Object value, // setText( dots + cellText.substring(nChars + 1) ); // } } - ((JLabel)c).setBorder(null); - + ((JLabel) c).setBorder(null); + return c; } } - - public void setConnected(boolean connected) { - if (!connected) { - removeAll(); - add(connectPanel, BorderLayout.CENTER); - setEnabled(false); - revalidate(); - repaint(); - } else { - removeAll(); - add(tablePanel, BorderLayout.CENTER); - setEnabled(true); - revalidate(); - repaint(); - ft.setEnabled(false); - String[] str = null; - try { - str = session.getRoots(); - } catch (Exception e) { - str = new String[0]; - JOptionPane.showMessageDialog(ft,e.toString()); - } - ActionListener listener = null; - if (roots.getActionListeners() != null && roots.getActionListeners().length != 0) { - listener = roots.getActionListeners()[0]; - roots.removeActionListener(listener); - } - roots.removeAllItems(); - if (str != null) - for (int i=0; i", "]"); - statusBar.addText(status+"\n"); - } - - public final JTable getTable() { return table; } - - ImageIcon mkdirIcon; - private Icon getMkDirIcon() { - if (mkdirIcon != null) return mkdirIcon; - try { - URL r = getClass().getResource("icons/mkdir.png"); - mkdirIcon = new ImageIcon(r); - } catch (Exception e) { } - return mkdirIcon; - } - - ImageIcon removeIcon; - private Icon getRemoveIcon() { - if (removeIcon != null) return removeIcon; - try { - URL r = getClass().getResource("icons/delete.png"); - removeIcon = new ImageIcon(r); - } catch (Exception e) { } - return removeIcon; - } - - ImageIcon connIcon; - private Icon getConnIcon() { - if (connIcon != null) return connIcon; - try { - URL r = getClass().getResource("icons/connect2.gif"); - connIcon = new ImageIcon(r); - } catch (Exception e) { } - return connIcon; - } - - ImageIcon verticalIcon; - private Icon getVertIcon() { - if (verticalIcon != null) return verticalIcon; - try { - URL r = getClass().getResource("icons/vertical.jpg"); - verticalIcon = new ImageIcon(r); - } catch (Exception e) { } - return verticalIcon; - } - + } diff --git a/src/lia/util/net/copy/gui/GUISSHControlStream.java b/src/lia/util/net/copy/gui/GUISSHControlStream.java index 71259e3..f3c76d3 100644 --- a/src/lia/util/net/copy/gui/GUISSHControlStream.java +++ b/src/lia/util/net/copy/gui/GUISSHControlStream.java @@ -3,66 +3,54 @@ */ package lia.util.net.copy.gui; -import java.io.IOException; - -import javax.swing.JDialog; -import javax.swing.JOptionPane; -import javax.swing.JPasswordField; - import lia.util.net.common.SSHControlStream; +import javax.swing.*; +import java.io.IOException; + /** - * * @author Ciprian Dobre - * */ public class GUISSHControlStream extends SSHControlStream { - private JDialog connDialog; - + private JDialog connDialog; + /** * Creates a new SSH control connection on the default ssh port. - * - * Same as {@link #GUISSHControlStream(String, String, int) GUISSHControlStream(hostname, username, 22)} - * - * @param hostname: - * remote host - * @param username: - * remote account - * @throws IOException - * in case of failure + *

    + * Same as {@link #GUISSHControlStream(String, String, int, JDialog)} GUISSHControlStream(hostname, username, 22)} + * + * @param hostname: remote host + * @param username: remote account + * @throws IOException in case of failure */ public GUISSHControlStream(String hostname, String username, JDialog connDialog) { this(hostname, username, 22, connDialog); } - /** + /** * Creates a new SSH control connection on the specified remote sshd port - * - * @param port: - * remote sshd port - * @param hostname: - * remote host - * @param username: - * remote account - * @throws IOException - * in case of failure - */ - public GUISSHControlStream(String hostname, String username, int port, JDialog connDialog) { - super(hostname, username, port); - this.connDialog = connDialog; - } - - public String getPassword(String message) throws IOException { - connDialog.setVisible(false); - System.out.println(message); - JPasswordField pwd = new JPasswordField(10); - int action = JOptionPane.showConfirmDialog(null, pwd, "Enter Password", JOptionPane.OK_CANCEL_OPTION); - if(action != JOptionPane.OK_OPTION) { - throw new IOException("Cancel, X or escape key selected"); - } - connDialog.setVisible(true); - return new String(pwd.getPassword()); - } + * + * @param port: remote sshd port + * @param hostname: remote host + * @param username: remote account + * @throws IOException in case of failure + */ + public GUISSHControlStream(String hostname, String username, int port, JDialog connDialog) { + super(hostname, username, port); + this.connDialog = connDialog; + } + + public String getPassword(String message) throws IOException { + connDialog.setVisible(false); + System.out.println(message); + JPasswordField pwd = new JPasswordField(10); + int action = JOptionPane.showConfirmDialog(null, pwd, "Enter Password", JOptionPane.OK_CANCEL_OPTION); + if (action != JOptionPane.OK_OPTION) { + throw new IOException("Cancel, X or escape key selected"); + } + connDialog.setVisible(true); + return new String(pwd.getPassword()); + } } diff --git a/src/lia/util/net/copy/gui/HelpDialog.java b/src/lia/util/net/copy/gui/HelpDialog.java index cf0a6ae..0957af4 100644 --- a/src/lia/util/net/copy/gui/HelpDialog.java +++ b/src/lia/util/net/copy/gui/HelpDialog.java @@ -3,89 +3,80 @@ */ package lia.util.net.copy.gui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; import java.net.URL; -import javax.swing.BoxLayout; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; - public class HelpDialog extends JDialog { - - public HelpDialog(JFrame parent) { - super(parent, "Key mappings", true); - getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); - getContentPane().add(constructPanel("F5", "Copy selected files")); - getContentPane().add(constructPanel("F7", "Create new dir")); - getContentPane().add(constructPanel("F8/Del", "Remove selected files")); - getContentPane().add(constructPanel("F10", "Quit the application")); - getContentPane().add(constructPanel("Tab", "Change focus")); - getContentPane().add(constructPanel("Enter", "Change selected directory")); - getContentPane().add(constructPanel("Arrows up/down", "Moves the cursor")); - getContentPane().add(constructPanel("Ctrl/Shift + Arrows up/down", "Select a range of files")); - - setVisible(false); - setSize(200,100); - setBackground(new Color(15724527)); - setResizable(false); + private ImageIcon caltechIcon; + + public HelpDialog(JFrame parent) { + super(parent, "Key mappings", true); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + + getContentPane().add(constructPanel("F5", "Copy selected files")); + getContentPane().add(constructPanel("F7", "Create new dir")); + getContentPane().add(constructPanel("F8/Del", "Remove selected files")); + getContentPane().add(constructPanel("F10", "Quit the application")); + getContentPane().add(constructPanel("Tab", "Change focus")); + getContentPane().add(constructPanel("Enter", "Change selected directory")); + getContentPane().add(constructPanel("Arrows up/down", "Moves the cursor")); + getContentPane().add(constructPanel("Ctrl/Shift + Arrows up/down", "Select a range of files")); + + setVisible(false); + setSize(200, 100); + setBackground(new Color(15724527)); + setResizable(false); + + JButton okButton = new JButton("OK"); + okButton.setMinimumSize(new Dimension(190, 22)); + okButton.setPreferredSize(new Dimension(190, 22)); + JPanel p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + p1.add(okButton, BorderLayout.CENTER); + getContentPane().add(p1); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + pack(); + } + + private final JPanel constructPanel(String key, String label) { + final JPanel p = new JPanel(); + p.setOpaque(false); + p.setLayout(new BorderLayout()); + JLabel l = new JLabel("

    " + key + " - " + label + "

    "); + p.add(l, BorderLayout.CENTER); + return p; + } + + /** + * Shows or hides the component depending on the Boolean flag b. + * + * @param b if true, show the component; otherwise, hide the + * component. + * @See java.awt.Component#isVisible + */ + public void setVisible(boolean b) { + if (b) { + Dimension bounds = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension abounds = getSize(); + setLocation((bounds.width - abounds.width) / 2, (bounds.height - abounds.height) / 3); + } + super.setVisible(b); + } - JButton okButton = new JButton("OK"); - okButton.setMinimumSize(new Dimension(190, 22)); - okButton.setPreferredSize(new Dimension(190, 22)); - JPanel p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - p1.add(okButton, BorderLayout.CENTER); - getContentPane().add(p1); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); - pack(); - } - - private final JPanel constructPanel(String key, String label) { - final JPanel p = new JPanel(); - p.setOpaque(false); - p.setLayout(new BorderLayout()); - JLabel l = new JLabel("

    "+key+" - "+label+"

    "); - p.add(l, BorderLayout.CENTER); - return p; - } - - /** - * Shows or hides the component depending on the Boolean flag b. - * @param b if true, show the component; otherwise, hide the - * component. - * @See java.awt.Component#isVisible - */ - public void setVisible(boolean b) { - if(b) { - Dimension bounds = Toolkit.getDefaultToolkit().getScreenSize(); - Dimension abounds = getSize(); - setLocation((bounds.width - abounds.width) / 2, (bounds.height - abounds.height) / 3); - } - super.setVisible(b); - } - - private ImageIcon caltechIcon; - public Icon getCaltechIcon() { - if (caltechIcon != null) return caltechIcon; - try { - URL url = getClass().getResource("icons/caltech.gif"); - caltechIcon = new ImageIcon(url); - } catch (Throwable t) { } - return caltechIcon; - } + public Icon getCaltechIcon() { + if (caltechIcon != null) return caltechIcon; + try { + URL url = getClass().getResource("icons/caltech.gif"); + caltechIcon = new ImageIcon(url); + } catch (Throwable t) { + } + return caltechIcon; + } } diff --git a/src/lia/util/net/copy/gui/PreferencesHandler.java b/src/lia/util/net/copy/gui/PreferencesHandler.java index 4d52752..781e24c 100644 --- a/src/lia/util/net/copy/gui/PreferencesHandler.java +++ b/src/lia/util/net/copy/gui/PreferencesHandler.java @@ -3,100 +3,98 @@ */ package lia.util.net.copy.gui; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; /** - * * @author Ciprian Dobre */ public class PreferencesHandler { - /** Logger used by this class */ - static final transient Logger logger = Logger.getLogger(PreferencesHandler.class.getCanonicalName()); - static String sPrefsFile; + /** + * Logger used by this class + */ + static final transient Logger logger = Logger.getLogger(PreferencesHandler.class.getCanonicalName()); + final static Properties p = new Properties(); + static String sPrefsFile; - final static Properties p = new Properties(); - static { - sPrefsFile = System.getProperty("user.home", ".") + System.getProperty("file.separator") + ".fdt"+ - System.getProperty("file.separator")+"gui.props"; - try { // first try and check if the dirs exists... - final File file = new File(System.getProperty("user.home", ".") + System.getProperty("file.separator") + ".fdt"); - file.mkdirs(); - } catch (Throwable t) { } - try { - //check we can write here - final File file = new File(sPrefsFile); - if(file.createNewFile()) - file.delete(); - } catch (IOException e) { - // resolve problems with buggy system reporting user.home as the parent dir for user dirs - sPrefsFile = System.getProperty("user.home", ".") + System.getProperty("file.separator") + System.getProperty("user.name") + System.getProperty("file.separator") - + ".fdt"+System.getProperty("file.separator")+"gui.props"; - } - System.out.println("Using [" + sPrefsFile +"] as preferences file"); - load(); - } + static { + sPrefsFile = System.getProperty("user.home", ".") + System.getProperty("file.separator") + ".fdt" + + System.getProperty("file.separator") + "gui.props"; + try { // first try and check if the dirs exists... + final File file = new File(System.getProperty("user.home", ".") + System.getProperty("file.separator") + ".fdt"); + file.mkdirs(); + } catch (Throwable t) { + } + try { + //check we can write here + final File file = new File(sPrefsFile); + if (file.createNewFile()) + file.delete(); + } catch (IOException e) { + // resolve problems with buggy system reporting user.home as the parent dir for user dirs + sPrefsFile = System.getProperty("user.home", ".") + System.getProperty("file.separator") + System.getProperty("user.name") + System.getProperty("file.separator") + + ".fdt" + System.getProperty("file.separator") + "gui.props"; + } + System.out.println("Using [" + sPrefsFile + "] as preferences file"); + load(); + } - public static synchronized void load() { - // Create an input stream on a file - InputStream is = null; - try { - File f = new File(sPrefsFile); - if (!f.exists()) - return; - is = new FileInputStream(f); - } catch (FileNotFoundException e) { - logger.log(Level.WARNING, "Got exception " + e.getLocalizedMessage(), e); - } - // Import preference data - try { - p.load(is); - } catch (IOException e) { - logger.log(Level.WARNING, "Got exception " + e.getLocalizedMessage(), e); - } - } + public static synchronized void load() { + // Create an input stream on a file + InputStream is = null; + try { + File f = new File(sPrefsFile); + if (!f.exists()) + return; + is = new FileInputStream(f); + } catch (FileNotFoundException e) { + logger.log(Level.WARNING, "Got exception " + e.getLocalizedMessage(), e); + } + // Import preference data + try { + p.load(is); + } catch (IOException e) { + logger.log(Level.WARNING, "Got exception " + e.getLocalizedMessage(), e); + } + } - public static synchronized void save() { - try { - File f = new File(sPrefsFile); - try { - f.createNewFile(); - } catch (Throwable t) { } - FileOutputStream fos = new FileOutputStream(f); - // Export the node to a file - p.store(fos, null); - fos.close(); - } catch (IOException e) { - logger.log(Level.WARNING, "Got exception " + e.getLocalizedMessage(), e); - } - } + public static synchronized void save() { + try { + File f = new File(sPrefsFile); + try { + f.createNewFile(); + } catch (Throwable t) { + } + FileOutputStream fos = new FileOutputStream(f); + // Export the node to a file + p.store(fos, null); + fos.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Got exception " + e.getLocalizedMessage(), e); + } + } - public static synchronized String get(String key, String def) { - return p.getProperty(key, def); - } + public static synchronized String get(String key, String def) { + return p.getProperty(key, def); + } - public static synchronized void put(String key, String value) { - p.setProperty(key, value); - } + public static synchronized void put(String key, String value) { + p.setProperty(key, value); + } - public static synchronized boolean getBoolean(String key, boolean def) { - try { - return Boolean.valueOf(p.getProperty(key, "" + def)); - } catch (Exception e) { - return def; - } - } + public static synchronized boolean getBoolean(String key, boolean def) { + try { + return Boolean.valueOf(p.getProperty(key, "" + def)); + } catch (Exception e) { + return def; + } + } - public static synchronized void putBoolean(String key, boolean value) { - p.setProperty(key, "" + value); - } + public static synchronized void putBoolean(String key, boolean value) { + p.setProperty(key, "" + value); + } } diff --git a/src/lia/util/net/copy/gui/ProgressBarUI.java b/src/lia/util/net/copy/gui/ProgressBarUI.java index cd8b0f9..d38ccfa 100644 --- a/src/lia/util/net/copy/gui/ProgressBarUI.java +++ b/src/lia/util/net/copy/gui/ProgressBarUI.java @@ -1,169 +1,153 @@ package lia.util.net.copy.gui; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GradientPaint; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Insets; -import java.awt.event.ActionListener; -import java.awt.image.BufferedImage; -import java.awt.image.ImageObserver; - -import javax.swing.JComponent; -import javax.swing.JProgressBar; -import javax.swing.plaf.ComponentUI; +import javax.swing.*; +import javax.swing.plaf.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; public class ProgressBarUI extends javax.swing.plaf.ProgressBarUI implements ImageObserver, -ActionListener { + ActionListener { - private static final GradientPaint foregroundColour = new GradientPaint(0, 0, new Color(85, 82, 137), 300, 0, new Color(137, 255, 0)); - private static final Color backgroundColour = new Color(238, 241, 238); + private static final GradientPaint foregroundColour = new GradientPaint(0, 0, new Color(85, 82, 137), 300, 0, new Color(137, 255, 0)); + private static final Color backgroundColour = new Color(238, 241, 238); - private static final int STRIPE_SIZE = 11; + private static final int STRIPE_SIZE = 11; + public int startOffset = 0; + JComponent c; + private BufferedImage image = null; + private javax.swing.Timer timer = null; + private javax.swing.plaf.ProgressBarUI defaultUI; - private BufferedImage image = null; + public static ComponentUI createUI(JComponent c) { + return new ProgressBarUI(); + } - public int startOffset = 0; - - private javax.swing.Timer timer = null; - - private javax.swing.plaf.ProgressBarUI defaultUI; - - public static ComponentUI createUI(JComponent c) { - return new ProgressBarUI(); - } - - - JComponent c; - - public void paint(Graphics g, JComponent c) { + public void paint(Graphics g, JComponent c) { // Get size - Insets insets = c.getInsets(); - - this.c = c; - - int x = insets.left; - int y = insets.top; - int width = c.getWidth() - insets.left - insets.right; - int height = c.getHeight() - insets.top - insets.bottom; - - JProgressBar bar = (JProgressBar)c; - int minimum = bar.getMinimum(); - int maximum = bar.getMaximum(); - int value = bar.getValue(); - - if(bar.isIndeterminate()) { - paintIndeterminateTimeProgress(g, x, y, width, height); - if (timer == null) - timer = new javax.swing.Timer(20, this); - timer.start(); - } - else { - if (timer != null) - timer.stop(); - - paintProgress(g, x, y, width, height, minimum, maximum, value); - } - if (((JProgressBar)c).isBorderPainted()) { - g.setColor(Color.black); - g.drawRect(x,y,width-1,height-1); - } - } - - private void paintProgress(Graphics g, int x, int y, int width, - int height, int minimum, int maximum, - int value) { - float percent = - (float)(value - minimum) / - (float)(maximum - minimum); - - int highlightWidth = (int)(width * percent); - ((Graphics2D)g).setPaint(foregroundColour); - g.fillRect(0, 0, width, height); - - if(highlightWidth < width) { - g.setColor(backgroundColour); - g.fillRect(highlightWidth + 1, y, width - highlightWidth, - height); - } - } - - private void paintIndeterminateTimeProgress(Graphics g, int x, int y, - int width, int height) { + Insets insets = c.getInsets(); + + this.c = c; + + int x = insets.left; + int y = insets.top; + int width = c.getWidth() - insets.left - insets.right; + int height = c.getHeight() - insets.top - insets.bottom; + + JProgressBar bar = (JProgressBar) c; + int minimum = bar.getMinimum(); + int maximum = bar.getMaximum(); + int value = bar.getValue(); + + if (bar.isIndeterminate()) { + paintIndeterminateTimeProgress(g, x, y, width, height); + if (timer == null) + timer = new javax.swing.Timer(20, this); + timer.start(); + } else { + if (timer != null) + timer.stop(); + + paintProgress(g, x, y, width, height, minimum, maximum, value); + } + if (((JProgressBar) c).isBorderPainted()) { + g.setColor(Color.black); + g.drawRect(x, y, width - 1, height - 1); + } + } + + private void paintProgress(Graphics g, int x, int y, int width, + int height, int minimum, int maximum, + int value) { + float percent = + (float) (value - minimum) / + (float) (maximum - minimum); + + int highlightWidth = (int) (width * percent); + ((Graphics2D) g).setPaint(foregroundColour); + g.fillRect(0, 0, width, height); + + if (highlightWidth < width) { + g.setColor(backgroundColour); + g.fillRect(highlightWidth + 1, y, width - highlightWidth, + height); + } + } + + private void paintIndeterminateTimeProgress(Graphics g, int x, int y, + int width, int height) { // Create buffer of stripe pattern if we havent already - if(image == null) { + if (image == null) { // Create buffer image longer than main image so we can // create scrolling effect merely by drawing it at // an offset - int bufferWidth = width + 4 * STRIPE_SIZE; + int bufferWidth = width + 4 * STRIPE_SIZE; - image = new BufferedImage(bufferWidth,height, - BufferedImage.TYPE_3BYTE_BGR); - Graphics bufferGraphics = image.getGraphics(); + image = new BufferedImage(bufferWidth, height, + BufferedImage.TYPE_3BYTE_BGR); + Graphics bufferGraphics = image.getGraphics(); // Fill background - bufferGraphics.setColor(backgroundColour); - bufferGraphics.fillRect(0, 0, bufferWidth, height); + bufferGraphics.setColor(backgroundColour); + bufferGraphics.fillRect(0, 0, bufferWidth, height); - int xoffset = 0; + int xoffset = 0; // Draw pattern - ((Graphics2D)bufferGraphics).setPaint(foregroundColour); + ((Graphics2D) bufferGraphics).setPaint(foregroundColour); - for(int yoffset = 0; yoffset <= height; yoffset++) { + for (int yoffset = 0; yoffset <= height; yoffset++) { - drawStrippedLine(bufferGraphics, STRIPE_SIZE, - xoffset, yoffset, bufferWidth); - xoffset++; + drawStrippedLine(bufferGraphics, STRIPE_SIZE, + xoffset, yoffset, bufferWidth); + xoffset++; - if(xoffset >= STRIPE_SIZE * 2) - xoffset = 0; - } - } + if (xoffset >= STRIPE_SIZE * 2) + xoffset = 0; + } + } // Draw image to screen - g.drawImage(image, -startOffset, y, this); + g.drawImage(image, -startOffset, y, this); // Draw at different offset next time to get "movement" // pattern - startOffset += 1; - - if(startOffset >= STRIPE_SIZE * 2) - startOffset -= STRIPE_SIZE * 2; - } - - private void drawStrippedLine(Graphics g, int stripeSize, int x, int y, - int width) { - - int xoffset = x; - - while(xoffset < width) { - g.drawLine(xoffset, - y, - xoffset + stripeSize, - y); - xoffset += stripeSize * 2; - } - } - - public boolean imageUpdate(Image image, int infofloags, int x, int y, - int width, int height) { - return true; - } - - public void actionPerformed(java.awt.event.ActionEvent actionEvent) { - if (c != null) - c.repaint(); - } - - public Dimension getMinimumSize(JComponent component) { - return new Dimension(50, 15); - } - - public Dimension getPreferredSize(JComponent component) { - return new Dimension(50, 15); - } + startOffset += 1; + + if (startOffset >= STRIPE_SIZE * 2) + startOffset -= STRIPE_SIZE * 2; + } + + private void drawStrippedLine(Graphics g, int stripeSize, int x, int y, + int width) { + + int xoffset = x; + + while (xoffset < width) { + g.drawLine(xoffset, + y, + xoffset + stripeSize, + y); + xoffset += stripeSize * 2; + } + } + + public boolean imageUpdate(Image image, int infofloags, int x, int y, + int width, int height) { + return true; + } + + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (c != null) + c.repaint(); + } + + public Dimension getMinimumSize(JComponent component) { + return new Dimension(50, 15); + } + + public Dimension getPreferredSize(JComponent component) { + return new Dimension(50, 15); + } } diff --git a/src/lia/util/net/copy/gui/RemoteSessionManager.java b/src/lia/util/net/copy/gui/RemoteSessionManager.java index 3907ca0..cf4a3d3 100644 --- a/src/lia/util/net/copy/gui/RemoteSessionManager.java +++ b/src/lia/util/net/copy/gui/RemoteSessionManager.java @@ -3,19 +3,6 @@ */ package lia.util.net.copy.gui; -import java.awt.BorderLayout; -import java.util.HashMap; -import java.util.Map; -import java.util.Vector; -import java.util.concurrent.RunnableScheduledFuture; -import java.util.concurrent.TimeUnit; - -import javax.swing.BoxLayout; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JProgressBar; - import lia.util.net.common.Config; import lia.util.net.common.ControlStream; import lia.util.net.common.Utils; @@ -27,564 +14,642 @@ import lia.util.net.copy.transport.gui.GUIControlChannelNotifier; import lia.util.net.copy.transport.gui.GUIMessage; +import javax.swing.*; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.RunnableScheduledFuture; +import java.util.concurrent.TimeUnit; + /** - * The manager is responsable with sending and receiving commands + * The manager is responsable with sending and receiving commands * through the communication channel between the client and the server - * + * * @author Ciprian Dobre */ public class RemoteSessionManager implements GUIControlChannelNotifier, Runnable { - final ClientSessionManager clientSessionManager = new ClientSessionManager(); - String hostname; - int port; - - ConnectMonitor connd; - - private FolderTable remoteTable; - private FolderTable localTable; - private GUIControlChannel channel; - - private String workingDir; - private boolean canWrite; - private String userHome; - private String osName; - private String freeSpace; - private String fileSeparator; - private Vector fileList; - private Exception remoteEx = null; - private String[] roots; - private boolean isRoot; - private HashMap shortNames; - - private final FDTPropsDialog props; - private Session localSession; - private Session remoteSession; - - private static final Object lock = new Object(); - - boolean initiated = false; - - private JPanel p; - - private RemoteSessionMonitor monitor; - - private RunnableScheduledFuture remoteSessionTask; - private RunnableScheduledFuture monitorTask; - - private static final long timeout = 3 * 1000L; - - public RemoteSessionManager(FDTPropsDialog props, JPanel panel) { - this.props = props; - this.p = panel; - } - - public void setSessions(Session localSession, Session remoteSession) { - this.localSession = localSession; - this.remoteSession = remoteSession; - } - - /** Receives the current directory of the session.. (default user home) */ - public String getWorkingDirectory() { - return workingDir; - } - - public String getFileSeparator() { - synchronized (lock) { - while (!initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - if (fileSeparator == null) // problem occuring... ? - return System.getProperty("file.separator"); - return fileSeparator; - } - - public boolean canWrite() { - synchronized (lock) { - while (!initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - return canWrite; - } - - /** Receives the directory home of the user */ - public String getUserHome() { - synchronized (lock) { - while (!initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - return userHome; - } - - /** Receives the name of the operating system */ - public String getOSName() { - synchronized (lock) { - while (!initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - return osName; - } - - /** Receives the amoung of free space */ - public String freeSpace() { - synchronized (lock) { - while (fileList == null || !initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - return freeSpace; - } - - /** Return true if the current working directory is a root (no upper level) */ - public boolean isRoot() { - if (channel == null) return false; - try { - // send request... + private static final Object lock = new Object(); + private static final long timeout = 3 * 1000L; + final ClientSessionManager clientSessionManager = new ClientSessionManager(); + private final FDTPropsDialog props; + String hostname; + int port; + ConnectMonitor connd; + boolean initiated = false; + private FolderTable remoteTable; + private FolderTable localTable; + private GUIControlChannel channel; + private String workingDir; + private boolean canWrite; + private String userHome; + private String osName; + private String freeSpace; + private String fileSeparator; + private Vector fileList; + private Exception remoteEx = null; + private String[] roots; + private boolean isRoot; + private HashMap shortNames; + private Session localSession; + private Session remoteSession; + private JPanel p; + private RemoteSessionMonitor monitor; + private RunnableScheduledFuture remoteSessionTask; + private RunnableScheduledFuture monitorTask; + + public RemoteSessionManager(FDTPropsDialog props, JPanel panel) { + this.props = props; + this.p = panel; + } + + public void setSessions(Session localSession, Session remoteSession) { + this.localSession = localSession; + this.remoteSession = remoteSession; + } + + /** + * Receives the current directory of the session.. (default user home) + */ + public String getWorkingDirectory() { + return workingDir; + } + + public String getFileSeparator() { + synchronized (lock) { + while (!initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + if (fileSeparator == null) // problem occuring... ? + return System.getProperty("file.separator"); + return fileSeparator; + } + + public boolean canWrite() { + synchronized (lock) { + while (!initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + return canWrite; + } + + /** + * Receives the directory home of the user + */ + public String getUserHome() { + synchronized (lock) { + while (!initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + return userHome; + } + + /** + * Receives the name of the operating system + */ + public String getOSName() { + synchronized (lock) { + while (!initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + return osName; + } + + /** + * Receives the amoung of free space + */ + public String freeSpace() { + synchronized (lock) { + while (fileList == null || !initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + return freeSpace; + } + + /** + * Return true if the current working directory is a root (no upper level) + */ + public boolean isRoot() { + if (channel == null) return false; + try { + // send request... // CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(5, null)); // channel.sendCtrlMessage(c); - synchronized (lock) { - while (fileList == null || !initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - return isRoot; - } catch (Throwable t) { - return false; - } - } - - /** Receives the list of current files and folder in the current directory of the session */ - public Vector getFileList() { - synchronized (lock) { - while (fileList == null || !initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - return fileList; - } - - public void removeFiles(String files[]) throws Exception { - if (channel == null) return; - fileList = null; - // send request - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(10, files)); - channel.sendCtrlMessage(c); - synchronized (lock) { - while (fileList == null) { - try { lock.wait(timeout); } catch (Throwable a) { } - } - } - Exception tmpEx = remoteEx; - remoteEx = null; - if (tmpEx != null) { - throw tmpEx; - } - } - - public void createDir(String name) throws Exception { - if (channel == null) return; - fileList = null; - // send request - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(11, name)); - channel.sendCtrlMessage(c); - - synchronized (lock) { - while (fileList == null) { - try { lock.wait(timeout); } catch (Throwable a) { } - } - } - Exception tmpEx = remoteEx; - remoteEx = null; - if (tmpEx != null) { - throw tmpEx; - } - } - - /** Send the command to the other end to set the current working directory to an absolute pathname */ - public void setAbsoluteDir(String dir) throws Exception { - if (channel == null) return; - workingDir = null; - fileList = null; - // send request - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(6, dir)); - channel.sendCtrlMessage(c); - synchronized (lock) { - while (fileList == null) { - try { lock.wait(timeout); } catch (Throwable a) { } - } - } - Exception tmpEx = remoteEx; - remoteEx = null; - if (tmpEx != null) { - throw tmpEx; - } - } - - /** Send the command to the other end to set the current working directory to a relative to the current dir pathname */ - public void setRelativeDir(String dir) throws Exception { - if (channel == null) return; - workingDir = null; - fileList = null; - // send request - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(7, dir)); - channel.sendCtrlMessage(c); - synchronized (lock) { - while (fileList == null) { - try { lock.wait(timeout); } catch (Throwable a) { } - } - } - Exception tmpEx = remoteEx; - remoteEx = null; - if (tmpEx != null) { - throw tmpEx; - } - } - - /** Send the command to the other end to set the current working directory up one level */ - public void setUpDir() throws Exception { - if (channel == null) return; - workingDir = null; - fileList = null; - // send request - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(8, null)); - channel.sendCtrlMessage(c); - synchronized (lock) { - while (fileList == null) { - try { lock.wait(timeout); } catch (Throwable a) { } - } - } - Exception tmpEx = remoteEx; - remoteEx = null; - if (tmpEx != null) { - throw tmpEx; - } - } - - /** Receives the known root pathes of the remote FS */ - public String[] getRoots() { - synchronized (lock) { - while (!initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - if (roots == null) { - roots = new String[] { "" }; - } - return roots; - } - - /** Receives the pathname denoted by a root folder (for the special case of links...) */ - public String getShortRootName(String rootFolder) { - synchronized (lock) { - while (!initiated) { - try { lock.wait(timeout); } catch (Throwable t) { } - } - } - if (rootFolder == null || shortNames == null || !shortNames.containsKey(rootFolder)) return null; - return shortNames.get(rootFolder); - } - - /** Called to initiatilize a file transfer */ - public String initiateTransfer(String files[], boolean push, boolean isRecursive) { - String destDir = "."; - if (push) { - if (!remoteSession.canWrite()) - return "Can not write to "+remoteSession.getWorkingDir(); - destDir = remoteSession.getWorkingDir(); - } else { - if (!localSession.canWrite()) - return "Can not write to "+localSession.getWorkingDir(); - destDir = localSession.getWorkingDir(); - } - return clientSessionManager.initTransfer(hostname, port, !push, files, destDir, props, isRecursive); - } - - /** Called in order to interrogate on the status of the current transfer */ - public double getTransferPercent() { - return clientSessionManager.transferProgress(); - } - - public String getTransferSpeed() { - return clientSessionManager.currentSpeed(); - } - - /** Called in order to stop the current transfer */ - public void stopTransfer() { - clientSessionManager.cancelTransfer(); - end(); - } - - public void end() { - clientSessionManager.end(); - } - - public void setCorrespondingPanel(FolderTable localTable, FolderTable remoteTable) { - this.localTable = localTable; - this.remoteTable = remoteTable; - if (channel != null) { - if (remoteTable != null) - remoteTable.setConnected(true); - } - } - - public void run() { - connd.setProgress(); - } - - /** Called in order to construct the special FDTGui channel between the client and the server... */ - public void connect(final String hostName, final String user, final int port) { - - if (connd == null) - connd = new ConnectMonitor(p); - remoteSessionTask = (RunnableScheduledFuture)Utils.getMonitoringExecService().scheduleWithFixedDelay(this, 1, 200, TimeUnit.MILLISECONDS); - connd.setVisible(true); - - final RemoteSessionManager _instance = this; - - new Thread(new Runnable() { - public void run() { - boolean auth = true; - if (user != null) { - try { - // initialize the config - Map confMap = new HashMap(); - confMap.put("-p", "" + port); - confMap.put("SCPSyntaxUsed", Boolean.TRUE); - try { - Config.initInstance(confMap); - } catch (Throwable t1) { - t1.printStackTrace(); - } - - // try to authenticate... - ControlStream sshConn = new GUISSHControlStream(hostName, user, connd); - sshConn.connect(); - - // start remote fdt - Config config = Config.getInstance(); - String localAddresses = config.getLocalAddresses(); - // append the required options to the configurable java - // command - String remoteCmd = config.getRemoteCommand() + " -p " + config.getPort() + " -silent -S -f " + localAddresses; - System.err.println(" [ CONFIG ] Starting FDT server over SSH using [ " + remoteCmd + " ]"); - sshConn.startProgram(remoteCmd); - sshConn.waitForControlMessage("READY"); - System.err.println(" [ CONFIG ] FDT server successfully started on [ " + config.getHostName() + " ]"); - - auth = true; - } catch (Throwable t) { - channel = null; - initiated = true; - auth = false; - } - } - if (auth) { - try { - if (channel != null) - channel.close("New connection", null); - channel = new GUIControlChannel(hostName, port, _instance); - new Thread(channel, "GUI Control channel for [ " + hostName + ":" + port + " ]").start(); - if (remoteTable != null) - remoteTable.setConnected(true); - _instance.hostname = hostName; - _instance.port = port; - Utils.getMonitoringExecService().remove(remoteSessionTask); - Utils.getMonitoringExecService().purge(); - connd.setVisible(false); - monitor = new RemoteSessionMonitor(); - monitorTask = (RunnableScheduledFuture)Utils.getMonitoringExecService().scheduleWithFixedDelay(monitor, 1, 500, TimeUnit.MILLISECONDS); - return; - } catch (Throwable t) { - try { channel.close(t.getLocalizedMessage(), t); } catch (Throwable tt) { } - channel = null; - t.printStackTrace(); - initiated = true; - } - } - Utils.getMonitoringExecService().remove(remoteSessionTask); - Utils.getMonitoringExecService().purge(); - connd.setVisible(false); - if (remoteTable != null) - remoteTable.setConnected(false); - } }).start(); - } - - private void process(GUIMessage msg) { - switch (msg.getMID()) { - case 0: // current working dir - { - Object o[] = (Object[])msg.getMsg(); - workingDir = (String)o[0]; - canWrite = (Boolean)o[1]; - break; - } - case 1: // user home - { - userHome = (String)msg.getMsg(); - break; - } - case 2: // os name - { - osName = (String)msg.getMsg(); - break; - } - case 3: // current files - { - fileList = (Vector)msg.getMsg(); - remoteEx = msg.getException(); - break; - } - case 4: // current roots - { - roots = (String[])msg.getMsg(); - break; - } - case 5: // is root - { - isRoot = ((Boolean)msg.getMsg()).booleanValue(); - break; - } - case 9: // short name - { - shortNames = (HashMap)msg.getMsg(); - break; - } - case 10: // file separator - { - fileSeparator = (String)msg.getMsg(); - break; - } - case 11: // end of init - { - initiated = true; + synchronized (lock) { + while (fileList == null || !initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + return isRoot; + } catch (Throwable t) { + return false; + } + } + + /** + * Receives the list of current files and folder in the current directory of the session + */ + public Vector getFileList() { + synchronized (lock) { + while (fileList == null || !initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + return fileList; + } + + public void removeFiles(String files[]) throws Exception { + if (channel == null) return; + fileList = null; + // send request + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(10, files)); + channel.sendCtrlMessage(c); + synchronized (lock) { + while (fileList == null) { + try { + lock.wait(timeout); + } catch (Throwable a) { + } + } + } + Exception tmpEx = remoteEx; + remoteEx = null; + if (tmpEx != null) { + throw tmpEx; + } + } + + public void createDir(String name) throws Exception { + if (channel == null) return; + fileList = null; + // send request + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(11, name)); + channel.sendCtrlMessage(c); + + synchronized (lock) { + while (fileList == null) { + try { + lock.wait(timeout); + } catch (Throwable a) { + } + } + } + Exception tmpEx = remoteEx; + remoteEx = null; + if (tmpEx != null) { + throw tmpEx; + } + } + + /** + * Send the command to the other end to set the current working directory to an absolute pathname + */ + public void setAbsoluteDir(String dir) throws Exception { + if (channel == null) return; + workingDir = null; + fileList = null; + // send request + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(6, dir)); + channel.sendCtrlMessage(c); + synchronized (lock) { + while (fileList == null) { + try { + lock.wait(timeout); + } catch (Throwable a) { + } + } + } + Exception tmpEx = remoteEx; + remoteEx = null; + if (tmpEx != null) { + throw tmpEx; + } + } + + /** + * Send the command to the other end to set the current working directory to a relative to the current dir pathname + */ + public void setRelativeDir(String dir) throws Exception { + if (channel == null) return; + workingDir = null; + fileList = null; + // send request + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(7, dir)); + channel.sendCtrlMessage(c); + synchronized (lock) { + while (fileList == null) { + try { + lock.wait(timeout); + } catch (Throwable a) { + } + } + } + Exception tmpEx = remoteEx; + remoteEx = null; + if (tmpEx != null) { + throw tmpEx; + } + } + + /** + * Send the command to the other end to set the current working directory up one level + */ + public void setUpDir() throws Exception { + if (channel == null) return; + workingDir = null; + fileList = null; + // send request + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(8, null)); + channel.sendCtrlMessage(c); + synchronized (lock) { + while (fileList == null) { + try { + lock.wait(timeout); + } catch (Throwable a) { + } + } + } + Exception tmpEx = remoteEx; + remoteEx = null; + if (tmpEx != null) { + throw tmpEx; + } + } + + /** + * Receives the known root pathes of the remote FS + */ + public String[] getRoots() { + synchronized (lock) { + while (!initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + if (roots == null) { + roots = new String[]{""}; + } + return roots; + } + + /** + * Receives the pathname denoted by a root folder (for the special case of links...) + */ + public String getShortRootName(String rootFolder) { + synchronized (lock) { + while (!initiated) { + try { + lock.wait(timeout); + } catch (Throwable t) { + } + } + } + if (rootFolder == null || shortNames == null || !shortNames.containsKey(rootFolder)) return null; + return shortNames.get(rootFolder); + } + + /** + * Called to initiatilize a file transfer + */ + public String initiateTransfer(String files[], boolean push, boolean isRecursive) { + String destDir = "."; + if (push) { + if (!remoteSession.canWrite()) + return "Can not write to " + remoteSession.getWorkingDir(); + destDir = remoteSession.getWorkingDir(); + } else { + if (!localSession.canWrite()) + return "Can not write to " + localSession.getWorkingDir(); + destDir = localSession.getWorkingDir(); + } + return clientSessionManager.initTransfer(hostname, port, !push, files, destDir, props, isRecursive); + } + + /** + * Called in order to interrogate on the status of the current transfer + */ + public double getTransferPercent() { + return clientSessionManager.transferProgress(); + } + + public String getTransferSpeed() { + return clientSessionManager.currentSpeed(); + } + + /** + * Called in order to stop the current transfer + */ + public void stopTransfer() { + clientSessionManager.cancelTransfer(); + end(); + } + + public void end() { + clientSessionManager.end(); + } + + public void setCorrespondingPanel(FolderTable localTable, FolderTable remoteTable) { + this.localTable = localTable; + this.remoteTable = remoteTable; + if (channel != null) { + if (remoteTable != null) + remoteTable.setConnected(true); + } + } + + public void run() { + connd.setProgress(); + } + + /** + * Called in order to construct the special FDTGui channel between the client and the server... + */ + public void connect(final String hostName, final String user, final int port) { + + if (connd == null) + connd = new ConnectMonitor(p); + remoteSessionTask = (RunnableScheduledFuture) Utils.getMonitoringExecService().scheduleWithFixedDelay(this, 1, 200, TimeUnit.MILLISECONDS); + connd.setVisible(true); + + final RemoteSessionManager _instance = this; + + new Thread(new Runnable() { + public void run() { + boolean auth = true; + if (user != null) { + try { + // initialize the config + Map confMap = new HashMap(); + confMap.put("-p", "" + port); + confMap.put("SCPSyntaxUsed", Boolean.TRUE); + try { + Config.initInstance(confMap); + } catch (Throwable t1) { + t1.printStackTrace(); + } + + // try to authenticate... + ControlStream sshConn = new GUISSHControlStream(hostName, user, connd); + sshConn.connect(); + + // start remote fdt + Config config = Config.getInstance(); + String localAddresses = config.getLocalAddresses(); + // append the required options to the configurable java + // command + String remoteCmd = config.getRemoteCommand() + " -p " + config.getPort() + " -silent -S -f " + localAddresses; + System.err.println(" [ CONFIG ] Starting FDT server over SSH using [ " + remoteCmd + " ]"); + sshConn.startProgram(remoteCmd); + sshConn.waitForControlMessage("READY"); + System.err.println(" [ CONFIG ] FDT server successfully started on [ " + config.getHostName() + " ]"); + + auth = true; + } catch (Throwable t) { + channel = null; + initiated = true; + auth = false; + } + } + if (auth) { + try { + if (channel != null) + channel.close("New connection", null); + channel = new GUIControlChannel(hostName, port, _instance); + new Thread(channel, "GUI Control channel for [ " + hostName + ":" + port + " ]").start(); + if (remoteTable != null) + remoteTable.setConnected(true); + _instance.hostname = hostName; + _instance.port = port; + Utils.getMonitoringExecService().remove(remoteSessionTask); + Utils.getMonitoringExecService().purge(); + connd.setVisible(false); + monitor = new RemoteSessionMonitor(); + monitorTask = (RunnableScheduledFuture) Utils.getMonitoringExecService().scheduleWithFixedDelay(monitor, 1, 500, TimeUnit.MILLISECONDS); + return; + } catch (Throwable t) { + try { + channel.close(t.getLocalizedMessage(), t); + } catch (Throwable tt) { + } + channel = null; + t.printStackTrace(); + initiated = true; + } + } + Utils.getMonitoringExecService().remove(remoteSessionTask); + Utils.getMonitoringExecService().purge(); + connd.setVisible(false); + if (remoteTable != null) + remoteTable.setConnected(false); + } + }).start(); + } + + private void process(GUIMessage msg) { + switch (msg.getMID()) { + case 0: // current working dir + { + Object o[] = (Object[]) msg.getMsg(); + workingDir = (String) o[0]; + canWrite = (Boolean) o[1]; + break; + } + case 1: // user home + { + userHome = (String) msg.getMsg(); + break; + } + case 2: // os name + { + osName = (String) msg.getMsg(); + break; + } + case 3: // current files + { + fileList = (Vector) msg.getMsg(); + remoteEx = msg.getException(); + break; + } + case 4: // current roots + { + roots = (String[]) msg.getMsg(); + break; + } + case 5: // is root + { + isRoot = ((Boolean) msg.getMsg()).booleanValue(); + break; + } + case 9: // short name + { + shortNames = (HashMap) msg.getMsg(); + break; + } + case 10: // file separator + { + fileSeparator = (String) msg.getMsg(); + break; + } + case 11: // end of init + { + initiated = true; // synchronized (lock) { // lock.notifyAll(); // } - break; - } - case 12: // freeSpace - { - freeSpace = (String)msg.getMsg(); - break; - } - } - synchronized (lock) { - lock.notifyAll(); - } - } - - public void sendMessage(GUIMessage msg) throws Exception { - if (channel != null) { - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, msg); - channel.sendCtrlMessage(c); - } - } - - public boolean isConnected() { - return channel != null; - } - - public void notifyCtrlMsg(GUIControlChannel controlChannel, Object o) throws FDTProcolException { - if (o == null) return; - if (o instanceof GUIMessage) { - process((GUIMessage)o); - return; - } - if (!(o instanceof CtrlMsg)) return; - CtrlMsg msg = (CtrlMsg)o; - if (msg.tag != CtrlMsg.GUI_MSG) return; // only this type of message can be processed here - process((GUIMessage)msg.message); - } - - public void notifyCtrlSessionDown(GUIControlChannel controlChannel, Throwable cause) throws FDTProcolException { - if (remoteTable != null) - remoteTable.setConnected(false); - } - - public FolderTable getLocalTable() { - return localTable; - } - - public FolderTable getRemoteTable() { - return remoteTable; - } - - public static class ConnectMonitor extends JDialog { - - private JProgressBar progress; - private JLabel label; - - public ConnectMonitor(JPanel component) { - super(); - JDialog.setDefaultLookAndFeelDecorated(true); - setLocation((int)component.getLocationOnScreen().getX() + component.getWidth() / 2 - 160, (int)component.getLocationOnScreen().getY() + component.getHeight() / 2 - 62); - setTitle("Connecting"); - setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - setAlwaysOnTop(true); - setDefaultLookAndFeelDecorated(true); - getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); - JPanel p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - label = new JLabel("Connecting"); - p1.add(label, BorderLayout.CENTER); - getContentPane().add(p1); - JPanel p2 = new JPanel(); - p2.setLayout(new BorderLayout()); - progress = new JProgressBar(); - progress.setIndeterminate(true); + break; + } + case 12: // freeSpace + { + freeSpace = (String) msg.getMsg(); + break; + } + } + synchronized (lock) { + lock.notifyAll(); + } + } + + public void sendMessage(GUIMessage msg) throws Exception { + if (channel != null) { + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, msg); + channel.sendCtrlMessage(c); + } + } + + public boolean isConnected() { + return channel != null; + } + + public void notifyCtrlMsg(GUIControlChannel controlChannel, Object o) throws FDTProcolException { + if (o == null) return; + if (o instanceof GUIMessage) { + process((GUIMessage) o); + return; + } + if (!(o instanceof CtrlMsg)) return; + CtrlMsg msg = (CtrlMsg) o; + if (msg.tag != CtrlMsg.GUI_MSG) return; // only this type of message can be processed here + process((GUIMessage) msg.message); + } + + public void notifyCtrlSessionDown(GUIControlChannel controlChannel, Throwable cause) throws FDTProcolException { + if (remoteTable != null) + remoteTable.setConnected(false); + } + + public FolderTable getLocalTable() { + return localTable; + } + + public FolderTable getRemoteTable() { + return remoteTable; + } + + public static class ConnectMonitor extends JDialog { + + private JProgressBar progress; + private JLabel label; + + public ConnectMonitor(JPanel component) { + super(); + JDialog.setDefaultLookAndFeelDecorated(true); + setLocation((int) component.getLocationOnScreen().getX() + component.getWidth() / 2 - 160, (int) component.getLocationOnScreen().getY() + component.getHeight() / 2 - 62); + setTitle("Connecting"); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + setAlwaysOnTop(true); + setDefaultLookAndFeelDecorated(true); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + JPanel p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + label = new JLabel("Connecting"); + p1.add(label, BorderLayout.CENTER); + getContentPane().add(p1); + JPanel p2 = new JPanel(); + p2.setLayout(new BorderLayout()); + progress = new JProgressBar(); + progress.setIndeterminate(true); // progress.setUI(new ProgressBarUI()); - progress.setStringPainted(false); - p2.add(progress, BorderLayout.CENTER); - getContentPane().add(p2); - setSize(240, 75); - setResizable(false); - } - - public void setProgress() { - this.progress.setValue((this.progress.getValue() + 1) % 100); - this.progress.repaint(); - } - - public void setVisible(boolean visible) { - if (visible) - progress.setValue(0); - super.setVisible(visible); - } - } - - private class RemoteSessionMonitor implements Runnable { - - public RemoteSessionMonitor() { - } - - public void run() { + progress.setStringPainted(false); + p2.add(progress, BorderLayout.CENTER); + getContentPane().add(p2); + setSize(240, 75); + setResizable(false); + } + + public void setProgress() { + this.progress.setValue((this.progress.getValue() + 1) % 100); + this.progress.repaint(); + } + + public void setVisible(boolean visible) { + if (visible) + progress.setValue(0); + super.setVisible(visible); + } + } + + private class RemoteSessionMonitor implements Runnable { + + public RemoteSessionMonitor() { + } + + public void run() { // Thread.currentThread().setName("RemoteSessionMonitor[GUI]"); - if (channel == null || channel.isClosed()) { - System.out.println("Detected connection closed..."); - initiated = true; - synchronized (lock) { - lock.notifyAll(); // force awake - } - try { - Utils.getMonitoringExecService().remove(remoteSessionTask); - Utils.getMonitoringExecService().purge(); - } catch (Throwable t) { } - if (connd != null) - connd.setVisible(false); - if (remoteTable != null) - remoteTable.setConnected(false); - if (monitor != null) { - Utils.getMonitoringExecService().remove(monitorTask); - Utils.getMonitoringExecService().purge(); - monitor = null; - } - try { channel.close("Closed from the other end", new Exception()); } catch (Throwable tt) { } - channel = null; - } - } - } + if (channel == null || channel.isClosed()) { + System.out.println("Detected connection closed..."); + initiated = true; + synchronized (lock) { + lock.notifyAll(); // force awake + } + try { + Utils.getMonitoringExecService().remove(remoteSessionTask); + Utils.getMonitoringExecService().purge(); + } catch (Throwable t) { + } + if (connd != null) + connd.setVisible(false); + if (remoteTable != null) + remoteTable.setConnected(false); + if (monitor != null) { + Utils.getMonitoringExecService().remove(monitorTask); + Utils.getMonitoringExecService().purge(); + monitor = null; + } + try { + channel.close("Closed from the other end", new Exception()); + } catch (Throwable tt) { + } + channel = null; + } + } + } } diff --git a/src/lia/util/net/copy/gui/StatusBar.java b/src/lia/util/net/copy/gui/StatusBar.java index 93f016e..dc48053 100644 --- a/src/lia/util/net/copy/gui/StatusBar.java +++ b/src/lia/util/net/copy/gui/StatusBar.java @@ -3,235 +3,219 @@ */ package lia.util.net.copy.gui; -import java.awt.Color; -import java.awt.Rectangle; -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.ClipboardOwner; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import javax.swing.*; +import javax.swing.text.*; +import javax.swing.text.html.HTML.*; +import javax.swing.text.html.*; +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.*; import java.util.Enumeration; import java.util.LinkedList; -import java.util.concurrent.TimeUnit; - -import javax.swing.JScrollPane; -import javax.swing.JTextPane; -import javax.swing.SwingUtilities; -import javax.swing.text.AttributeSet; -import javax.swing.text.Element; -import javax.swing.text.Utilities; -import javax.swing.text.html.HTMLDocument; -import javax.swing.text.html.HTML.Tag; - -import lia.util.net.common.Utils; /** - * The status bar class - * - * @author Ciprian Dobre + * The status bar class + * + * @author Ciprian Dobre */ public class StatusBar extends JTextPane implements ClipboardOwner, Runnable { - private final LinkedList currentLines = new LinkedList(); - private String lastLine = ""; - private JScrollPane pane; - - private JTextPane p1; - - private static final Object lock = new Object(); - private boolean redoCalled = false; - - private final StatusBar _instance; - - public StatusBar(final JScrollPane pane) { - super(); - this.pane = pane; - pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - setOpaque(true); - setToolTipText("Status bar"); - setBackground(Color.white); - setContentType("text/html"); - - p1 = new JTextPane(); - p1.setOpaque(true); - p1.setToolTipText("Status bar"); - p1.setBackground(Color.white); - p1.setContentType("text/html"); - - final StatusBar textArea = this; - - textArea.addMouseListener( new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if ( SwingUtilities.isRightMouseButton(e) ) { - try { - int offset = viewToModel( e.getPoint() ); - int rowStart = Utilities.getRowStart(textArea, offset); - int rowEnd = Utilities.getRowEnd(textArea, offset); - synchronized (getTreeLock()) { - textArea.select(rowStart, rowEnd); - } - }catch (Exception e2) {} - } - } - }); - - textArea.addKeyListener( new KeyAdapter(){ - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_C && e.isControlDown()) { // copy - setClipboardContents(textArea.getSelectedText()); - } - } - }); + private static final Object lock = new Object(); + static String nl = "\n"; + private final LinkedList currentLines = new LinkedList(); + private final StatusBar _instance; + private String lastLine = ""; + private JScrollPane pane; + private JTextPane p1; + private boolean redoCalled = false; + + public StatusBar(final JScrollPane pane) { + super(); + this.pane = pane; + pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + setOpaque(true); + setToolTipText("Status bar"); + setBackground(Color.white); + setContentType("text/html"); + + p1 = new JTextPane(); + p1.setOpaque(true); + p1.setToolTipText("Status bar"); + p1.setBackground(Color.white); + p1.setContentType("text/html"); + + final StatusBar textArea = this; + + textArea.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e)) { + try { + int offset = viewToModel(e.getPoint()); + int rowStart = Utilities.getRowStart(textArea, offset); + int rowEnd = Utilities.getRowEnd(textArea, offset); + synchronized (getTreeLock()) { + textArea.select(rowStart, rowEnd); + } + } catch (Exception e2) { + } + } + } + }); + + textArea.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_C && e.isControlDown()) { // copy + setClipboardContents(textArea.getSelectedText()); + } + } + }); // Utils.getMonitoringExecService().scheduleWithFixedDelay(this, 1, 500, TimeUnit.MILLISECONDS); - _instance = this; - (new Thread(this)).start(); - } - - /** - * Place a String on the clipboard, and make this class the - * owner of the Clipboard's contents. - */ - public void setClipboardContents( String aString ){ - StringSelection stringSelection = new StringSelection( aString ); - Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - clipboard.setContents( stringSelection, this ); - } - - static String nl = "\n"; - - public synchronized void addText(String text) { - if (text == null) - return; - synchronized (currentLines) { - while (true) { - int index = text.indexOf(nl); - if (index >= 0) { - String str = text.substring(0, index); - if (lastLine.length() != 0) { - str = lastLine + str; - lastLine = ""; - } - currentLines.addLast(str); - if (currentLines.size() > 100) - currentLines.removeFirst(); - text = text.substring(index + nl.length()); - } else { - if (text.length() != 0) { - lastLine = lastLine + text; - } - break; - } - } - } - synchronized (lock) { - redoCalled = true; - lock.notifyAll(); - } - } - - public void run() { - Thread.currentThread().setName("[FDT StatusThread]"); - while (true) { - synchronized (lock) { - while (!redoCalled) { - try { lock.wait(); } catch (Exception e) { } - } - redoCalled = false; - } - redo(); - } - } - - private void redo() { - StringBuffer buf = new StringBuffer(); - boolean first = true; - synchronized (currentLines) { - buf.append(""); - for (String t : currentLines) { - if (!first) buf.append("
    "); - buf.append(t); - first = false; - } - if (lastLine.length() != 0) { - if (!first) buf.append("
    "); - buf.append(lastLine); - } - buf.append(""); - } - - p1.setText(buf.toString()); - synchronized (getTreeLock()) { - try { - setEditorKit(p1.getEditorKit()); - setDocument(p1.getDocument()); - pane.getViewport().scrollRectToVisible(new Rectangle(0, _instance.getPreferredSize().height,0,0)); - } catch (Throwable t) { - } - } - _instance.repaint(); - p1 = new JTextPane(); - p1.setOpaque(true); - p1.setToolTipText("Status bar"); - p1.setBackground(Color.white); - p1.setContentType("text/html"); - } - - /** - * Empty implementation of the ClipboardOwner interface. - */ - public void lostOwnership( Clipboard aClipboard, Transferable aContents) { - //do nothing - } - - public String getSelectedText(){ - return getSelectedHTMLAsText(); - } - - public String getSelectedHTMLAsText(){ - String text = super.getSelectedText(); - if(text == null || text.length() == 0){ - return text; - } - - int startPos = getSelectionStart(); - int endPos = getSelectionEnd(); - - int selectedCharacters = endPos - startPos; - StringBuffer buffer = new StringBuffer(selectedCharacters); - int nbspCount = 0; - for(int i = 0, j = startPos; i < selectedCharacters; i++, j++){ - Element element1 = ((HTMLDocument)getDocument()).getCharacterElement(j); - if(isReplaceWithNewLine(element1)){ - buffer.append('\n'); - }else{ - - buffer.append(text.charAt(i)); - } - - } - return buffer.toString(); - } - - private boolean isReplaceWithNewLine(Element element1){ - AttributeSet as1 = element1.getAttributes(); - Enumeration attribEntriesOriginal1 = as1.getAttributeNames(); - while(attribEntriesOriginal1.hasMoreElements()) { - Object entryKey = attribEntriesOriginal1.nextElement(); - Object entryValue = as1.getAttribute(entryKey); - - if(entryValue instanceof Tag){ - if(entryValue == Tag.BR){ - return true; - } - } - } - return false; - } - + _instance = this; + (new Thread(this)).start(); + } + + /** + * Place a String on the clipboard, and make this class the + * owner of the Clipboard's contents. + */ + public void setClipboardContents(String aString) { + StringSelection stringSelection = new StringSelection(aString); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(stringSelection, this); + } + + public synchronized void addText(String text) { + if (text == null) + return; + synchronized (currentLines) { + while (true) { + int index = text.indexOf(nl); + if (index >= 0) { + String str = text.substring(0, index); + if (lastLine.length() != 0) { + str = lastLine + str; + lastLine = ""; + } + currentLines.addLast(str); + if (currentLines.size() > 100) + currentLines.removeFirst(); + text = text.substring(index + nl.length()); + } else { + if (text.length() != 0) { + lastLine = lastLine + text; + } + break; + } + } + } + synchronized (lock) { + redoCalled = true; + lock.notifyAll(); + } + } + + public void run() { + Thread.currentThread().setName("[FDT StatusThread]"); + while (true) { + synchronized (lock) { + while (!redoCalled) { + try { + lock.wait(); + } catch (Exception e) { + } + } + redoCalled = false; + } + redo(); + } + } + + private void redo() { + StringBuffer buf = new StringBuffer(); + boolean first = true; + synchronized (currentLines) { + buf.append(""); + for (String t : currentLines) { + if (!first) buf.append("
    "); + buf.append(t); + first = false; + } + if (lastLine.length() != 0) { + if (!first) buf.append("
    "); + buf.append(lastLine); + } + buf.append(""); + } + + p1.setText(buf.toString()); + synchronized (getTreeLock()) { + try { + setEditorKit(p1.getEditorKit()); + setDocument(p1.getDocument()); + pane.getViewport().scrollRectToVisible(new Rectangle(0, _instance.getPreferredSize().height, 0, 0)); + } catch (Throwable t) { + } + } + _instance.repaint(); + p1 = new JTextPane(); + p1.setOpaque(true); + p1.setToolTipText("Status bar"); + p1.setBackground(Color.white); + p1.setContentType("text/html"); + } + + /** + * Empty implementation of the ClipboardOwner interface. + */ + public void lostOwnership(Clipboard aClipboard, Transferable aContents) { + //do nothing + } + + public String getSelectedText() { + return getSelectedHTMLAsText(); + } + + public String getSelectedHTMLAsText() { + String text = super.getSelectedText(); + if (text == null || text.length() == 0) { + return text; + } + + int startPos = getSelectionStart(); + int endPos = getSelectionEnd(); + + int selectedCharacters = endPos - startPos; + StringBuffer buffer = new StringBuffer(selectedCharacters); + int nbspCount = 0; + for (int i = 0, j = startPos; i < selectedCharacters; i++, j++) { + Element element1 = ((HTMLDocument) getDocument()).getCharacterElement(j); + if (isReplaceWithNewLine(element1)) { + buffer.append('\n'); + } else { + + buffer.append(text.charAt(i)); + } + + } + return buffer.toString(); + } + + private boolean isReplaceWithNewLine(Element element1) { + AttributeSet as1 = element1.getAttributes(); + Enumeration attribEntriesOriginal1 = as1.getAttributeNames(); + while (attribEntriesOriginal1.hasMoreElements()) { + Object entryKey = attribEntriesOriginal1.nextElement(); + Object entryValue = as1.getAttribute(entryKey); + + if (entryValue instanceof Tag) { + if (entryValue == Tag.BR) { + return true; + } + } + } + return false; + } + } // end of class StatusBar diff --git a/src/lia/util/net/copy/gui/TransferMonitor.java b/src/lia/util/net/copy/gui/TransferMonitor.java index 96c6f26..3cb957b 100644 --- a/src/lia/util/net/copy/gui/TransferMonitor.java +++ b/src/lia/util/net/copy/gui/TransferMonitor.java @@ -3,208 +3,204 @@ */ package lia.util.net.copy.gui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; import java.text.NumberFormat; import java.util.Locale; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.UIManager; - //import lia.util.net.common.Utils; /** - * * @author Ciprian Dobre */ public class TransferMonitor implements Runnable { - private final RemoteSessionManager manager; - private ProgressMonitor progressMonitor; - private boolean push; - - private final TransferMonitor _instance; - private static final Object lock = new Object(); - private boolean toRun = false; - private boolean workInProgress = false; - - private static final NumberFormat nf = NumberFormat.getInstance(Locale.US); - static { - nf.setMaximumFractionDigits(2); - } - - public TransferMonitor(final JPanel ft, boolean push, final RemoteSessionManager manager) { - this.push = push; - this.manager = manager; - progressMonitor = new ProgressMonitor(ft, "Initializing...", 0, 100); - ft.setEnabled(true); - UIManager.put("ProgressBar.foreground", new Color(8, 32, 128)); - _instance = this; - (new Thread(this)).start(); - } - - public void restart(final JPanel ft, boolean push) { - this.push = push; - progressMonitor.setProgress(0); - progressMonitor.setNote("Initializing..."); + private static final Object lock = new Object(); + private static final NumberFormat nf = NumberFormat.getInstance(Locale.US); + + static { + nf.setMaximumFractionDigits(2); + } + + private final RemoteSessionManager manager; + private final TransferMonitor _instance; + private ProgressMonitor progressMonitor; + private boolean push; + private boolean toRun = false; + private boolean workInProgress = false; + + public TransferMonitor(final JPanel ft, boolean push, final RemoteSessionManager manager) { + this.push = push; + this.manager = manager; + progressMonitor = new ProgressMonitor(ft, "Initializing...", 0, 100); + ft.setEnabled(true); + UIManager.put("ProgressBar.foreground", new Color(8, 32, 128)); + _instance = this; + (new Thread(this)).start(); + } + + public static void main(String args[]) { + ProgressMonitor m = new ProgressMonitor(new JPanel(), "Init", 0, 100); + for (int i = 0; i < 100; i++) { + m.setProgress(i); + m.setNote("Transfer is " + nf.format(i) + "% complete
    Transfer rate is " + i + ""); + try { + Thread.sleep(100); + } catch (Exception e) { + } + } + } + + public void restart(final JPanel ft, boolean push) { + this.push = push; + progressMonitor.setProgress(0); + progressMonitor.setNote("Initializing..."); // if (progressMonitor != null) { progressMonitor.close(); progressMonitor = null; } // progressMonitor = new ProgressMonitor(ft, "Initializing...", 0, 100); // ft.setEnabled(true); - } - - public void start() { - progressMonitor.setVisible(true); - synchronized (lock) { - toRun = true; - lock.notifyAll(); - } - } - - public boolean finished() { - synchronized (lock) { - return toRun == false && workInProgress == false; - } - } - - public void run() { - - Thread.currentThread().setName("[FDT TransferMonitorThread]"); - while (true) { - synchronized (lock) { - while (!toRun) { - try { lock.wait(); } catch (Exception e) { } - } - toRun = false; - workInProgress = true; - } - - while (true) { - if (manager == null) { - progressMonitor.close(); - break; - } - if (progressMonitor.isCanceled()) { - System.out.println("cancelled"); - progressMonitor.close(); - manager.stopTransfer(); - break; - } - double progress = manager.getTransferPercent(); - if (Double.isNaN(progress) || Double.isInfinite(progress)) { - progressMonitor.close(); - manager.end(); - break; - } - double val = Math.min(100.0, progress); + } + + public void start() { + progressMonitor.setVisible(true); + synchronized (lock) { + toRun = true; + lock.notifyAll(); + } + } + + public boolean finished() { + synchronized (lock) { + return toRun == false && workInProgress == false; + } + } + + public void run() { + + Thread.currentThread().setName("[FDT TransferMonitorThread]"); + while (true) { + synchronized (lock) { + while (!toRun) { + try { + lock.wait(); + } catch (Exception e) { + } + } + toRun = false; + workInProgress = true; + } + + while (true) { + if (manager == null) { + progressMonitor.close(); + break; + } + if (progressMonitor.isCanceled()) { + System.out.println("cancelled"); + progressMonitor.close(); + manager.stopTransfer(); + break; + } + double progress = manager.getTransferPercent(); + if (Double.isNaN(progress) || Double.isInfinite(progress)) { + progressMonitor.close(); + manager.end(); + break; + } + double val = Math.min(100.0, progress); // System.out.println(val); - if (val >= 100.0) { - progressMonitor.close(); - if (push) { - if (manager != null && manager.getRemoteTable() != null) - manager.getRemoteTable().setConnected(true); - manager.getLocalTable().getTable().requestFocusInWindow(); - } else { - if (manager != null && manager.getLocalTable() != null) { - manager.getLocalTable().setConnected(true); - } - manager.getRemoteTable().getTable().requestFocusInWindow(); - } - manager.end(); - break; - } - if (!Double.isInfinite(val) && !Double.isNaN(val)) { - progressMonitor.setProgress((int)val); - progressMonitor.setNote("Transfer is "+nf.format(val)+"% complete
    Transfer rate is "+manager.getTransferSpeed()+""); - } - try { Thread.sleep(200); } catch (Exception e) { } - } - synchronized (lock) { - workInProgress = false; - } - } - } - - public static class ProgressMonitor extends JDialog { - - private JProgressBar progress; - private JLabel label; - private JButton cancel; - private boolean isCancelled = false; - - public ProgressMonitor(JPanel component, String text, int min, int max) { - super(); - JDialog.setDefaultLookAndFeelDecorated(true); - setLocation((int)component.getLocationOnScreen().getX() + component.getWidth() / 2 - 160, (int)component.getLocationOnScreen().getY() + component.getHeight() / 2 - 62); - setTitle("Copying"); - setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - setAlwaysOnTop(true); - setDefaultLookAndFeelDecorated(true); - getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); - JPanel p1 = new JPanel(); - p1.setLayout(new BorderLayout()); - label = new JLabel(text); - p1.add(label, BorderLayout.CENTER); - getContentPane().add(p1); - JPanel p2 = new JPanel(); - p2.setLayout(new BorderLayout()); - progress = new JProgressBar(min, max); - progress.setUI(new ProgressBarUI()); - progress.setStringPainted(false); - p2.add(progress, BorderLayout.CENTER); - getContentPane().add(p2); - JPanel p3 = new JPanel(); - p3.setLayout(new BoxLayout(p3, BoxLayout.X_AXIS)); - cancel = new JButton("Cancel"); - cancel.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - isCancelled = true; - setVisible(false); - } - }); - p3.add(cancel); - getContentPane().add(Box.createVerticalStrut(3)); - getContentPane().add(p3); - getContentPane().add(Box.createVerticalStrut(3)); - setSize(320, 125); - setResizable(false); - } - - public void close() { - isCancelled = false; - setVisible(false); - } - - public boolean isCanceled() { - return isCancelled; - } - - public void setProgress(int progress) { - this.progress.setValue(progress); - this.progress.repaint(); - } - - public void setNote(String note) { - label.setText(note); - label.repaint(); - } - } - - public static void main(String args[]) { - ProgressMonitor m = new ProgressMonitor(new JPanel(), "Init", 0, 100); - for (int i=0; i<100; i++) { - m.setProgress(i); - m.setNote("Transfer is "+nf.format(i)+"% complete
    Transfer rate is "+i+""); - try { - Thread.sleep(100); - } catch (Exception e) { } - } - } + if (val >= 100.0) { + progressMonitor.close(); + if (push) { + if (manager != null && manager.getRemoteTable() != null) + manager.getRemoteTable().setConnected(true); + manager.getLocalTable().getTable().requestFocusInWindow(); + } else { + if (manager != null && manager.getLocalTable() != null) { + manager.getLocalTable().setConnected(true); + } + manager.getRemoteTable().getTable().requestFocusInWindow(); + } + manager.end(); + break; + } + if (!Double.isInfinite(val) && !Double.isNaN(val)) { + progressMonitor.setProgress((int) val); + progressMonitor.setNote("Transfer is " + nf.format(val) + "% complete
    Transfer rate is " + manager.getTransferSpeed() + ""); + } + try { + Thread.sleep(200); + } catch (Exception e) { + } + } + synchronized (lock) { + workInProgress = false; + } + } + } + + public static class ProgressMonitor extends JDialog { + + private JProgressBar progress; + private JLabel label; + private JButton cancel; + private boolean isCancelled = false; + + public ProgressMonitor(JPanel component, String text, int min, int max) { + super(); + JDialog.setDefaultLookAndFeelDecorated(true); + setLocation((int) component.getLocationOnScreen().getX() + component.getWidth() / 2 - 160, (int) component.getLocationOnScreen().getY() + component.getHeight() / 2 - 62); + setTitle("Copying"); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + setAlwaysOnTop(true); + setDefaultLookAndFeelDecorated(true); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + JPanel p1 = new JPanel(); + p1.setLayout(new BorderLayout()); + label = new JLabel(text); + p1.add(label, BorderLayout.CENTER); + getContentPane().add(p1); + JPanel p2 = new JPanel(); + p2.setLayout(new BorderLayout()); + progress = new JProgressBar(min, max); + progress.setUI(new ProgressBarUI()); + progress.setStringPainted(false); + p2.add(progress, BorderLayout.CENTER); + getContentPane().add(p2); + JPanel p3 = new JPanel(); + p3.setLayout(new BoxLayout(p3, BoxLayout.X_AXIS)); + cancel = new JButton("Cancel"); + cancel.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + isCancelled = true; + setVisible(false); + } + }); + p3.add(cancel); + getContentPane().add(Box.createVerticalStrut(3)); + getContentPane().add(p3); + getContentPane().add(Box.createVerticalStrut(3)); + setSize(320, 125); + setResizable(false); + } + + public void close() { + isCancelled = false; + setVisible(false); + } + + public boolean isCanceled() { + return isCancelled; + } + + public void setProgress(int progress) { + this.progress.setValue(progress); + this.progress.repaint(); + } + + public void setNote(String note) { + label.setText(note); + label.repaint(); + } + } } diff --git a/src/lia/util/net/copy/gui/session/LocalSession.java b/src/lia/util/net/copy/gui/session/LocalSession.java index 643a3dc..7d2caa3 100644 --- a/src/lia/util/net/copy/gui/session/LocalSession.java +++ b/src/lia/util/net/copy/gui/session/LocalSession.java @@ -3,223 +3,47 @@ */ package lia.util.net.copy.gui.session; +import javax.swing.filechooser.*; import java.io.File; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; - -import javax.swing.filechooser.FileSystemView; +import java.util.*; /** - * * The LocalSession represents the session on which the client is running - * + * * @author Ciprian Dobre */ public class LocalSession extends Session { - private static FileSystemView local = FileSystemView.getFileSystemView(); - - /** The current working directory */ - public String currentDir; - - private boolean canWrite = true; - - /** The default directory of the user */ - public String userDir; - - /** The name of the operating system */ - public String osName; - - // helper mapping between display names and real File folders used for root folders - private final HashMap roots = new HashMap(); - - private File currentFile; - - public LocalSession() { - currentFile = local.getHomeDirectory(); - this.currentDir = currentFile.getAbsolutePath(); - osName = System.getProperty("os.name"); - userDir = System.getProperty("user.home"); - update(); -// System.out.println(currentDir); - } - - public void setAbsoluteDir(String dir) { - if (dir == null) return; - dir = getRoot(dir); - if (roots.containsKey(dir)) { - currentFile = roots.get(dir); - currentDir = currentFile.getAbsolutePath(); - update(); - return; - } - try { - File f = new File(dir); - if (!f.exists() || !f.isDirectory() || !f.canRead()) return; - currentFile = f; - currentDir = currentFile.getAbsolutePath(); - update(); - } catch (Exception e) { } - } - - private final String getRoot(String dir) { - if (roots.containsKey(dir)) return dir; - for (Map.Entry entry : roots.entrySet()) { - if (entry.getValue().getAbsolutePath().equals(dir)) return entry.getKey(); - } - return dir; - } - - public void setRelativeDir(String dir) { - File f = local.getChild(currentFile, dir); - if (f==null || !f.exists() || !f.isDirectory()) return; // error, cannot change - currentFile = f; - currentDir = f.getAbsolutePath(); - update(); - } + private static FileSystemView local = FileSystemView.getFileSystemView(); + // helper mapping between display names and real File folders used for root folders + private final HashMap roots = new HashMap(); + /** + * The current working directory + */ + public String currentDir; + /** + * The default directory of the user + */ + public String userDir; - public void setUpDir() { - File f = local.getParentDirectory(currentFile); - if (f == null || !f.exists() || !f.isDirectory()) return; // error, cannot change - currentFile = f; - currentDir = f.getAbsolutePath(); - update(); - } + /** + * The name of the operating system + */ + public String osName; + private boolean canWrite = true; + private File currentFile; - public String[] getRoots() { - roots.clear(); // clear the previously discovered roots - // auxiliary hash used - final HashSet h = new HashSet(); - // start by using the FileSystemView... - File roots[] = local.getRoots(); - if (roots != null) { - for (int i=0; i it = this.roots.keySet().iterator(); it.hasNext() && i entry : roots.entrySet()) { + if (entry.getValue().getAbsolutePath().equals(dir)) return entry.getKey(); + } + return dir; + } - public String getUserDir() { - return userDir; - } - - public boolean fileExists(String fileName) { - if (fileName == null) return false; - return modif.containsKey(fileName); - } + public void setRelativeDir(String dir) { + File f = local.getChild(currentFile, dir); + if (f == null || !f.exists() || !f.isDirectory()) return; // error, cannot change + currentFile = f; + currentDir = f.getAbsolutePath(); + update(); + } - public void removeFiles(String[] files) { - if (files == null) return; - for (int i=0; i h = new HashSet(); + // start by using the FileSystemView... + File roots[] = local.getRoots(); + if (roots != null) { + for (int i = 0; i < roots.length; i++) { + String displayName = local.getSystemDisplayName(roots[i]); + if (h.contains(displayName)) continue; // do not add it twice + if (displayName.length() == 0) + continue; // skip empty drives (it applies to cd drives and floppy drives without media + h.add(displayName); +// String description = local.getSystemTypeDescription(roots[i]); +// if (description.length() != 0) { +// displayName += " "+description; +// } + this.roots.put(displayName, roots[i]); + } + } + // then use the File object.... + roots = File.listRoots(); + if (roots != null) { + for (int i = 0; i < roots.length; i++) { + String displayName = local.getSystemDisplayName(roots[i]); + if (h.contains(displayName)) continue; // do not add it twice + if (displayName.length() == 0) + continue; // skip empty drives (it applies to cd drives and floppy drives without media + h.add(displayName); +// String description = local.getSystemTypeDescription(roots[i]); +// if (description.length() != 0) { +// displayName += " "+description; +// } + this.roots.put(displayName, roots[i]); + } + } + // also if linux put the path to the home directory... + if (System.getProperty("os.name").toLowerCase(Locale.US).contains("linux")) { + String p = System.getProperty("user.home"); + File f = new File(p); + if (f.exists()) { + File pa = null; + while ((pa = f.getParentFile()) != null && pa.exists()) { + this.roots.put(f.getAbsolutePath(), f); + f = pa; + } + } + } + final String[] keys = new String[this.roots.size()]; + int i = 0; + for (Iterator it = this.roots.keySet().iterator(); it.hasNext() && i < keys.length; i++) { + keys[i] = it.next(); + } + return keys; + } + + public String getShortRootName(String rootFolder) { + if (rootFolder == null) return null; + rootFolder = getRoot(rootFolder); + if (roots.containsKey(rootFolder)) return roots.get(rootFolder).getAbsolutePath(); + return null; + } + + public boolean isRoot() { + if (local.isDrive(currentFile)) { + return true; + } + File f = local.getParentDirectory(currentFile); + return f == null; + } + + private void update() { // updates the current working directory + + // clear the current known properties.... + dirs.clear(); + length.clear(); + icons.clear(); + modif.clear(); + read.clear(); + write.clear(); + // list the current files + File l[] = local.getFiles(currentFile, false); + +// System.out.println(currentFile.getUsableSpace()); + + if (l != null) { + for (int i = 0; i < l.length; i++) { + String fn = local.getSystemDisplayName(l[i]); + if (fn.length() == 0) continue; + if (l[i].isDirectory()) { + // check to see if we are not looking at some links... + try { + File tmpf = local.getChild(currentFile, fn); + if (tmpf == null || !local.isTraversable(tmpf)) continue; // alarm, link detected here + local.getFiles(tmpf, false); + } catch (Throwable t) { + continue; + } + // otherwise is ok to add it... + dirs.add(fn); + icons.put(fn, local.getSystemIcon(l[i])); + modif.put(fn, l[i].lastModified()); + read.put(fn, l[i].canRead()); + write.put(fn, l[i].canWrite()); + } else if (l[i].isFile()) { + length.put(fn, l[i].length()); + icons.put(fn, local.getSystemIcon(l[i])); + modif.put(fn, l[i].lastModified()); + read.put(fn, l[i].canRead()); + write.put(fn, l[i].canWrite()); + } + } + } + try { + int i = 0; + while (true) { + File f = new File(currentDir + System.getProperty("file.separator") + "fdt" + i); + if (f.exists()) continue; + f.createNewFile(); + canWrite = f.exists(); + if (canWrite) + f.delete(); + break; + } + } catch (Throwable t) { + canWrite = false; + } + } + + public String getFileSeparator() { + return System.getProperty("file.separator"); + } + + public String getWorkingDir() { + return currentDir; + } + + public boolean canWrite() { + return canWrite; + } + + public String getOSName() { + return osName; + } + + public String getUserDir() { + return userDir; + } + + public boolean fileExists(String fileName) { + if (fileName == null) return false; + return modif.containsKey(fileName); + } + + public void removeFiles(String[] files) { + if (files == null) return; + for (int i = 0; i < files.length; i++) { + try { + File f = new File(files[i]); + if (f.isDirectory()) { + File ff[] = f.listFiles(); + if (ff != null) { + String str[] = new String[ff.length]; + for (int j = 0; j < str.length; j++) { + str[j] = ff[j].getAbsolutePath(); + } + removeFiles(str); + } + } + f.delete(); + } catch (Throwable t) { + } + } + update(); + } + + public void createDir(String name) { + try { + File f = new File(name); + f.mkdirs(); + } catch (Throwable t) { + } + update(); + } + + public String freeSpace() { + if (currentFile == null) return null; + try { + long space = currentFile.getFreeSpace(); + return parseSize(space); + } catch (Throwable t) { + } + return null; + } + + private final String parseSize(long space) { + if (space > 1024l) { + space = space / 1024l; + if (space > 1024l) { + space = space / 1024l; + if (space > 1024l) { + space = space / 1024l; + if (space > 1024l) { + space = space / 1024l; + return space + " TB"; + } else + return space + " GB"; + } else + return space + " MB"; + } else + return space + "KB"; + } else { + return space + " B"; + } + } - public String freeSpace() { - if (currentFile == null) return null; - try { - long space = currentFile.getFreeSpace(); - return parseSize(space); - } catch (Throwable t) { - } - return null; - } - - private final String parseSize(long space) { - if (space > 1024l) { - space = space / 1024l; - if (space > 1024l) { - space = space / 1024l; - if (space > 1024l) { - space = space / 1024l; - if (space > 1024l) { - space = space / 1024l; - return space + " TB"; - } else - return space + " GB"; - } else - return space + " MB"; - } else - return space + "KB"; - } else { - return space+" B"; - } - } - } // end of class LocalSession diff --git a/src/lia/util/net/copy/gui/session/RemoteSession.java b/src/lia/util/net/copy/gui/session/RemoteSession.java index c95a1c9..1932738 100644 --- a/src/lia/util/net/copy/gui/session/RemoteSession.java +++ b/src/lia/util/net/copy/gui/session/RemoteSession.java @@ -3,205 +3,202 @@ */ package lia.util.net.copy.gui.session; +import lia.util.net.copy.gui.RemoteSessionManager; +import lia.util.net.copy.transport.gui.FileHandler; + +import javax.swing.*; +import javax.swing.filechooser.*; import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; import java.net.URL; import java.util.HashMap; import java.util.Locale; import java.util.Vector; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.filechooser.FileSystemView; - -import lia.util.net.copy.gui.RemoteSessionManager; -import lia.util.net.copy.transport.gui.FileHandler; - /** - * * The RemoteSession represents the session with which the client is communicating - * + * * @author Ciprian Dobre */ public class RemoteSession extends Session { - private final RemoteSessionManager manager; + public static final Icon folderIcon; + // mapping between extensions and associated known icons.. + public static final HashMap icons = new HashMap(); - final static String osName = System.getProperty("os.name").toLowerCase(Locale.US); - // private String workingDir; // private boolean root = false; - - public RemoteSession(RemoteSessionManager manager) { - this.manager = manager; - } - - private static final boolean isWindows() { - return osName.indexOf("windows") > -1; - } - - private static final boolean isLinux() { - return osName.indexOf("linux") > -1; - } - - private static final boolean isMac() { - return osName.indexOf("mac") > -1; - } - - public void setAbsoluteDir(String dir) throws Exception { - manager.setAbsoluteDir(dir); - update(); - } - - public void setRelativeDir(String dir) throws Exception { - manager.setRelativeDir(dir); - update(); - } - - public void setUpDir() throws Exception { - manager.setUpDir(); - update(); - } - - public String[] getRoots() throws Exception { - return manager.getRoots(); - } - - public String getFileSeparator() { - return manager.getFileSeparator(); - } - - public String getShortRootName(String rootFolder) { - return manager.getShortRootName(rootFolder); - } - - public boolean isRoot() { - return manager.isRoot(); - } - - public void removeFiles(String[] files) throws Exception { - manager.removeFiles(files); - } - - public void createDir(String name) throws Exception { - manager.createDir(name); - } - - private void update() { // updates the current working directory - // clear the current known properties.... - dirs.clear(); - length.clear(); - icons.clear(); - modif.clear(); - read.clear(); - write.clear(); - // list the current files - Vector v = manager.getFileList(); - if (v != null) { - for (FileHandler h : v) { - final String fn = h.getName(); - if (h.getSize() < 0) { // folder - dirs.add(fn); - super.icons.put(fn, getFolderIcon()); - } else { // file - length.put(fn, h.getSize()); - super.icons.put(fn, getFileIcon(fn)); - } - modif.put(fn, h.getModif()); - read.put(fn, h.canRead()); - write.put(fn, h.canWrite()); - } - } - } - - public static final Icon folderIcon; - private static final FileSystemView local = FileSystemView.getFileSystemView(); - static { - folderIcon = local.getSystemIcon(new File(System.getProperty("user.dir"))); - } - // mapping between extensions and associated known icons.. - public static final HashMap icons = new HashMap(); - - private Icon getFolderIcon() { - return folderIcon; - } - - private ImageIcon unknownIcon; - private Icon getUnknownIcon() { - if (unknownIcon != null) return unknownIcon; - try { - URL r = getClass().getResource("../icons/file.png"); - unknownIcon = new ImageIcon(r); - } catch (Exception e) { } - return unknownIcon; - } - - private final boolean okFS(String str) { - if (isWindows()) { - return !(str.contains("\\") || str.contains("/") || str.contains(":") || str.contains("*") || str.contains("?") - || str.contains("\"") || str.contains("<") || str.contains(">") || str.contains("|")); - } - return true; - } - - private Icon getFileIcon(String fileName) { - // get the file extension, if any... - int index = fileName.lastIndexOf("."); - if (index < 0) { // no extension, use unknown file type... - return getUnknownIcon(); - } - // else get the file extension... - String ext = fileName.substring(index+1); - if (!okFS(ext)) - return getUnknownIcon(); - if (icons.containsKey(ext)) return icons.get(ext); - try { - //Create a temporary file with the specified extension - File file = File.createTempFile("icon", "." + ext); - Icon icon = null; - if (file.exists()) { - try { - icon = local.getSystemIcon(file); - icons.put(ext, icon); - } catch (Throwable tt) { } - } - //Delete the temporary file - file.delete(); - if (icon != null) - return icon; - return getUnknownIcon(); - } catch (Throwable t) { - t.printStackTrace(); - } - return getUnknownIcon(); - } - - public String getWorkingDir() { - return manager.getWorkingDirectory(); - } - - public boolean canWrite() { - return manager.canWrite(); - } - - public String getOSName() { - return manager.getOSName(); - } - - public String getUserDir() { - return manager.getUserHome(); - } - - public boolean fileExists(String fileName) { - if (fileName == null) return false; - return modif.containsKey(fileName); - } - - public String freeSpace() { - return manager.freeSpace(); - } - + final static String osName = System.getProperty("os.name").toLowerCase(Locale.US); + private static final FileSystemView local = FileSystemView.getFileSystemView(); + + static { + folderIcon = local.getSystemIcon(new File(System.getProperty("user.dir"))); + } + + private final RemoteSessionManager manager; + private ImageIcon unknownIcon; + + public RemoteSession(RemoteSessionManager manager) { + this.manager = manager; + } + + private static final boolean isWindows() { + return osName.indexOf("windows") > -1; + } + + private static final boolean isLinux() { + return osName.indexOf("linux") > -1; + } + + private static final boolean isMac() { + return osName.indexOf("mac") > -1; + } + + public void setAbsoluteDir(String dir) throws Exception { + manager.setAbsoluteDir(dir); + update(); + } + + public void setRelativeDir(String dir) throws Exception { + manager.setRelativeDir(dir); + update(); + } + + public void setUpDir() throws Exception { + manager.setUpDir(); + update(); + } + + public String[] getRoots() throws Exception { + return manager.getRoots(); + } + + public String getFileSeparator() { + return manager.getFileSeparator(); + } + + public String getShortRootName(String rootFolder) { + return manager.getShortRootName(rootFolder); + } + + public boolean isRoot() { + return manager.isRoot(); + } + + public void removeFiles(String[] files) throws Exception { + manager.removeFiles(files); + } + + public void createDir(String name) throws Exception { + manager.createDir(name); + } + + private void update() { // updates the current working directory + // clear the current known properties.... + dirs.clear(); + length.clear(); + icons.clear(); + modif.clear(); + read.clear(); + write.clear(); + // list the current files + Vector v = manager.getFileList(); + if (v != null) { + for (FileHandler h : v) { + final String fn = h.getName(); + if (h.getSize() < 0) { // folder + dirs.add(fn); + super.icons.put(fn, getFolderIcon()); + } else { // file + length.put(fn, h.getSize()); + super.icons.put(fn, getFileIcon(fn)); + } + modif.put(fn, h.getModif()); + read.put(fn, h.canRead()); + write.put(fn, h.canWrite()); + } + } + } + + private Icon getFolderIcon() { + return folderIcon; + } + + private Icon getUnknownIcon() { + if (unknownIcon != null) return unknownIcon; + try { + URL r = getClass().getResource("../icons/file.png"); + unknownIcon = new ImageIcon(r); + } catch (Exception e) { + } + return unknownIcon; + } + + private final boolean okFS(String str) { + if (isWindows()) { + return !(str.contains("\\") || str.contains("/") || str.contains(":") || str.contains("*") || str.contains("?") + || str.contains("\"") || str.contains("<") || str.contains(">") || str.contains("|")); + } + return true; + } + + private Icon getFileIcon(String fileName) { + // get the file extension, if any... + int index = fileName.lastIndexOf("."); + if (index < 0) { // no extension, use unknown file type... + return getUnknownIcon(); + } + // else get the file extension... + String ext = fileName.substring(index + 1); + if (!okFS(ext)) + return getUnknownIcon(); + if (icons.containsKey(ext)) return icons.get(ext); + try { + //Create a temporary file with the specified extension + File file = File.createTempFile("icon", "." + ext); + Icon icon = null; + if (file.exists()) { + try { + icon = local.getSystemIcon(file); + icons.put(ext, icon); + } catch (Throwable tt) { + } + } + //Delete the temporary file + file.delete(); + if (icon != null) + return icon; + return getUnknownIcon(); + } catch (Throwable t) { + t.printStackTrace(); + } + return getUnknownIcon(); + } + + public String getWorkingDir() { + return manager.getWorkingDirectory(); + } + + public boolean canWrite() { + return manager.canWrite(); + } + + public String getOSName() { + return manager.getOSName(); + } + + public String getUserDir() { + return manager.getUserHome(); + } + + public boolean fileExists(String fileName) { + if (fileName == null) return false; + return modif.containsKey(fileName); + } + + public String freeSpace() { + return manager.freeSpace(); + } + } // end of class RemoteSession diff --git a/src/lia/util/net/copy/gui/session/Session.java b/src/lia/util/net/copy/gui/session/Session.java index 599448b..d0fb758 100644 --- a/src/lia/util/net/copy/gui/session/Session.java +++ b/src/lia/util/net/copy/gui/session/Session.java @@ -3,77 +3,101 @@ */ package lia.util.net.copy.gui.session; +import javax.swing.*; import java.util.HashMap; import java.util.HashSet; -import javax.swing.Icon; - /** - * * Helper class to handle the movements through current folders - * + * * @author Ciprian Dobre */ public abstract class Session { - /** The working file attributes */ - - public final HashSet dirs = new HashSet(); - - public final HashMap length = new HashMap(); - - public final HashMap icons = new HashMap(); - - public final HashMap modif = new HashMap(); - - public final HashMap read = new HashMap(); - - public final HashMap write = new HashMap(); - - public Session() { - } - - /** Returns the current working directory for this FS session */ - public abstract String getWorkingDir(); - - /** Returns true if we can write in the directory denoted by the current working directory pathname */ - public abstract boolean canWrite(); - - /** Returns the name of the OS */ - public abstract String getOSName(); - - public abstract String getFileSeparator(); - - /** Returns the home directory of the user */ - public abstract String getUserDir(); - - /** Called when the user chooses from the drop-down list one of the root filesystems */ - public abstract void setAbsoluteDir(String dir) throws Exception; - - /** Called when the user double clicks on a folder */ - public abstract void setRelativeDir(String dir) throws Exception; - - /** Called when the user double clicks on the up folder */ - public abstract void setUpDir() throws Exception; - - /** Returns the roots folders of the current FS - possible complete description */ - public abstract String[] getRoots() throws Exception; - - public abstract String getShortRootName(String rootFolder); - - /** Returns true if the currentDir is root */ - public abstract boolean isRoot(); - - /** Check whether the file denoted by fileName exists or not */ - public abstract boolean fileExists(String fileName); - - /** Removes the files denoted */ - public abstract void removeFiles(String[] files) throws Exception; - - /** Creates a new directory */ - public abstract void createDir(String dirName) throws Exception; - - public abstract String freeSpace(); - + /** + * The working file attributes + */ + + public final HashSet dirs = new HashSet(); + + public final HashMap length = new HashMap(); + + public final HashMap icons = new HashMap(); + + public final HashMap modif = new HashMap(); + + public final HashMap read = new HashMap(); + + public final HashMap write = new HashMap(); + + public Session() { + } + + /** + * Returns the current working directory for this FS session + */ + public abstract String getWorkingDir(); + + /** + * Returns true if we can write in the directory denoted by the current working directory pathname + */ + public abstract boolean canWrite(); + + /** + * Returns the name of the OS + */ + public abstract String getOSName(); + + public abstract String getFileSeparator(); + + /** + * Returns the home directory of the user + */ + public abstract String getUserDir(); + + /** + * Called when the user chooses from the drop-down list one of the root filesystems + */ + public abstract void setAbsoluteDir(String dir) throws Exception; + + /** + * Called when the user double clicks on a folder + */ + public abstract void setRelativeDir(String dir) throws Exception; + + /** + * Called when the user double clicks on the up folder + */ + public abstract void setUpDir() throws Exception; + + /** + * Returns the roots folders of the current FS - possible complete description + */ + public abstract String[] getRoots() throws Exception; + + public abstract String getShortRootName(String rootFolder); + + /** + * Returns true if the currentDir is root + */ + public abstract boolean isRoot(); + + /** + * Check whether the file denoted by fileName exists or not + */ + public abstract boolean fileExists(String fileName); + + /** + * Removes the files denoted + */ + public abstract void removeFiles(String[] files) throws Exception; + + /** + * Creates a new directory + */ + public abstract void createDir(String dirName) throws Exception; + + public abstract String freeSpace(); + } // end of class Session diff --git a/src/lia/util/net/copy/monitoring/ApMonReportingTask.java b/src/lia/util/net/copy/monitoring/ApMonReportingTask.java index 8350f94..f5203e9 100644 --- a/src/lia/util/net/copy/monitoring/ApMonReportingTask.java +++ b/src/lia/util/net/copy/monitoring/ApMonReportingTask.java @@ -3,125 +3,154 @@ */ package lia.util.net.copy.monitoring; -import java.util.HashMap; -import java.util.Map; -import java.util.Vector; -import java.util.logging.Level; -import java.util.logging.Logger; - +import apmon.ApMon; import lia.util.net.common.Config; import lia.util.net.common.Utils; import lia.util.net.copy.FDTReaderSession; import lia.util.net.copy.FDTSession; import lia.util.net.copy.FDTWriterSession; -import apmon.ApMon; + +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; /** - * * Simple implementation for a {@link FDTReportingTask} which is used to send data * through ApMon - * + * * @author ramiro - * */ public class ApMonReportingTask extends FDTReportingTask { private static final Logger logger = Logger.getLogger("lia.util.net.copy.monitoring.ApMonReportingTask"); - + private static final ApMon apMon; - + static { ApMon apMonInstace = null; try { System.out.println("Starting ApMonReportingTask ..."); apMonInstace = Utils.getApMon(); System.out.println("ApMonReportingTask started!"); - } catch(Throwable t) { - logger.log(Level.WARNING, "Got exception getting apmon instance",t); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception getting apmon instance", t); } - + apMon = apMonInstace; } + private static final void sendParams(final HashMap> paramsToSend, final String clusterName) throws Exception { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, " Sending to ApMonReportingTask :- ClusterName: " + clusterName + " Params: " + paramsToSend); + } + + if (paramsToSend.size() > 0) { + + for (Map.Entry> entry : paramsToSend.entrySet()) { + HashMap hToSend = entry.getValue(); + Vector paramValues = null; + Vector paramNames = null; + Vector paramTypes = null; + if (hToSend.size() > 0) { + paramValues = new Vector(hToSend.size()); + paramNames = new Vector(hToSend.size()); + paramTypes = new Vector(hToSend.size()); + + for (Map.Entry pEntry : hToSend.entrySet()) { + paramValues.add(pEntry.getValue()); + paramNames.add(pEntry.getKey()); + paramTypes.add(ApMon.XDR_REAL64); + } + } + + if (paramValues != null) { + apMon.sendParameters(clusterName, entry.getKey(), paramValues.size(), paramNames, paramTypes, paramValues); + } + } + } + } + private void publisStartFinishParams(final FDTSession fdtSession) { - if(fdtSession != null) { + if (fdtSession != null) { try { final HashMap> paramsToSend = new HashMap>(); final HashMap fdtSessionParams = new HashMap(); final FDTSessionMonitoringTask fdtSessionMTask = fdtSession.getMonitoringTask(); String apMonClusterName = "N/A"; - - if(fdtSessionMTask != null) { - if(fdtSession instanceof FDTWriterSession) { + + if (fdtSessionMTask != null) { + if (fdtSession instanceof FDTWriterSession) { final double rate = fdtSessionMTask.getTotalRate() / Utils.MEGA_BYTE; fdtSessionParams.put("DISK_WRITE_MB", rate); - final double tSize = fdtSession.getSize()/(double)Utils.MEGA_BYTE; - final double cSize = fdtSession.getTotalBytes()/(double)Utils.MEGA_BYTE; + final double tSize = fdtSession.getSize() / (double) Utils.MEGA_BYTE; + final double cSize = fdtSession.getTotalBytes() / (double) Utils.MEGA_BYTE; fdtSessionParams.put("TotalMBytes", tSize); fdtSessionParams.put("TransferredMBytes", cSize); - fdtSessionParams.put("Status", (double)fdtSession.getCurrentStatus()); + fdtSessionParams.put("Status", (double) fdtSession.getCurrentStatus()); - if(fdtSession.getSize() != 0) { - fdtSessionParams.put("TransferRatio", (cSize*100)/tSize); + if (fdtSession.getSize() != 0) { + fdtSessionParams.put("TransferRatio", (cSize * 100) / tSize); } - final String monID = fdtSession.getMonID(); - if(monID != null) { + final String monID = fdtSession.getMonID(); + if (monID != null) { paramsToSend.put(monID, fdtSessionParams); } else { - paramsToSend.put(fdtSession.getRemoteAddress().getHostAddress()+":"+fdtSession.getRemotePort(), fdtSessionParams); + paramsToSend.put(fdtSession.getRemoteAddress().getHostAddress() + ":" + fdtSession.getRemotePort(), fdtSessionParams); } apMonClusterName = "Readers"; - - } else if(fdtSession instanceof FDTReaderSession){ - final double rate = fdtSessionMTask.getTotalRate()/Utils.MEGA_BYTE; + + } else if (fdtSession instanceof FDTReaderSession) { + final double rate = fdtSessionMTask.getTotalRate() / Utils.MEGA_BYTE; fdtSessionParams.put("DISK_READ_MB", rate); - final double tSize = fdtSession.getSize()/(double)Utils.MEGA_BYTE; - final double cSize = fdtSession.getTotalBytes()/(double)Utils.MEGA_BYTE; + final double tSize = fdtSession.getSize() / (double) Utils.MEGA_BYTE; + final double cSize = fdtSession.getTotalBytes() / (double) Utils.MEGA_BYTE; fdtSessionParams.put("TotalMBytes", tSize); fdtSessionParams.put("TransferredMBytes", cSize); - if(fdtSession.getSize() != 0) { - fdtSessionParams.put("TransferRatio", (cSize*100)/tSize); + if (fdtSession.getSize() != 0) { + fdtSessionParams.put("TransferRatio", (cSize * 100) / tSize); } - fdtSessionParams.put("Status", (double)fdtSession.getCurrentStatus()); + fdtSessionParams.put("Status", (double) fdtSession.getCurrentStatus()); - final String monID = fdtSession.getMonID(); - if(monID != null) { + final String monID = fdtSession.getMonID(); + if (monID != null) { paramsToSend.put(monID, fdtSessionParams); } else { - paramsToSend.put(fdtSession.getRemoteAddress().getHostAddress()+":"+fdtSession.getRemotePort(), fdtSessionParams); + paramsToSend.put(fdtSession.getRemoteAddress().getHostAddress() + ":" + fdtSession.getRemotePort(), fdtSessionParams); } - + apMonClusterName = "Writers"; } else { logger.log(Level.WARNING, "[ERROR] FDT Session is not an \"instanceof\" FDTWriterSession or FDTReaderSession!!!"); return; } - - if(paramsToSend.size() > 0) { - - if(logger.isLoggable(Level.FINE)) { + + if (paramsToSend.size() > 0) { + + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " Sending to ApMonReportingTask :- " + apMonClusterName + " Params: " + paramsToSend); } - for(Map.Entry> entry: paramsToSend.entrySet()) { + for (Map.Entry> entry : paramsToSend.entrySet()) { HashMap hToSend = entry.getValue(); Vector paramValues = null; Vector paramNames = null; Vector paramTypes = null; - if(hToSend.size() > 0) { + if (hToSend.size() > 0) { paramValues = new Vector(hToSend.size()); paramNames = new Vector(hToSend.size()); - paramTypes = new Vector(hToSend.size()) ; - - for(Map.Entry pEntry: hToSend.entrySet()) { + paramTypes = new Vector(hToSend.size()); + + for (Map.Entry pEntry : hToSend.entrySet()) { paramValues.add(pEntry.getValue()); paramNames.add(pEntry.getKey()); paramTypes.add(ApMon.XDR_REAL64); } } - - if(paramValues != null) { + + if (paramValues != null) { apMon.sendParameters(apMonClusterName, entry.getKey(), paramValues.size(), paramNames, paramTypes, paramValues); } } @@ -131,63 +160,32 @@ private void publisStartFinishParams(final FDTSession fdtSession) { logger.log(Level.WARNING, "[ERROR] FDTSessionMonitoringTask is null in finishFDTSession(fdtSession)!!!"); } - }catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, "Got expcetion notifying last params for " + fdtSession.sessionID(), t); } } else { logger.log(Level.WARNING, "[ERROR] FDT Session is null in finishFDTSession(fdtSession)!!!"); } } - + public void startFDTSession(final FDTSession fdtSession) { publisStartFinishParams(fdtSession); } - + public void finishFDTSession(final FDTSession fdtSession) { publisStartFinishParams(fdtSession); } - private static final void sendParams(final HashMap> paramsToSend, final String clusterName) throws Exception { - if(logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, " Sending to ApMonReportingTask :- ClusterName: " + clusterName + " Params: " + paramsToSend); - } - - if(paramsToSend.size() > 0) { - - for(Map.Entry> entry: paramsToSend.entrySet()) { - HashMap hToSend = entry.getValue(); - Vector paramValues = null; - Vector paramNames = null; - Vector paramTypes = null; - if(hToSend.size() > 0) { - paramValues = new Vector(hToSend.size()); - paramNames = new Vector(hToSend.size()); - paramTypes = new Vector(hToSend.size()) ; - - for(Map.Entry pEntry: hToSend.entrySet()) { - paramValues.add(pEntry.getValue()); - paramNames.add(pEntry.getKey()); - paramTypes.add(ApMon.XDR_REAL64); - } - } - - if(paramValues != null) { - apMon.sendParameters(clusterName, entry.getKey(), paramValues.size(), paramNames, paramTypes, paramValues); - } - } - } - } - public void run() { try { - - if(logger.isLoggable(Level.FINEST)) { + + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "ApMonReportingTask entering run()"); } - + HashMap> paramsToSend = getReaderParams(); - - if(paramsToSend.size() > 0) { + + if (paramsToSend.size() > 0) { sendParams(paramsToSend, "Readers"); } @@ -196,37 +194,37 @@ public void run() { double totalNet = 0; double totalDisk = 0; - if(paramsToSend.size() > 0) { - - for(Map.Entry> entry: paramsToSend.entrySet()) { + if (paramsToSend.size() > 0) { + + for (Map.Entry> entry : paramsToSend.entrySet()) { HashMap hToSend = entry.getValue(); - if(hToSend.size() > 0) { + if (hToSend.size() > 0) { Double dToAdd = hToSend.get("NET_IN_Mb"); - if(dToAdd != null) { + if (dToAdd != null) { totalNet += dToAdd; } - + dToAdd = hToSend.get("DISK_WRITE_MB"); - if(dToAdd != null) { + if (dToAdd != null) { totalDisk += dToAdd; } } } } - - if(paramsToSend.size() > 0) { + + if (paramsToSend.size() > 0) { sendParams(paramsToSend, "Writers"); } - - if(Config.getInstance().getHostName() == null) { + + if (Config.getInstance().getHostName() == null) { HashMap localParams = new HashMap(); - localParams.put("CLIENTS_NO", (double)paramsToSend.size()); + localParams.put("CLIENTS_NO", (double) paramsToSend.size()); localParams.put("DISK_WRITE_MB", totalDisk); localParams.put("NET_IN_Mb", totalNet); - + // lisaMon.sendServerParameters("FDT_PARAMS", localParams); } - + // HashMap fdtLisaParams = FDTInternalMonitoringTask.getInstance().getLisaParams(); // // String key = "FDT_MON:"; @@ -258,8 +256,8 @@ public void run() { // } // // lisaMon.sendServerParameters(key, fdtLisaParams); - - }catch(Throwable t) { + + } catch (Throwable t) { logger.log(Level.INFO, " LISAReportingTask got exception:", t); } diff --git a/src/lia/util/net/copy/monitoring/ClientTransportMonitorTask.java b/src/lia/util/net/copy/monitoring/ClientTransportMonitorTask.java index b1550ee..86655c6 100644 --- a/src/lia/util/net/copy/monitoring/ClientTransportMonitorTask.java +++ b/src/lia/util/net/copy/monitoring/ClientTransportMonitorTask.java @@ -3,68 +3,65 @@ */ package lia.util.net.copy.monitoring; +import lia.util.net.copy.monitoring.lisa.LISAReportingTask; + import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.disk.DiskReaderManager; -import lia.util.net.copy.monitoring.lisa.LISAReportingTask; - /** - * - * This class is used to monitor the Client transfer and to notify LISA - * if something goes wrong - * + * This class is used to monitor the Client transfer and to notify LISA + * if something goes wrong + * * @author ramiro - * */ public class ClientTransportMonitorTask implements Runnable { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final transient Logger logger = Logger.getLogger(ClientTransportMonitorTask.class.getName()); private static final double THRESHOLD = 0.01; private static final int FAILED_ITERATIONS_THRESHOLD = 3; - double cRate = Double.MAX_VALUE; - - private final DiskReaderManagerMonitoringTask diskReaderMonitoringTask; private static final LISAReportingTask lisaReportingTask = LISAReportingTask.getInstance(); - + private final DiskReaderManagerMonitoringTask diskReaderMonitoringTask; + double cRate = Double.MAX_VALUE; private boolean isTransportDown; - + private int failedIterations; - - + + public ClientTransportMonitorTask(DiskReaderManagerMonitoringTask diskReaderMonitoringTask) { logger.log(Level.INFO, "ClientTransportMonitorTask started! "); this.diskReaderMonitoringTask = diskReaderMonitoringTask; failedIterations = 0; isTransportDown = false; } - + private void notifyTransportDown() { logger.log(Level.WARNING, "\n\n [ ClientTransportMonitorTask ] Current Rate " + cRate + " & failedIterations: " + failedIterations + " notifying LISA Wrapper \n\n"); isTransportDown = true; try { - lisaReportingTask.sendClientNow("RESTARTME", new HashMap()); - }catch(Throwable t) { + lisaReportingTask.sendClientNow("RESTARTME", new HashMap()); + } catch (Throwable t) { logger.log(Level.WARNING, "\n\n [ ClientTransportMonitorTask ] failed to notify LISA !! \n\n", t); } } - + public boolean isTransportDown() { return isTransportDown; } - + public void run() { cRate = diskReaderMonitoringTask.getTotalRate(); - if(diskReaderMonitoringTask.getTotalRate() < THRESHOLD) { + if (diskReaderMonitoringTask.getTotalRate() < THRESHOLD) { failedIterations++; } else { failedIterations = 0; } - - if(failedIterations > FAILED_ITERATIONS_THRESHOLD) { + + if (failedIterations > FAILED_ITERATIONS_THRESHOLD) { notifyTransportDown(); } else { isTransportDown = false; diff --git a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java index f67e9d7..e34b37f 100644 --- a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java +++ b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java @@ -3,6 +3,13 @@ */ package lia.util.net.copy.monitoring; +import lia.util.net.common.Utils; +import lia.util.net.copy.FDTSession; +import lia.util.net.copy.disk.DiskReaderManager; +import lia.util.net.copy.disk.DiskWriterManager; +import lia.util.net.copy.monitoring.base.AbstractAccountableMonitoringTask; +import lia.util.net.copy.transport.TCPTransportProvider; + import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -13,13 +20,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Utils; -import lia.util.net.copy.FDTSession; -import lia.util.net.copy.disk.DiskReaderManager; -import lia.util.net.copy.disk.DiskWriterManager; -import lia.util.net.copy.monitoring.base.AbstractAccountableMonitoringTask; -import lia.util.net.copy.transport.TCPTransportProvider; - /** * This class is the only class which should report to the stdout * @@ -32,16 +32,11 @@ public class ConsoleReportingTask extends AbstractAccountableMonitoringTask { private static final DiskWriterManager diskWriterManager = DiskWriterManager.getInstance(); private static final DiskReaderManager diskReaderManager = DiskReaderManager.getInstance(); - + private static final ConsoleReportingTask thisInstace = new ConsoleReportingTask(); // private final DateFormat dateFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss"); private final DateFormat dateFormat = new SimpleDateFormat("dd/MM HH:mm:ss"); - private final Set oldReaderSessions = new TreeSet(); - private final Set oldWriterSessions = new TreeSet(); - - private static final ConsoleReportingTask thisInstace = new ConsoleReportingTask(); - private final boolean customLog; private ConsoleReportingTask() { @@ -69,7 +64,7 @@ private final boolean reportStatus(final Set currentSessionSet, fina sb.append(oldSessionSet.size()).append(" active sessions:"); } - for (Iterator it = oldSessionSet.iterator(); it.hasNext();) { + for (Iterator it = oldSessionSet.iterator(); it.hasNext(); ) { final FDTSession fdtSession = it.next(); final TCPTransportProvider tcpTransportProvider = fdtSession.getTransportProvider(); diff --git a/src/lia/util/net/copy/monitoring/DiskReaderManagerMonitoringTask.java b/src/lia/util/net/copy/monitoring/DiskReaderManagerMonitoringTask.java index f750d9f..ead0436 100644 --- a/src/lia/util/net/copy/monitoring/DiskReaderManagerMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/DiskReaderManagerMonitoringTask.java @@ -3,44 +3,43 @@ */ package lia.util.net.copy.monitoring; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import lia.util.net.common.Config; import lia.util.net.common.Utils; import lia.util.net.copy.Accountable; import lia.util.net.copy.disk.DiskReaderManager; import lia.util.net.copy.monitoring.base.AbstractAccountableMonitoringTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + /** - * * Monitors disk activity - * + * * @author ramiro */ public class DiskReaderManagerMonitoringTask extends AbstractAccountableMonitoringTask { - Config config = Config.getInstance(); private static AtomicBoolean inited = new AtomicBoolean(false); - + Config config = Config.getInstance(); + public DiskReaderManagerMonitoringTask(DiskReaderManager drm) { - super(new Accountable[] {drm}); + super(new Accountable[]{drm}); } - + public void rateComputed() { - if(inited.compareAndSet(false, true) && config.getHostName() != null && config.isLisaRestartEnabled() && !config.isLisaDisabled()) { + if (inited.compareAndSet(false, true) && config.getHostName() != null && config.isLisaRestartEnabled() && !config.isLisaDisabled()) { //is Client lisa is enabled and restart also... start monitoring transfer Utils.getMonitoringExecService().scheduleWithFixedDelay(new ClientTransportMonitorTask(this), 6, 6, TimeUnit.SECONDS); - } + } - if(DiskReaderManager.getInstance().sessionsSize() == 0) { + if (DiskReaderManager.getInstance().sessionsSize() == 0) { resetAllCounters(); } } - + public double getTotalRate() { return getTotalRate(DiskReaderManager.getInstance()); } - + } diff --git a/src/lia/util/net/copy/monitoring/DiskWriterManagerMonitoringTask.java b/src/lia/util/net/copy/monitoring/DiskWriterManagerMonitoringTask.java index fa07a9a..7e311bc 100644 --- a/src/lia/util/net/copy/monitoring/DiskWriterManagerMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/DiskWriterManagerMonitoringTask.java @@ -8,25 +8,23 @@ import lia.util.net.copy.monitoring.base.AbstractAccountableMonitoringTask; /** - * - * Monitors disk activity - * + * Monitors disk activity + * * @author ramiro - * */ public class DiskWriterManagerMonitoringTask extends AbstractAccountableMonitoringTask { - + private final DiskWriterManager diskWriterManager; - - + + public DiskWriterManagerMonitoringTask(DiskWriterManager diskWriterManager) { - super(new Accountable[] {diskWriterManager}); + super(new Accountable[]{diskWriterManager}); this.diskWriterManager = diskWriterManager; } - + public void rateComputed() { - if(diskWriterManager.sessionsSize() == 0) { + if (diskWriterManager.sessionsSize() == 0) { resetAllCounters(); } } diff --git a/src/lia/util/net/copy/monitoring/DiskWriterMonitoringTask.java b/src/lia/util/net/copy/monitoring/DiskWriterMonitoringTask.java index 526a66f..0463777 100644 --- a/src/lia/util/net/copy/monitoring/DiskWriterMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/DiskWriterMonitoringTask.java @@ -3,17 +3,18 @@ */ package lia.util.net.copy.monitoring; +import apmon.ApMon; +import lia.util.net.common.Utils; +import lia.util.net.copy.disk.DiskWriterTask; + import java.util.Date; import java.util.Vector; import java.util.concurrent.locks.Lock; import java.util.logging.Logger; -import lia.util.net.common.Utils; -import lia.util.net.copy.disk.DiskWriterTask; -import apmon.ApMon; - /** - * Monitors disk activity per writer + * Monitors disk activity per writer + * * @author ramiro */ public class DiskWriterMonitoringTask implements Runnable { @@ -21,54 +22,57 @@ public class DiskWriterMonitoringTask implements Runnable { private static final Logger logger = Logger.getLogger(DiskWriterManagerMonitoringTask.class.getName()); private final Lock countersRLock; - + long lastDtTake; long lastDtWrite; long lastDtFinishSession; long lastDtTotal; - boolean initialized = false; + boolean initialized = false; StringBuilder sb = new StringBuilder(); DiskWriterTask writerTask; - + Vector paramNames = new Vector(); + Vector paramValues = new Vector(); + Vector valueTypes = new Vector(); + + public DiskWriterMonitoringTask(DiskWriterTask writerTask) { + this.writerTask = writerTask; + countersRLock = writerTask.getCountersRLock(); + } + String getNiceProcent(double value) { - int aux = (int)(value*100.0); - if ( value >= 1 ) { + int aux = (int) (value * 100.0); + if (value >= 1) { - if ( (aux % 100)!=0 ) - return aux/100f+"%"; + if ((aux % 100) != 0) + return aux / 100f + "%"; - return (int)value+"%"; - } else if ( value < 1 && value > 0 ) { - return aux/100f+"%"; + return (int) value + "%"; + } else if (value < 1 && value > 0) { + return aux / 100f + "%"; } - return value+"%"; + return value + "%"; } - public DiskWriterMonitoringTask(DiskWriterTask writerTask) { - this.writerTask = writerTask; - countersRLock = writerTask.getCountersRLock(); - } - public void run() { sb.setLength(0); sb.append("\n[ FileWriterMonitorTask Status @ ").append(new Date().toString()).append(" ]\n"); sb.append("\n******************************************************\n"); - long diffDtTotal = 0, diffDtWrite =0 , diffDtFinishSession =0, diffDtTake =0, diffDtOther = 0; + long diffDtTotal = 0, diffDtWrite = 0, diffDtFinishSession = 0, diffDtTake = 0, diffDtOther = 0; double procWrite = 0, procFinish = 0, procTake = 0, procOther = 0; boolean reportOk = false; - + countersRLock.lock(); try { - if(this.lastDtTotal != writerTask.dtTotal) { + if (this.lastDtTotal != writerTask.dtTotal) { reportOk = true; - if(initialized) { + if (initialized) { diffDtTotal = writerTask.dtTotal - this.lastDtTotal; diffDtTake = writerTask.dtTake - this.lastDtTake; diffDtFinishSession = writerTask.dtFinishSession - this.lastDtFinishSession; @@ -80,12 +84,12 @@ public void run() { lastDtTotal = writerTask.dtTotal; lastDtWrite = writerTask.dtWrite; lastDtFinishSession = writerTask.dtFinishSession; - } - }finally { + } + } finally { countersRLock.unlock(); } - if(!initialized) { + if (!initialized) { initialized = true; initParams(); return; @@ -94,15 +98,15 @@ public void run() { sb.append("PoolStats:"); // sb.append("\nPayload Pool: [ ").append(bufferPool.getSize()).append(" / ").append(bufferPool.getCapacity()).append(" ]"); // sb.append("\nPacket Header Pool: [ ").append(Utils.getHeaderBufferPool().getSize()).append(" / ").append(Utils.getHeaderBufferPool().getCapacity()).append(" ]"); - if(reportOk) { + if (reportOk) { - procWrite = ( diffDtWrite * 100D ) / diffDtTotal; - procFinish = ( diffDtFinishSession *100D ) / diffDtTotal; - procTake = ( diffDtTake * 100D ) / diffDtTotal; + procWrite = (diffDtWrite * 100D) / diffDtTotal; + procFinish = (diffDtFinishSession * 100D) / diffDtTotal; + procTake = (diffDtTake * 100D) / diffDtTotal; procOther = 100 - (procWrite + procFinish + procTake); sb.append("\n DT = ").append(diffDtTotal); - sb.append(" DtTake = ").append( diffDtTake ).append(" ( ").append(getNiceProcent(procTake)).append(" ) "); + sb.append(" DtTake = ").append(diffDtTake).append(" ( ").append(getNiceProcent(procTake)).append(" ) "); sb.append(" DtWrite = ").append(diffDtWrite).append(" ( ").append(getNiceProcent(procWrite)).append(" )"); sb.append(" DtFinish = ").append(diffDtFinishSession).append(" ( ").append(getNiceProcent(procFinish)).append(" )"); sb.append(" DtOther = ").append(diffDtOther).append(" ( ").append(getNiceProcent(procOther)).append(" )"); @@ -112,16 +116,16 @@ public void run() { System.out.println(sb.toString()); //use the apmon monitoring - if ( Utils.getApMon()!=null ) { + if (Utils.getApMon() != null) { try { // Utils.getApMon().sendParameter( null, null, "PoolStat_Available", bufferPool.getSize()); // Utils.getApMon().sendParameter( null, null, "PoolStat_Total", bufferPool.getCapacity()); - if(reportOk) { + if (reportOk) { paramValues.set(0, new Double(procTake)); paramValues.set(1, new Double(procWrite)); paramValues.set(2, new Double(procFinish)); paramValues.set(3, new Double(procOther)); - Utils.getApMon().sendParameters( null, null, paramNames.size(), paramNames, valueTypes, paramValues); + Utils.getApMon().sendParameters(null, null, paramNames.size(), paramNames, valueTypes, paramValues); } } catch (Exception ex) { logger.warning("Could not send monitoring information to MonALISA."); @@ -130,16 +134,14 @@ public void run() { } }//run() - Vector paramNames = new Vector(); - Vector paramValues = new Vector(); - Vector valueTypes = new Vector(); public void initParams() { - String[] names = { "Network_GET", "Disk_PUT", "File_CLOSE", "Other"}; - for ( int i=0; i= 1) { - - if ((aux % 100) != 0) return aux / 100f + "%"; - - return (int) value + "%"; - } else if (value < 1 && value > 0) { return aux / 100f + "%"; } - - return value + "%"; + static { + synchronized (FDTInternalMonitoringTask.class) { + _theInstance = new FDTInternalMonitoringTask(); + initialized = true; + FDTInternalMonitoringTask.class.notifyAll(); + } } //Params to monitor int dbpool_total; - int dbpool_free; - int hpool_total; - int hpool_free; - int mon_queue_count; - int fdt_wdisk_ses_count; - int fdt_rdisk_ses_count; - //It's used from a single thread StringBuilder sb = null; - HashMap> hmWriters; - static { - synchronized (FDTInternalMonitoringTask.class) { - _theInstance = new FDTInternalMonitoringTask(); - initialized = true; - FDTInternalMonitoringTask.class.notifyAll(); - } - } - //only one instance per application private FDTInternalMonitoringTask() { sb = new StringBuilder(2048); hmWriters = new HashMap>(); } + private static final String getNiceProcent(double value) { + + int aux = (int) (value * 100.0); + if (value >= 1) { + + if ((aux % 100) != 0) return aux / 100f + "%"; + + return (int) value + "%"; + } else if (value < 1 && value > 0) { + return aux / 100f + "%"; + } + + return value + "%"; + } + public static final FDTInternalMonitoringTask getInstance() { if (!initialized) { synchronized (FDTInternalMonitoringTask.class) { @@ -153,7 +108,7 @@ private final void printStats() { sb.append(" Disk Writer Sessions: ").append(fdt_wdisk_ses_count).append(" Disk Reader Sessions: ").append(fdt_rdisk_ses_count); sb.append(EOL); - for (Iterator>> it = hmWriters.entrySet().iterator(); it.hasNext();) { + for (Iterator>> it = hmWriters.entrySet().iterator(); it.hasNext(); ) { Map.Entry> entry = it.next(); Integer id = entry.getKey(); @@ -230,7 +185,7 @@ private void updateWritersAccounting() { logger.log(Level.FINER, " The writer seem idle same time [ " + wac.lastDtTotal + " ] as in previous iteration "); } } - }finally { + } finally { wac.countersRLock.unlock(); } @@ -258,7 +213,7 @@ private void updateWritersAccounting() { }//end for //check for dead writers - for (Iterator it = hmWriters.keySet().iterator(); it.hasNext();) { + for (Iterator it = hmWriters.keySet().iterator(); it.hasNext(); ) { Integer partitionID = it.next(); if (!currentWriters.containsKey(partitionID)) { it.remove(); @@ -280,13 +235,13 @@ public HashMap getLisaParams() { fdtLisaParams.put("fdt_ses_rdisk", (double) fdt_rdisk_ses_count); //Writers accounting - for (Iterator>> it = hmWriters.entrySet().iterator(); it.hasNext();) { + for (Iterator>> it = hmWriters.entrySet().iterator(); it.hasNext(); ) { Map.Entry> entry = it.next(); Integer pid = entry.getKey(); HashMap wacMap = entry.getValue(); - for (Iterator> iti = wacMap.entrySet().iterator(); iti.hasNext();) { + for (Iterator> iti = wacMap.entrySet().iterator(); iti.hasNext(); ) { Map.Entry ientry = iti.next(); Integer wID = ientry.getKey(); @@ -339,4 +294,32 @@ public void run() { } } + private static final class WriterAccountingContors { + + private final Lock countersRLock; + + boolean reportOk; + + long lastDtTake; + + long lastDtWrite; + + long lastDtFinishSession; + + long lastDtTotal; + + double procWrite = 0; + + double procFinish = 0; + + double procTake = 0; + + double procOther = 0; + + WriterAccountingContors(final Lock lock) { + this.countersRLock = lock; + reportOk = false; + } + } + } diff --git a/src/lia/util/net/copy/monitoring/FDTReportingTask.java b/src/lia/util/net/copy/monitoring/FDTReportingTask.java index 2345209..bdc0213 100644 --- a/src/lia/util/net/copy/monitoring/FDTReportingTask.java +++ b/src/lia/util/net/copy/monitoring/FDTReportingTask.java @@ -3,86 +3,85 @@ */ package lia.util.net.copy.monitoring; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - import lia.util.net.common.Utils; import lia.util.net.copy.FDTSession; import lia.util.net.copy.disk.DiskReaderManager; import lia.util.net.copy.disk.DiskWriterManager; import lia.util.net.copy.transport.TCPTransportProvider; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * Abstract implementation for internal monitoring and reporting - * - * @author ramiro * + * @author ramiro */ public abstract class FDTReportingTask implements Runnable { - + + protected static final DiskReaderManager diskReaderManager = DiskReaderManager.getInstance(); + protected static final DiskWriterManager diskWriterManager = DiskWriterManager.getInstance(); private static final Logger logger = Logger.getLogger("lia.util.net.copy.monitoring.FDTReportingTask"); - + public abstract void finishFDTSession(final FDTSession fdtSession); + public abstract void startFDTSession(final FDTSession fdtSession); - - protected static final DiskReaderManager diskReaderManager = DiskReaderManager.getInstance(); - protected static final DiskWriterManager diskWriterManager = DiskWriterManager.getInstance(); - + public HashMap> getReaderParams() { final HashMap> monitoringParams = new HashMap>(); FDTSession fdtSession = null; Iterator it = null; - + double rate = 0; - - final Set fdtSessions = diskReaderManager.getSessions(); + + final Set fdtSessions = diskReaderManager.getSessions(); //Client monitoring - DiskReaderManager it = fdtSessions.iterator(); - - if(logger.isLoggable(Level.FINEST)) { + + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "FDTReportingTask - ReaderSessions: " + fdtSessions); } - - while(it.hasNext()) { + + while (it.hasNext()) { fdtSession = it.next(); TCPTransportProvider transportProvider = fdtSession.getTransportProvider(); HashMap fdtSessionParams = new HashMap(); - - if(transportProvider != null && transportProvider.monitoringTask != null) { - rate = ( transportProvider.monitoringTask.getTotalRate() * 8D ) / Utils.MEGA_BIT; + + if (transportProvider != null && transportProvider.monitoringTask != null) { + rate = (transportProvider.monitoringTask.getTotalRate() * 8D) / Utils.MEGA_BIT; fdtSessionParams.put("NET_OUT_Mb", rate); } - + final FDTSessionMonitoringTask fdtSessionMTask = fdtSession.getMonitoringTask(); - if(fdtSessionMTask != null) { + if (fdtSessionMTask != null) { rate = fdtSessionMTask.getTotalRate() / Utils.MEGA_BYTE; fdtSessionParams.put("DISK_READ_MB", rate); - final double tSize = (fdtSession.getSize() <= 0L)?0D:(fdtSession.getSize() / (double)Utils.MEGA_BYTE); - final double cSize = (fdtSession.getTotalBytes() <= 0L)?0D:(fdtSession.getTotalBytes()/ (double)Utils.MEGA_BYTE); + final double tSize = (fdtSession.getSize() <= 0L) ? 0D : (fdtSession.getSize() / (double) Utils.MEGA_BYTE); + final double cSize = (fdtSession.getTotalBytes() <= 0L) ? 0D : (fdtSession.getTotalBytes() / (double) Utils.MEGA_BYTE); fdtSessionParams.put("TotalMBytes", tSize); fdtSessionParams.put("TransferredMBytes", cSize); - if(fdtSession.getSize() > 0L) { - fdtSessionParams.put("TransferRatio", (cSize*100)/tSize); + if (fdtSession.getSize() > 0L) { + fdtSessionParams.put("TransferRatio", (cSize * 100) / tSize); } } else { - if(logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "No FDTSessionMonitoringTask started for fdtSession: " + fdtSession); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "No FDTSessionMonitoringTask started for fdtSession: " + fdtSession); } } - - final String monID = fdtSession.getMonID(); - if(monID != null) { + + final String monID = fdtSession.getMonID(); + if (monID != null) { monitoringParams.put(monID, fdtSessionParams); } else { - monitoringParams.put(fdtSession.getRemoteAddress().getHostAddress()+":"+fdtSession.getRemotePort(), fdtSessionParams); + monitoringParams.put(fdtSession.getRemoteAddress().getHostAddress() + ":" + fdtSession.getRemotePort(), fdtSessionParams); } } - if(logger.isLoggable(Level.FINEST)) { + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "FDTReportingTask - returning ReaderParams: " + monitoringParams); } @@ -94,46 +93,46 @@ public HashMap> getWriterParams() { FDTSession fdtSession = null; Iterator it = null; - + //Server monitoring - DiskWriterManager it = diskWriterManager.getSessions().iterator(); double rate = 0; - - while(it.hasNext()) { + + while (it.hasNext()) { fdtSession = it.next(); TCPTransportProvider transportProvider = fdtSession.getTransportProvider(); HashMap fdtSessionParams = new HashMap(); - - if(transportProvider != null && transportProvider.monitoringTask != null) { - rate = ( transportProvider.monitoringTask.getTotalRate() * 8 ) / Utils.MEGA_BIT; + + if (transportProvider != null && transportProvider.monitoringTask != null) { + rate = (transportProvider.monitoringTask.getTotalRate() * 8) / Utils.MEGA_BIT; fdtSessionParams.put("NET_IN_Mb", rate); } - + final FDTSessionMonitoringTask fdtSessionMTask = fdtSession.getMonitoringTask(); - if(fdtSessionMTask != null) { + if (fdtSessionMTask != null) { rate = fdtSessionMTask.getTotalRate() / Utils.MEGA_BYTE; fdtSessionParams.put("DISK_WRITE_MB", rate); - final double tSize = (fdtSession.getSize() <= 0L)?0D:(fdtSession.getSize()/(double)Utils.MEGA_BYTE); - final double cSize = (fdtSession.getTotalBytes() <= 0L)?0D:(fdtSession.getTotalBytes()/(double)Utils.MEGA_BYTE); + final double tSize = (fdtSession.getSize() <= 0L) ? 0D : (fdtSession.getSize() / (double) Utils.MEGA_BYTE); + final double cSize = (fdtSession.getTotalBytes() <= 0L) ? 0D : (fdtSession.getTotalBytes() / (double) Utils.MEGA_BYTE); fdtSessionParams.put("TotalMBytes", tSize); fdtSessionParams.put("TransferredMBytes", cSize); - if(fdtSession.getSize() > 0L) { - fdtSessionParams.put("TransferRatio", (cSize*100)/tSize); + if (fdtSession.getSize() > 0L) { + fdtSessionParams.put("TransferRatio", (cSize * 100) / tSize); } } else { - if(logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "No FDTSessionMonitoringTask started for fdtSession: " + fdtSession); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "No FDTSessionMonitoringTask started for fdtSession: " + fdtSession); } } - - final String monID = fdtSession.getMonID(); - if(monID != null) { + + final String monID = fdtSession.getMonID(); + if (monID != null) { monitoringParams.put(monID, fdtSessionParams); } else { - monitoringParams.put(fdtSession.getRemoteAddress().getHostAddress()+":"+fdtSession.getRemotePort(), fdtSessionParams); + monitoringParams.put(fdtSession.getRemoteAddress().getHostAddress() + ":" + fdtSession.getRemotePort(), fdtSessionParams); } - + } return monitoringParams; diff --git a/src/lia/util/net/copy/monitoring/FDTSessionMonitoringTask.java b/src/lia/util/net/copy/monitoring/FDTSessionMonitoringTask.java index 0727458..964a3a5 100644 --- a/src/lia/util/net/copy/monitoring/FDTSessionMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/FDTSessionMonitoringTask.java @@ -3,8 +3,6 @@ */ package lia.util.net.copy.monitoring; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import lia.util.net.common.Utils; import lia.util.net.copy.Accountable; import lia.util.net.copy.FDTSession; @@ -12,8 +10,10 @@ import lia.util.net.copy.monitoring.lisa.CmdCheckerTask; import lia.util.net.copy.monitoring.lisa.LISAReportingTask; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + /** - * * @author ramiro */ public class FDTSessionMonitoringTask extends AbstractAccountableMonitoringTask { @@ -38,7 +38,7 @@ public void finishSession() { final LISAReportingTask lisaReportingTask = LISAReportingTask.getInstanceNow(); if (lisaReportingTask != null) { lisaReportingTask.finishFDTSession(fdtSession); - if(cmdCheckerTaskFuture != null) { + if (cmdCheckerTaskFuture != null) { cmdCheckerTaskFuture.cancel(true); } } diff --git a/src/lia/util/net/copy/monitoring/NetSessionMonitoringTask.java b/src/lia/util/net/copy/monitoring/NetSessionMonitoringTask.java index 75abb67..d5f3751 100644 --- a/src/lia/util/net/copy/monitoring/NetSessionMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/NetSessionMonitoringTask.java @@ -3,14 +3,14 @@ */ package lia.util.net.copy.monitoring; -import java.util.logging.Level; -import java.util.logging.Logger; import lia.util.net.copy.Accountable; import lia.util.net.copy.monitoring.base.AbstractAccountableMonitoringTask; import lia.util.net.copy.transport.TCPTransportProvider; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * * @author ramiro */ public class NetSessionMonitoringTask extends AbstractAccountableMonitoringTask { @@ -18,11 +18,12 @@ public class NetSessionMonitoringTask extends AbstractAccountableMonitoringTask private static final Logger logger = Logger.getLogger(NetSessionMonitoringTask.class.getName()); private final TCPTransportProvider transportProvider; + public NetSessionMonitoringTask(TCPTransportProvider transportProvider) { - super(new Accountable[] {transportProvider}); - - if(logger.isLoggable(Level.FINEST)) { - logger.log(Level.FINEST, "[ NetSessionMonitoringTask ] for transportProvider " + transportProvider + " instantiating", new Exception(" Debug stack trace")) ; + super(new Accountable[]{transportProvider}); + + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "[ NetSessionMonitoringTask ] for transportProvider " + transportProvider + " instantiating", new Exception(" Debug stack trace")); } this.transportProvider = transportProvider; @@ -31,11 +32,11 @@ public NetSessionMonitoringTask(TCPTransportProvider transportProvider) { public double getTotalRate() { return getTotalRate(transportProvider); } - + @Override public void rateComputed() { // TODO Auto-generated method stub - + } } diff --git a/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java b/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java index 2763fe3..9c3349f 100644 --- a/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java @@ -1,5 +1,7 @@ package lia.util.net.copy.monitoring.base; +import lia.util.net.copy.Accountable; + import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; @@ -7,15 +9,12 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.Accountable; - /** * Generic class to monitor an {@link Accountable} class - * + *

    * The subclasses should only call super.computeRate() - * + * * @author ramiro - * */ public abstract class AbstractAccountableMonitoringTask implements Runnable { @@ -23,55 +22,6 @@ public abstract class AbstractAccountableMonitoringTask implements Runnable { private final ConcurrentHashMap accMap = new ConcurrentHashMap(); - private static class AccountableEntry { - final boolean debug; - - long monCount; - - protected long startTime; - protected long lastTimeCalled; - - protected long lastUtilBytes; - protected long currentUtilBytes; - protected long lastTotalBytes; - protected long currentTotalBytes; - - protected long startUtilBytes; - protected long startTotalBytes; - - protected double utilRate; - protected double totalRate; - - protected double avgUtilRate; - protected double avgTotalRate; - - AccountableEntry() { - this(false); - } - - AccountableEntry(boolean debug) { - this.debug = debug; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(" startTimeMillis: ").append(startTime).append(", "); - sb.append(" startUtilBytes: ").append(startUtilBytes).append(", "); - sb.append(" startTotalBytes: ").append(startTotalBytes).append(", "); - sb.append(" lastTimeCalled: ").append(lastTimeCalled).append(", "); - sb.append(" lastUtilBytes: ").append(lastUtilBytes).append(", "); - sb.append(" currentUtilBytes: ").append(currentUtilBytes).append(", "); - sb.append(" lastTotalBytes: ").append(lastTotalBytes).append(", "); - sb.append(" currentTotalBytes: ").append(currentTotalBytes).append(", "); - sb.append(" utilRate: ").append(utilRate).append(", "); - sb.append(" totalRate: ").append(totalRate).append(", "); - sb.append(" avgUtilRate: ").append(avgUtilRate).append(", "); - sb.append(" avgUtilRate: ").append(avgUtilRate); - return sb.toString(); - } - } - public AbstractAccountableMonitoringTask(Accountable[] accountableList) { //nehotarat mai esti ... poate vrei mai tarliu if (accountableList == null) { @@ -252,4 +202,47 @@ protected boolean addIfAbsent(final Accountable accountable, boolean debug) { protected boolean remove(final Accountable accountable) { return (accMap.remove(accountable) != null); } + + private static class AccountableEntry { + final boolean debug; + protected long startTime; + protected long lastTimeCalled; + protected long lastUtilBytes; + protected long currentUtilBytes; + protected long lastTotalBytes; + protected long currentTotalBytes; + protected long startUtilBytes; + protected long startTotalBytes; + protected double utilRate; + protected double totalRate; + protected double avgUtilRate; + protected double avgTotalRate; + long monCount; + + AccountableEntry() { + this(false); + } + + AccountableEntry(boolean debug) { + this.debug = debug; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(" startTimeMillis: ").append(startTime).append(", "); + sb.append(" startUtilBytes: ").append(startUtilBytes).append(", "); + sb.append(" startTotalBytes: ").append(startTotalBytes).append(", "); + sb.append(" lastTimeCalled: ").append(lastTimeCalled).append(", "); + sb.append(" lastUtilBytes: ").append(lastUtilBytes).append(", "); + sb.append(" currentUtilBytes: ").append(currentUtilBytes).append(", "); + sb.append(" lastTotalBytes: ").append(lastTotalBytes).append(", "); + sb.append(" currentTotalBytes: ").append(currentTotalBytes).append(", "); + sb.append(" utilRate: ").append(utilRate).append(", "); + sb.append(" totalRate: ").append(totalRate).append(", "); + sb.append(" avgUtilRate: ").append(avgUtilRate).append(", "); + sb.append(" avgUtilRate: ").append(avgUtilRate); + return sb.toString(); + } + } } diff --git a/src/lia/util/net/copy/monitoring/jmx/DBPoolJMX.java b/src/lia/util/net/copy/monitoring/jmx/DBPoolJMX.java index 5c08c27..a4d4e97 100644 --- a/src/lia/util/net/copy/monitoring/jmx/DBPoolJMX.java +++ b/src/lia/util/net/copy/monitoring/jmx/DBPoolJMX.java @@ -5,18 +5,11 @@ */ package lia.util.net.copy.monitoring.jmx; -import java.util.Arrays; - -import javax.management.MBeanAttributeInfo; -import javax.management.MBeanInfo; -import javax.management.MBeanNotificationInfo; -import javax.management.MBeanOperationInfo; -import javax.management.MBeanParameterInfo; -import javax.management.NotCompliantMBeanException; -import javax.management.StandardMBean; - import lia.util.net.common.DirectByteBufferPool; +import javax.management.*; +import java.util.Arrays; + /** * Class DBPoolJMX; just to test the JMX. Nothing intelligent, for the moment * @@ -148,6 +141,7 @@ public int getSize() { /** * Operation exposed for management + * * @return long */ public long totalAllocated() { diff --git a/src/lia/util/net/copy/monitoring/jmx/DBPoolJMXMBean.java b/src/lia/util/net/copy/monitoring/jmx/DBPoolJMXMBean.java index cc92533..3f8dd16 100644 --- a/src/lia/util/net/copy/monitoring/jmx/DBPoolJMXMBean.java +++ b/src/lia/util/net/copy/monitoring/jmx/DBPoolJMXMBean.java @@ -29,6 +29,7 @@ public interface DBPoolJMXMBean { /** * Operation exposed for management + * * @return long */ public long totalAllocated(); diff --git a/src/lia/util/net/copy/monitoring/lisa/CmdCheckerTask.java b/src/lia/util/net/copy/monitoring/lisa/CmdCheckerTask.java index 579e559..436cb22 100644 --- a/src/lia/util/net/copy/monitoring/lisa/CmdCheckerTask.java +++ b/src/lia/util/net/copy/monitoring/lisa/CmdCheckerTask.java @@ -3,20 +3,21 @@ */ package lia.util.net.copy.monitoring.lisa; +import lia.util.net.copy.FDTSession; + import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.FDTSession; - /** - * * Periodically checks for remote commands for a specific FDT session - * + * * @author ramiro */ public class CmdCheckerTask implements Runnable { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final Logger logger = Logger.getLogger(CmdCheckerTask.class.getName()); private final FDTSession fdtSession; private final LISAReportingTask lisaReportingTask; @@ -30,11 +31,11 @@ public CmdCheckerTask(final FDTSession fdtSession, LISAReportingTask lrt, LisaCt } public void run() { - if(cmdToSend == null){ - cmdToSend = "exec FDTClientController getControlParams " + fdtSession.getMonID(); + if (cmdToSend == null) { + cmdToSend = "exec FDTClientController getControlParams " + fdtSession.getMonID(); logger.log(Level.INFO, "[ CmdCheckerTask ] LISA/ML remote command checker started with sessionID: " + fdtSession.getMonID()); - } - + } + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "[ CmdCheckerTask ] LISA/ML remote command checker for sessionID: " + fdtSession.getMonID() + " running"); } @@ -44,11 +45,11 @@ public void run() { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "[ CmdCheckerTask ] for sessionID: " + fdtSession.getMonID() + " received: " + response); } - - if(response != null) { + + if (response != null) { notifier.notifyLisaCtrlMsg(response); } - + } catch (Throwable t) { logger.log(Level.WARNING, "[ CmdCheckerTask ] Exception in main loop ", t); } diff --git a/src/lia/util/net/copy/monitoring/lisa/HostPropertiesMonitor.java b/src/lia/util/net/copy/monitoring/lisa/HostPropertiesMonitor.java index 439a9a8..f500eb2 100755 --- a/src/lia/util/net/copy/monitoring/lisa/HostPropertiesMonitor.java +++ b/src/lia/util/net/copy/monitoring/lisa/HostPropertiesMonitor.java @@ -13,423 +13,425 @@ import java.util.logging.Logger; /** - * * @author Ciprian Dobre */ public class HostPropertiesMonitor { - - final static String osName = System.getProperty("os.name"); - ProcReader reader = null; - MacHostPropertiesMonitor macHostMonitor = null; - - static { - if (osName.indexOf("Win")>=0) { - saveSystemLibrary(HostPropertiesMonitor.class); - } - } - - String macAddress; - HashMap cpuvm; - HashMap mem; - HashMap disk; - int processes; - HashMap load; - HashMap net; - - public static void saveSystemLibrary(Class baseClass) { - try { - URL url = baseClass.getResource("system.dll"); // here should be the system library package (for jni) - File file = new File("system.dll"); - if (!file.exists()) { - byte[] buffer = new byte[1024]; - URLConnection con = url.openConnection(); - InputStream in = con.getInputStream(); - FileOutputStream out = new FileOutputStream(file); - int n; - while ((n = in.read(buffer, 0, buffer.length)) != -1) - out.write(buffer, 0, n); - in.close(); - out.close(); - } - System.load(file.getAbsolutePath()); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - public HostPropertiesMonitor(final Logger logger) { - if (osName.indexOf("Linux") != -1){ - reader = new ProcReader(logger); - } else if (osName.indexOf("Mac") != -1){ - macHostMonitor = new MacHostPropertiesMonitor(logger); - } - } - - public native String getMacAddresses(); - - public String getMacAddressesCall() { - if (osName.indexOf("Linux") != -1) { - return macAddress; - } - if (osName.indexOf("Mac") != -1) - return macHostMonitor.getMacAddresses(); - return getMacAddresses(); - } - - public native void update(); - - public void updateCall() { - if (osName.indexOf("Linux") != -1) { - try { - macAddress = reader.getMACAddress(); - cpuvm = reader.getCPUVM(); - mem = reader.getMEM(); - disk = reader.getDISK(); - processes = reader.getProcesses(); - load = reader.getLOAD(); - net = reader.getNet(); - } catch (Exception ex) { - ex.printStackTrace(); - } - return; - } - if (osName.indexOf("Mac") != -1) { - macHostMonitor.update(); - return; - } - update(); - } - - private final double get(String val) { - try { - return Double.parseDouble(val); - } catch (Throwable t) { } - return -1.0; - } - - public native String getCpuUsage(); - - public double getCpuUsageCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_Usage"); - } - if (osName.indexOf("Mac") != -1) { - return get(macHostMonitor.getCpuUsage()); - } - return get(getCpuUsage()); - } - - public native String getCpuUSR(); - - public double getCpuUSRCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_usr"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getCpuUSR()); - return get(getCpuUSR()); - } - - public native String getCpuSYS(); - - public double getCpuSYSCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_sys"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getCpuSYS()); - return get(getCpuSYS()); - } - - public native String getCpuNICE(); - - public double getCpuNICECall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_nice"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getCpuNICE()); - return get(getCpuNICE()); - } - - public native String getCpuIDLE(); - - public double getCpuIDLECall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_idle"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getCpuIDLE()); - return get(getCpuIDLE()); - } - - public double getCPUIoWaitCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_iowait"); - } - return -1.0; - } - - public double getCPUIntCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_int"); - } - return -1.0; - } - - public double getCPUSoftIntCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_softint"); - } - return -1.0; - } - - public double getCPUStealCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("CPU_steal"); - } - return -1.0; - } - - public native String getPagesIn(); - - public double getPagesInCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("Page_in"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getPagesIn()); - return get(getPagesIn()); - } - - public native String getPagesOut(); - - public double getPagesOutCall() { - - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("Page_out"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getPagesOut()); - return get(getPagesOut()); - } - - /** Get swap in - works in linux flavors only */ - public double getSwapInCall() { - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("Swap_in"); - } - return -1.0; - } - - public double getSwapOutCall() { - if (osName.indexOf("Linux") != -1) { - if (cpuvm == null) return -1.0; - return cpuvm.get("Swap_out"); - } - return -1.0; - } - - public native String getMemUsage(); - - public double getMemUsageCall() { - if (osName.indexOf("Linux") != -1) { - if (mem == null) return -1.0; - return mem.get("MemUsage"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getMemUsage()); - return get(getMemUsage()); - } - - public native String getMemUsed(); - - public double getMemUsedCall() { - if (osName.indexOf("Linux") != -1) { - if (mem == null) return -1.0; - return mem.get("MemUsed"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getMemUsed()); - return get(getMemUsed()); - } - - public native String getMemFree(); - - public double getMemFreeCall() { - if (osName.indexOf("Linux") != -1) { - if (mem == null) return -1.0; - return mem.get("MemFree"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getMemFree()); - return get(getMemFree()); - } - - public native String getDiskIO(); - - public double getDiskIOCall() { - if (osName.indexOf("Linux") != -1) { - if (disk == null) return -1.0; - return disk.get("DiskIO"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getDiskIO()); - return get(getDiskIO()); - } - - public native String getDiskTotal(); - - public double getDiskTotalCall() { - if (osName.indexOf("Linux") != -1) { - if (disk == null) return -1.0; - return disk.get("DiskTotal"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getDiskTotal()); - return get(getDiskTotal()); - } - - public native String getDiskUsed(); - - public double getDiskUsedCall() { - if (osName.indexOf("Linux") != -1) { - if (disk == null) return -1.0; - return disk.get("DiskUsed"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getDiskUsed()); - return get(getDiskUsed()); - } - - public native String getDiskFree(); - - public double getDiskFreeCall() { - if (osName.indexOf("Linux") != -1) { - if (disk == null) return -1.0; - return disk.get("DiskFree"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getDiskFree()); - return get(getDiskFree()); - } - - public native String getNoProcesses(); - - public int getNoProcessesCall() { - if (osName.indexOf("Linux") != -1) { - return processes; - } - if (osName.indexOf("Mac") != -1) - return (int)get(macHostMonitor.getNoProcesses()); - return (int)get(getNoProcesses()); - } - - public native String getLoad1(); - - public double getLoad1Call() { - if (osName.indexOf("Linux") != -1) { - if (load == null) return -1.0; - return load.get("Load1"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getLoad1()); - return get(getLoad1()); - } - - public native String getLoad5(); - - public double getLoad5Call() { - if (osName.indexOf("Linux") != -1) { - if (load == null) return -1.0; - return load.get("Load5"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getLoad5()); - return get(getLoad5()); - } - - public native String getLoad15(); - - public double getLoad15Call() { - if (osName.indexOf("Linux") != -1) { - if (load == null) return -1.0; - return load.get("Load15"); - } - if (osName.indexOf("Mac") != -1) - return get(macHostMonitor.getLoad15()); - return get(getLoad15()); - } - - public native String[] getNetInterfaces(); - - public String[] getNetInterfacesCall() { - if (osName.indexOf("Linux") != -1) { - if (net == null) return null; - final String[] ret = new String[net.size() / 2]; - int i=0; - for (Iterator it = net.keySet().iterator();it.hasNext() && i= 0) { + saveSystemLibrary(HostPropertiesMonitor.class); + } + } + + ProcReader reader = null; + MacHostPropertiesMonitor macHostMonitor = null; + String macAddress; + HashMap cpuvm; + HashMap mem; + HashMap disk; + int processes; + HashMap load; + HashMap net; + + public HostPropertiesMonitor(final Logger logger) { + if (osName.indexOf("Linux") != -1) { + reader = new ProcReader(logger); + } else if (osName.indexOf("Mac") != -1) { + macHostMonitor = new MacHostPropertiesMonitor(logger); + } + } + + public static void saveSystemLibrary(Class baseClass) { + try { + URL url = baseClass.getResource("system.dll"); // here should be the system library package (for jni) + File file = new File("system.dll"); + if (!file.exists()) { + byte[] buffer = new byte[1024]; + URLConnection con = url.openConnection(); + InputStream in = con.getInputStream(); + FileOutputStream out = new FileOutputStream(file); + int n; + while ((n = in.read(buffer, 0, buffer.length)) != -1) + out.write(buffer, 0, n); + in.close(); + out.close(); + } + System.load(file.getAbsolutePath()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public native String getMacAddresses(); + + public String getMacAddressesCall() { + if (osName.indexOf("Linux") != -1) { + return macAddress; + } + if (osName.indexOf("Mac") != -1) + return macHostMonitor.getMacAddresses(); + return getMacAddresses(); + } + + public native void update(); + + public void updateCall() { + if (osName.indexOf("Linux") != -1) { + try { + macAddress = reader.getMACAddress(); + cpuvm = reader.getCPUVM(); + mem = reader.getMEM(); + disk = reader.getDISK(); + processes = reader.getProcesses(); + load = reader.getLOAD(); + net = reader.getNet(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return; + } + if (osName.indexOf("Mac") != -1) { + macHostMonitor.update(); + return; + } + update(); + } + + private final double get(String val) { + try { + return Double.parseDouble(val); + } catch (Throwable t) { + } + return -1.0; + } + + public native String getCpuUsage(); + + public double getCpuUsageCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_Usage"); + } + if (osName.indexOf("Mac") != -1) { + return get(macHostMonitor.getCpuUsage()); + } + return get(getCpuUsage()); + } + + public native String getCpuUSR(); + + public double getCpuUSRCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_usr"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getCpuUSR()); + return get(getCpuUSR()); + } + + public native String getCpuSYS(); + + public double getCpuSYSCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_sys"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getCpuSYS()); + return get(getCpuSYS()); + } + + public native String getCpuNICE(); + + public double getCpuNICECall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_nice"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getCpuNICE()); + return get(getCpuNICE()); + } + + public native String getCpuIDLE(); + + public double getCpuIDLECall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_idle"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getCpuIDLE()); + return get(getCpuIDLE()); + } + + public double getCPUIoWaitCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_iowait"); + } + return -1.0; + } + + public double getCPUIntCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_int"); + } + return -1.0; + } + + public double getCPUSoftIntCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_softint"); + } + return -1.0; + } + + public double getCPUStealCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("CPU_steal"); + } + return -1.0; + } + + public native String getPagesIn(); + + public double getPagesInCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("Page_in"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getPagesIn()); + return get(getPagesIn()); + } + + public native String getPagesOut(); + + public double getPagesOutCall() { + + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("Page_out"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getPagesOut()); + return get(getPagesOut()); + } + + /** + * Get swap in - works in linux flavors only + */ + public double getSwapInCall() { + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("Swap_in"); + } + return -1.0; + } + + public double getSwapOutCall() { + if (osName.indexOf("Linux") != -1) { + if (cpuvm == null) return -1.0; + return cpuvm.get("Swap_out"); + } + return -1.0; + } + + public native String getMemUsage(); + + public double getMemUsageCall() { + if (osName.indexOf("Linux") != -1) { + if (mem == null) return -1.0; + return mem.get("MemUsage"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getMemUsage()); + return get(getMemUsage()); + } + + public native String getMemUsed(); + + public double getMemUsedCall() { + if (osName.indexOf("Linux") != -1) { + if (mem == null) return -1.0; + return mem.get("MemUsed"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getMemUsed()); + return get(getMemUsed()); + } + + public native String getMemFree(); + + public double getMemFreeCall() { + if (osName.indexOf("Linux") != -1) { + if (mem == null) return -1.0; + return mem.get("MemFree"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getMemFree()); + return get(getMemFree()); + } + + public native String getDiskIO(); + + public double getDiskIOCall() { + if (osName.indexOf("Linux") != -1) { + if (disk == null) return -1.0; + return disk.get("DiskIO"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getDiskIO()); + return get(getDiskIO()); + } + + public native String getDiskTotal(); + + public double getDiskTotalCall() { + if (osName.indexOf("Linux") != -1) { + if (disk == null) return -1.0; + return disk.get("DiskTotal"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getDiskTotal()); + return get(getDiskTotal()); + } + + public native String getDiskUsed(); + + public double getDiskUsedCall() { + if (osName.indexOf("Linux") != -1) { + if (disk == null) return -1.0; + return disk.get("DiskUsed"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getDiskUsed()); + return get(getDiskUsed()); + } + + public native String getDiskFree(); + + public double getDiskFreeCall() { + if (osName.indexOf("Linux") != -1) { + if (disk == null) return -1.0; + return disk.get("DiskFree"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getDiskFree()); + return get(getDiskFree()); + } + + public native String getNoProcesses(); + + public int getNoProcessesCall() { + if (osName.indexOf("Linux") != -1) { + return processes; + } + if (osName.indexOf("Mac") != -1) + return (int) get(macHostMonitor.getNoProcesses()); + return (int) get(getNoProcesses()); + } + + public native String getLoad1(); + + public double getLoad1Call() { + if (osName.indexOf("Linux") != -1) { + if (load == null) return -1.0; + return load.get("Load1"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getLoad1()); + return get(getLoad1()); + } + + public native String getLoad5(); + + public double getLoad5Call() { + if (osName.indexOf("Linux") != -1) { + if (load == null) return -1.0; + return load.get("Load5"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getLoad5()); + return get(getLoad5()); + } + + public native String getLoad15(); + + public double getLoad15Call() { + if (osName.indexOf("Linux") != -1) { + if (load == null) return -1.0; + return load.get("Load15"); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getLoad15()); + return get(getLoad15()); + } + + public native String[] getNetInterfaces(); + + public String[] getNetInterfacesCall() { + if (osName.indexOf("Linux") != -1) { + if (net == null) return null; + final String[] ret = new String[net.size() / 2]; + int i = 0; + for (Iterator it = net.keySet().iterator(); it.hasNext() && i < ret.length; ) { + final String key = it.next(); + if (key.startsWith("In_")) { + ret[i] = key.substring(3); + i++; + } + } + while (i < ret.length) ret[i++] = null; + return ret; + } + if (osName.indexOf("Mac") != -1) + return macHostMonitor.getNetInterfaces(); + return getNetInterfaces(); + } + + public native String getNetIn(String ifName); + + public double getNetInCall(String ifName) { + + if (osName.indexOf("Linux") != -1) { + if (net == null || !net.containsKey("In_" + ifName)) return -1.0; + return net.get("In_" + ifName); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getNetIn(ifName)); + return get(getNetIn(ifName)); + } + + public native String getNetOut(String ifName); + + public double getNetOutCall(String ifName) { + + if (osName.indexOf("Linux") != -1) { + if (net == null || !net.containsKey("Out_" + ifName)) return -1.0; + return net.get("Out_" + ifName); + } + if (osName.indexOf("Mac") != -1) + return get(macHostMonitor.getNetOut(ifName)); + return get(getNetOut(ifName)); + } } // end of class HostPropertiesMonitor diff --git a/src/lia/util/net/copy/monitoring/lisa/LISAReportingTask.java b/src/lia/util/net/copy/monitoring/lisa/LISAReportingTask.java index 24951ae..474db5a 100644 --- a/src/lia/util/net/copy/monitoring/lisa/LISAReportingTask.java +++ b/src/lia/util/net/copy/monitoring/lisa/LISAReportingTask.java @@ -3,11 +3,6 @@ */ package lia.util.net.copy.monitoring.lisa; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - import lia.util.net.common.Config; import lia.util.net.common.Utils; import lia.util.net.copy.FDTReaderSession; @@ -16,29 +11,35 @@ import lia.util.net.copy.monitoring.FDTReportingTask; import lia.util.net.copy.monitoring.FDTSessionMonitoringTask; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * This class is used to send the internal monitoring informations back to LISA/MonALISA * over an (XDR) Socket - * + * * @author ramiro */ public class LISAReportingTask extends FDTReportingTask { private static final Logger logger = Logger.getLogger(LISAReportingTask.class.getName()); - - + private static LISAReportingTask _thisInstance; private final String lisaHost; private final int lisaPort; - volatile public MonClient lisaMon; - private boolean errorReported = false; - - private static LISAReportingTask _thisInstance; - + + private LISAReportingTask(String lisaHost, int lisaPort) { + this.lisaHost = lisaHost; + this.lisaPort = lisaPort; + setupMonClient(); + } + public static final LISAReportingTask initInstance(String lisaHost, int lisaPort) { - synchronized(LISAReportingTask.class) { - if(_thisInstance == null) { + synchronized (LISAReportingTask.class) { + if (_thisInstance == null) { _thisInstance = new LISAReportingTask(lisaHost, lisaPort); LISAReportingTask.class.notifyAll(); } @@ -47,97 +48,91 @@ public static final LISAReportingTask initInstance(String lisaHost, int lisaPort } public static final LISAReportingTask getInstanceNow() { - synchronized(LISAReportingTask.class) { + synchronized (LISAReportingTask.class) { return _thisInstance; } } - + public static final LISAReportingTask getInstance() { - synchronized(LISAReportingTask.class) { - while(_thisInstance == null) { + synchronized (LISAReportingTask.class) { + while (_thisInstance == null) { try { LISAReportingTask.class.wait(5000); logger.log(Level.WARNING, " getInstace timeout on LISAReporting task ... "); - }catch(Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } } return _thisInstance; } } - - private LISAReportingTask(String lisaHost, int lisaPort) { - this.lisaHost = lisaHost; - this.lisaPort = lisaPort; - setupMonClient(); - } - + private void publishStartFinishParams(final FDTSession fdtSession) { - if(fdtSession != null) { + if (fdtSession != null) { try { final HashMap> lisaParams = new HashMap>(); final HashMap fdtSessionParams = new HashMap(); final FDTSessionMonitoringTask fdtSessionMTask = fdtSession.getMonitoringTask(); - if(fdtSessionMTask != null) { - if(fdtSession instanceof FDTWriterSession) { - final double rate = fdtSessionMTask.getTotalRate()/Utils.MEGA_BYTE; + if (fdtSessionMTask != null) { + if (fdtSession instanceof FDTWriterSession) { + final double rate = fdtSessionMTask.getTotalRate() / Utils.MEGA_BYTE; fdtSessionParams.put("DISK_WRITE_MB", rate); - final double tSize = fdtSession.getSize()/(double)Utils.MEGA_BYTE; - final double cSize = fdtSession.getTotalBytes()/(double)Utils.MEGA_BYTE; + final double tSize = fdtSession.getSize() / (double) Utils.MEGA_BYTE; + final double cSize = fdtSession.getTotalBytes() / (double) Utils.MEGA_BYTE; fdtSessionParams.put("TotalMBytes", tSize); fdtSessionParams.put("TransferredMBytes", cSize); - fdtSessionParams.put("Status", (double)fdtSession.getCurrentStatus()); + fdtSessionParams.put("Status", (double) fdtSession.getCurrentStatus()); - if(fdtSession.getSize() != 0) { - fdtSessionParams.put("TransferRatio", (cSize*100)/tSize); + if (fdtSession.getSize() != 0) { + fdtSessionParams.put("TransferRatio", (cSize * 100) / tSize); } - final String monID = fdtSession.getMonID(); - if(monID != null) { + final String monID = fdtSession.getMonID(); + if (monID != null) { lisaParams.put(monID, fdtSessionParams); } else { - lisaParams.put(fdtSession.getRemoteAddress().getHostAddress()+":"+fdtSession.getRemotePort(), fdtSessionParams); + lisaParams.put(fdtSession.getRemoteAddress().getHostAddress() + ":" + fdtSession.getRemotePort(), fdtSessionParams); } - if(lisaParams.size() > 0) { - if(logger.isLoggable(Level.FINE)) { + if (lisaParams.size() > 0) { + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " Sending to LISA :- Client Params: " + lisaParams); } - for(Map.Entry> entry: lisaParams.entrySet()) { + for (Map.Entry> entry : lisaParams.entrySet()) { HashMap hToSend = entry.getValue(); - if(hToSend.size() > 0) { + if (hToSend.size() > 0) { lisaMon.sendServerParameters(entry.getKey(), hToSend); } } } - } else if(fdtSession instanceof FDTReaderSession){ - final double rate = fdtSessionMTask.getTotalRate()/Utils.MEGA_BYTE; + } else if (fdtSession instanceof FDTReaderSession) { + final double rate = fdtSessionMTask.getTotalRate() / Utils.MEGA_BYTE; fdtSessionParams.put("DISK_READ_MB", rate); - final double tSize = fdtSession.getSize()/(double)Utils.MEGA_BYTE; - final double cSize = fdtSession.getTotalBytes()/(double)Utils.MEGA_BYTE; + final double tSize = fdtSession.getSize() / (double) Utils.MEGA_BYTE; + final double cSize = fdtSession.getTotalBytes() / (double) Utils.MEGA_BYTE; fdtSessionParams.put("TotalMBytes", tSize); fdtSessionParams.put("TransferredMBytes", cSize); - if(fdtSession.getSize() != 0) { - fdtSessionParams.put("TransferRatio", (cSize*100)/tSize); + if (fdtSession.getSize() != 0) { + fdtSessionParams.put("TransferRatio", (cSize * 100) / tSize); } - fdtSessionParams.put("Status", (double)fdtSession.getCurrentStatus()); + fdtSessionParams.put("Status", (double) fdtSession.getCurrentStatus()); - final String monID = fdtSession.getMonID(); - if(monID != null) { + final String monID = fdtSession.getMonID(); + if (monID != null) { lisaParams.put(monID, fdtSessionParams); } else { - lisaParams.put(fdtSession.getRemoteAddress().getHostAddress()+":"+fdtSession.getRemotePort(), fdtSessionParams); + lisaParams.put(fdtSession.getRemoteAddress().getHostAddress() + ":" + fdtSession.getRemotePort(), fdtSessionParams); } - if(lisaParams.size() > 0) { - if(logger.isLoggable(Level.FINE)) { + if (lisaParams.size() > 0) { + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " Sending to LISA :- Server Params: " + lisaParams); } - for(Map.Entry> entry: lisaParams.entrySet()) { + for (Map.Entry> entry : lisaParams.entrySet()) { HashMap hToSend = entry.getValue(); - if(hToSend.size() > 0) { + if (hToSend.size() > 0) { lisaMon.sendClientParameters(entry.getKey(), hToSend); } } @@ -151,27 +146,27 @@ private void publishStartFinishParams(final FDTSession fdtSession) { logger.log(Level.WARNING, "[ERROR] FDTSessionMonitoringTask is null in finishFDTSession(fdtSession)!!!"); } - }catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, "Got expcetion notifying last params for " + fdtSession.sessionID(), t); } } else { logger.log(Level.WARNING, "[ERROR] FDT Session is null in finishFDTSession(fdtSession)!!!"); } } - + public void startFDTSession(final FDTSession fdtSession) { publishStartFinishParams(fdtSession); } - + public void finishFDTSession(final FDTSession fdtSession) { publishStartFinishParams(fdtSession); } - - private void setupMonClient(){ + + private void setupMonClient() { try { lisaMon = new MonClient(lisaHost, lisaPort); - }catch(Throwable t) { - if(!errorReported) { + } catch (Throwable t) { + if (!errorReported) { logger.log(Level.WARNING, " Cannot connect to lisa", t); } else { logger.log(Level.FINER, " Cannot connect to lisa", t); @@ -179,67 +174,67 @@ private void setupMonClient(){ errorReported = true; } } - + public void run() { try { - if(lisaMon == null) { - setupMonClient(); + if (lisaMon == null) { + setupMonClient(); } - - if(lisaMon == null) return; - + + if (lisaMon == null) return; + errorReported = false; - + HashMap> lisaParams = getReaderParams(); - - if(lisaParams.size() > 0) { - if(logger.isLoggable(Level.FINE)) { + + if (lisaParams.size() > 0) { + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " Sending to LISA :- Client Params: " + lisaParams); } - for(Map.Entry> entry: lisaParams.entrySet()) { + for (Map.Entry> entry : lisaParams.entrySet()) { HashMap hToSend = entry.getValue(); - if(hToSend.size() > 0) { + if (hToSend.size() > 0) { lisaMon.sendClientParameters(entry.getKey(), hToSend); } } } - + lisaParams = getWriterParams(); double totalNet = 0; double totalDisk = 0; - if(lisaParams.size() > 0) { - if(logger.isLoggable(Level.FINE)) { + if (lisaParams.size() > 0) { + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " Sending to LISA :- Server Params: " + lisaParams); } - for(Map.Entry> entry: lisaParams.entrySet()) { + for (Map.Entry> entry : lisaParams.entrySet()) { HashMap hToSend = entry.getValue(); - if(hToSend.size() > 0) { + if (hToSend.size() > 0) { Double dToAdd = hToSend.get("NET_IN_Mb"); - if(dToAdd != null) { + if (dToAdd != null) { totalNet += dToAdd; } - + dToAdd = hToSend.get("DISK_WRITE_MB"); - if(dToAdd != null) { + if (dToAdd != null) { totalDisk += dToAdd; } - + lisaMon.sendServerParameters(entry.getKey(), hToSend); } } } - - if(Config.getInstance().getHostName() == null) { + + if (Config.getInstance().getHostName() == null) { HashMap localParams = new HashMap(); - localParams.put("CLIENTS_NO", (double)lisaParams.size()); + localParams.put("CLIENTS_NO", (double) lisaParams.size()); localParams.put("DISK_WRITE_MB", totalDisk); localParams.put("NET_IN_Mb", totalNet); - + lisaMon.sendServerParameters("FDT_PARAMS", localParams); } - + // HashMap fdtLisaParams = FDTInternalMonitoringTask.getInstance().getLisaParams(); // // String key = "FDT_MON:"; @@ -271,12 +266,12 @@ public void run() { // } // // lisaMon.sendServerParameters(key, fdtLisaParams); - - }catch(Throwable t) { + + } catch (Throwable t) { logger.log(Level.INFO, " LISAReportingTask got exception:", t); } } - + public void sendClientNow(String key, HashMap params) throws Exception { lisaMon.sendClientParameters(key, params); } diff --git a/src/lia/util/net/copy/monitoring/lisa/LisaCtrlNotifier.java b/src/lia/util/net/copy/monitoring/lisa/LisaCtrlNotifier.java index 8367d76..c4f55e1 100644 --- a/src/lia/util/net/copy/monitoring/lisa/LisaCtrlNotifier.java +++ b/src/lia/util/net/copy/monitoring/lisa/LisaCtrlNotifier.java @@ -1,15 +1,15 @@ /* * $Id: $ - */ + */ package lia.util.net.copy.monitoring.lisa; /** * Receiver of remote commands from LISA/ML modules - * + * * @author ramiro */ public interface LisaCtrlNotifier { public void notifyLisaCtrlMsg(String lisaCtrlMsg); - + } diff --git a/src/lia/util/net/copy/monitoring/lisa/MacHostPropertiesMonitor.java b/src/lia/util/net/copy/monitoring/lisa/MacHostPropertiesMonitor.java index d69bc31..975b1ad 100755 --- a/src/lia/util/net/copy/monitoring/lisa/MacHostPropertiesMonitor.java +++ b/src/lia/util/net/copy/monitoring/lisa/MacHostPropertiesMonitor.java @@ -3,502 +3,472 @@ */ package lia.util.net.copy.monitoring.lisa; +import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; + import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; - /** - * * @author Gregory Denis * @author Ciprian Dobre */ public class MacHostPropertiesMonitor { - protected String[] networkInterfaces; - - protected String activeInterface; - - protected String cpuUsage = "0"; - - protected String cpuUSR = "0"; - - protected String cpuSYS = "0"; - - protected String cpuIDLE = "0"; - - protected String nbProcesses = "0"; - - protected String load1 = "0"; - - protected String load5 = "0"; - - protected String load15 = "0"; - - protected String memUsed = "0"; - - protected String memFree = "0"; - - protected String memUsage = "0"; - - protected String netIn = "0"; - - protected String netOut = "0"; - - protected String pagesIn = "0"; - - protected String pagesOut = "0"; - - protected String macAddress = "unknown"; - - protected String diskIO = "0"; - - protected String diskIn = "0"; - - protected String diskOut = "0"; - - protected String diskFree = "0"; - - protected String diskUsed = "0"; - - protected String diskTotal = "0"; - - protected String command = ""; - - protected static final Object lock = new Object(); - - protected static int ptnr = 0; - - protected cmdExec execute = null; - - protected String sep = null; - - protected final Logger logger; - - public MacHostPropertiesMonitor(final Logger logger) { - - this.logger = logger; - - execute = cmdExec.getInstance(); - sep = System.getProperty("file.separator"); - // get the network interfaces up - command = sep + "sbin" + sep + "ifconfig -l -u"; - CommandResult cmdRes = execute.executeCommand(command, "lo0", 3 * 1000L); - String result = cmdRes.getOutput(); - // System.out.println(command + " = "+ result); - if (result == null || result.equals("")) { - logger.warning(command + ": No result???"); - } else { - int where = result.indexOf("lo0"); - networkInterfaces = result.substring(where + 3, result.length()).replaceAll(" ", " ").trim().split(" "); - - // get the currently used Mac Address - for (int i = 0; i < networkInterfaces.length; i++) { - String current = networkInterfaces[i]; - command = sep + "sbin" + sep + "ifconfig " + current; - cmdRes = execute.executeCommand(command, current, 3 * 1000L); - result = cmdRes.getOutput(); - // System.out.println(command + " = " + result); - if (result == null || result.equals("")) { - logger.warning(command + ": No result???"); - } else { - if (result.indexOf("inet ") != -1) { - int pointI = result.indexOf("ether"); - int pointJ = result.indexOf("media", pointI); - macAddress = result.substring(pointI + 5, pointJ).trim(); - // System.out.println("Mac Address:" + macAddress); - activeInterface = current; - } - } - } - } - - // get the disk information - command = sep + "bin" + sep + "df -k -h " + sep; - cmdRes = execute.executeCommand(command, "/dev", 3 * 1000L); - result = cmdRes.getOutput(); - // System.out.println(command + " = "+ result); - if (result == null || result.equals("")) { - logger.warning(command + ": No result???"); - } else { - parseDf(result); - } - - update(); - } - - public String getMacAddresses() { - return macAddress; - } - - public void update() { - - if (execute == null) { - execute = cmdExec.getInstance(); - } - - // get CPU, load, Mem, Pages, Processes from '/usr/bin/top' - command = sep + "usr" + sep + "bin" + sep + "top -d -l2 -n1 -F -R -X"; - CommandResult cmdRes = execute.executeCommand(command, "PID", 2, 100 * 1000L); - String result = cmdRes.getOutput(); - // System.out.println(command + " = "+ result); - if (result == null || result.equals("")) { - logger.warning("No result???"); - } else { - parseTop(result); - } - } - - // private void parseIfConfig(String toParse) { - // - // //System.out.println("result of ifconfig:" + toParse); - // if (toParse.indexOf("inet ") != -1) { - // int pointI = toParse.indexOf("ether"); - // int pointJ = toParse.indexOf("media", pointI); - // macAddress = toParse.substring(pointJ + 5, pointI).trim(); - // System.out.println("Mac Address:" + macAddress); - // } - // } - - private void parseDf(String toParse) { - - // System.out.println("result of df -k -h /:" + toParse); - - int pointI = toParse.indexOf("/dev/"); - int pointJ = 0; - int pointK = 0; - - // Get the size of the root disk - try { - pointJ = toParse.indexOf(" ", pointI); - pointK = indexOfUnitLetter(toParse, pointJ); - diskTotal = toParse.substring(pointJ, pointK).trim(); - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - - // Get the capacity used - try { - pointI = toParse.indexOf(" ", pointK); - pointJ = indexOfUnitLetter(toParse, pointI); - diskUsed = toParse.substring(pointI, pointJ).trim(); - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - - // Get the free space - try { - pointK = toParse.indexOf(" ", pointJ); - pointI = indexOfUnitLetter(toParse, pointK); - diskFree = toParse.substring(pointK, pointI).trim(); - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; + protected static final Object lock = new Object(); + protected static int ptnr = 0; + protected final Logger logger; + protected String[] networkInterfaces; + protected String activeInterface; + protected String cpuUsage = "0"; + protected String cpuUSR = "0"; + protected String cpuSYS = "0"; + protected String cpuIDLE = "0"; + protected String nbProcesses = "0"; + protected String load1 = "0"; + protected String load5 = "0"; + protected String load15 = "0"; + protected String memUsed = "0"; + protected String memFree = "0"; + protected String memUsage = "0"; + protected String netIn = "0"; + protected String netOut = "0"; + protected String pagesIn = "0"; + protected String pagesOut = "0"; + protected String macAddress = "unknown"; + protected String diskIO = "0"; + protected String diskIn = "0"; + protected String diskOut = "0"; + protected String diskFree = "0"; + protected String diskUsed = "0"; + protected String diskTotal = "0"; + protected String command = ""; + protected cmdExec execute = null; + protected String sep = null; + + public MacHostPropertiesMonitor(final Logger logger) { + + this.logger = logger; + + execute = cmdExec.getInstance(); + sep = System.getProperty("file.separator"); + // get the network interfaces up + command = sep + "sbin" + sep + "ifconfig -l -u"; + CommandResult cmdRes = execute.executeCommand(command, "lo0", 3 * 1000L); + String result = cmdRes.getOutput(); + // System.out.println(command + " = "+ result); + if (result == null || result.equals("")) { + logger.warning(command + ": No result???"); + } else { + int where = result.indexOf("lo0"); + networkInterfaces = result.substring(where + 3, result.length()).replaceAll(" ", " ").trim().split(" "); + + // get the currently used Mac Address + for (int i = 0; i < networkInterfaces.length; i++) { + String current = networkInterfaces[i]; + command = sep + "sbin" + sep + "ifconfig " + current; + cmdRes = execute.executeCommand(command, current, 3 * 1000L); + result = cmdRes.getOutput(); + // System.out.println(command + " = " + result); + if (result == null || result.equals("")) { + logger.warning(command + ": No result???"); + } else { + if (result.indexOf("inet ") != -1) { + int pointI = result.indexOf("ether"); + int pointJ = result.indexOf("media", pointI); + macAddress = result.substring(pointI + 5, pointJ).trim(); + // System.out.println("Mac Address:" + macAddress); + activeInterface = current; + } + } + } + } + + // get the disk information + command = sep + "bin" + sep + "df -k -h " + sep; + cmdRes = execute.executeCommand(command, "/dev", 3 * 1000L); + result = cmdRes.getOutput(); + // System.out.println(command + " = "+ result); + if (result == null || result.equals("")) { + logger.warning(command + ": No result???"); + } else { + parseDf(result); + } + + update(); + } + + public String getMacAddresses() { + return macAddress; + } + + public void update() { + + if (execute == null) { + execute = cmdExec.getInstance(); + } + + // get CPU, load, Mem, Pages, Processes from '/usr/bin/top' + command = sep + "usr" + sep + "bin" + sep + "top -d -l2 -n1 -F -R -X"; + CommandResult cmdRes = execute.executeCommand(command, "PID", 2, 100 * 1000L); + String result = cmdRes.getOutput(); + // System.out.println(command + " = "+ result); + if (result == null || result.equals("")) { + logger.warning("No result???"); + } else { + parseTop(result); + } + } + + // private void parseIfConfig(String toParse) { + // + // //System.out.println("result of ifconfig:" + toParse); + // if (toParse.indexOf("inet ") != -1) { + // int pointI = toParse.indexOf("ether"); + // int pointJ = toParse.indexOf("media", pointI); + // macAddress = toParse.substring(pointJ + 5, pointI).trim(); + // System.out.println("Mac Address:" + macAddress); + // } + // } + + private void parseDf(String toParse) { + + // System.out.println("result of df -k -h /:" + toParse); + + int pointI = toParse.indexOf("/dev/"); + int pointJ = 0; + int pointK = 0; + + // Get the size of the root disk + try { + pointJ = toParse.indexOf(" ", pointI); + pointK = indexOfUnitLetter(toParse, pointJ); + diskTotal = toParse.substring(pointJ, pointK).trim(); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get the capacity used + try { + pointI = toParse.indexOf(" ", pointK); + pointJ = indexOfUnitLetter(toParse, pointI); + diskUsed = toParse.substring(pointI, pointJ).trim(); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get the free space + try { + pointK = toParse.indexOf(" ", pointJ); + pointI = indexOfUnitLetter(toParse, pointK); + diskFree = toParse.substring(pointK, pointI).trim(); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; /* - * System.out.println( "Disk: Total:" + diskTotal + " Used:" + diskUsed + " Free:" + diskFree); + * System.out.println( "Disk: Total:" + diskTotal + " Used:" + diskUsed + " Free:" + diskFree); */ - } - - private int indexOfUnitLetter(String inside, int from) { - - int temp = inside.indexOf('K', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('M', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('G', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('B', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('T', from); - if (temp == -1 || (temp - from > 10)) { - temp = inside.indexOf('b', from); - if (temp - from > 10) - temp = -1; - } - } - } - } - } - return temp; - } - - private int lastIndexOfUnitLetter(String inside, int from) { - - int temp = inside.lastIndexOf('K', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('M', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('G', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('B', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('T', from); - if (temp == -1 || (from - temp > 10)) { - temp = inside.lastIndexOf('b', from); - if (from - temp > 10) - temp = -1; - } - } - } - } - } - return temp; - } - - // private double howMuchKiloBytes(char a) { - // - // switch (a) { - // case 'T' : - // return 1073741824.0; - // case 'G' : - // return 1048576.0; - // case 'M' : - // return 1024.0; - // case 'K' : - // return 1.0; - // case 'B' : - // return 0.0009765625; - // default : - // return 1.0; - // } - // } - - private double howMuchMegaBytes(char a) { - - switch (a) { - case 'T': - return 1048576.0; - case 'G': - return 1024.0; - case 'M': - return 1.0; - case 'K': - return 0.0009765625; - case 'B': - return 0.0000009537; - default: - return 1.0; - } - } - - private void parseTop(String toParse) { - - // System.out.println("\n******\n"+toParse+"\n********\n"); - - int pointA = 0; - int pointB = 0; - int unitPos = 0; - double sum = 0.0; - - // Get number of total Processes - try { - pointA = toParse.indexOf("Procs:"); - // System.out.println("First Procs at " + pointA); - pointA = toParse.indexOf("Procs:", pointA + 6) + 6; - // System.out.println("Second Procs at " + pointA); - pointB = toParse.indexOf(",", pointA + 1); - nbProcesses = toParse.substring(pointA, pointB).trim(); - // System.out.println(nbProcesses + " processes"); - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - - // Get the loads... - try { - pointA = toParse.indexOf("LoadAvg:", pointA); - pointA += 9; - pointB = toParse.indexOf(",", pointA); - load1 = toParse.substring(pointA, pointB).trim(); - pointA = toParse.indexOf(",", pointB + 1); - load5 = toParse.substring(pointB + 1, pointA).trim(); - pointB = toParse.indexOf("CPU:", pointA + 1); - pointB = toParse.lastIndexOf(".", pointB); - load15 = toParse.substring(pointA + 1, pointB).trim(); - // System.out.println("load: [" + load1 + "][" + load5 + "][" + load15 + "]"); - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - - // Get CPUs... - try { - pointB = toParse.indexOf("CPU:", pointB + 1) + 4; - pointA = toParse.indexOf("% user", pointB); - cpuUSR = toParse.substring(pointB, pointA).trim(); - pointA = toParse.indexOf(",", pointA); - pointB = toParse.indexOf("% sys", pointA + 1); - cpuSYS = toParse.substring(pointA + 1, pointB).trim(); - pointA = toParse.indexOf(",", pointB); - pointB = toParse.indexOf("% idle", pointA + 1); - cpuIDLE = toParse.substring(pointA + 1, pointB).trim(); - sum = 100.0 - Double.parseDouble(cpuIDLE); - cpuUsage = String.valueOf(sum); + } + + private int indexOfUnitLetter(String inside, int from) { + + int temp = inside.indexOf('K', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('M', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('G', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('B', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('T', from); + if (temp == -1 || (temp - from > 10)) { + temp = inside.indexOf('b', from); + if (temp - from > 10) + temp = -1; + } + } + } + } + } + return temp; + } + + private int lastIndexOfUnitLetter(String inside, int from) { + + int temp = inside.lastIndexOf('K', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('M', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('G', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('B', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('T', from); + if (temp == -1 || (from - temp > 10)) { + temp = inside.lastIndexOf('b', from); + if (from - temp > 10) + temp = -1; + } + } + } + } + } + return temp; + } + + // private double howMuchKiloBytes(char a) { + // + // switch (a) { + // case 'T' : + // return 1073741824.0; + // case 'G' : + // return 1048576.0; + // case 'M' : + // return 1024.0; + // case 'K' : + // return 1.0; + // case 'B' : + // return 0.0009765625; + // default : + // return 1.0; + // } + // } + + private double howMuchMegaBytes(char a) { + + switch (a) { + case 'T': + return 1048576.0; + case 'G': + return 1024.0; + case 'M': + return 1.0; + case 'K': + return 0.0009765625; + case 'B': + return 0.0000009537; + default: + return 1.0; + } + } + + private void parseTop(String toParse) { + + // System.out.println("\n******\n"+toParse+"\n********\n"); + + int pointA = 0; + int pointB = 0; + int unitPos = 0; + double sum = 0.0; + + // Get number of total Processes + try { + pointA = toParse.indexOf("Procs:"); + // System.out.println("First Procs at " + pointA); + pointA = toParse.indexOf("Procs:", pointA + 6) + 6; + // System.out.println("Second Procs at " + pointA); + pointB = toParse.indexOf(",", pointA + 1); + nbProcesses = toParse.substring(pointA, pointB).trim(); + // System.out.println(nbProcesses + " processes"); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get the loads... + try { + pointA = toParse.indexOf("LoadAvg:", pointA); + pointA += 9; + pointB = toParse.indexOf(",", pointA); + load1 = toParse.substring(pointA, pointB).trim(); + pointA = toParse.indexOf(",", pointB + 1); + load5 = toParse.substring(pointB + 1, pointA).trim(); + pointB = toParse.indexOf("CPU:", pointA + 1); + pointB = toParse.lastIndexOf(".", pointB); + load15 = toParse.substring(pointA + 1, pointB).trim(); + // System.out.println("load: [" + load1 + "][" + load5 + "][" + load15 + "]"); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get CPUs... + try { + pointB = toParse.indexOf("CPU:", pointB + 1) + 4; + pointA = toParse.indexOf("% user", pointB); + cpuUSR = toParse.substring(pointB, pointA).trim(); + pointA = toParse.indexOf(",", pointA); + pointB = toParse.indexOf("% sys", pointA + 1); + cpuSYS = toParse.substring(pointA + 1, pointB).trim(); + pointA = toParse.indexOf(",", pointB); + pointB = toParse.indexOf("% idle", pointA + 1); + cpuIDLE = toParse.substring(pointA + 1, pointB).trim(); + sum = 100.0 - Double.parseDouble(cpuIDLE); + cpuUsage = String.valueOf(sum); /* * System.out.println("Cpu Usage:" + cpuUsage + " user:" + cpuUSR + " sys:" + cpuSYS + " idle:" + cpuIDLE); */ - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - - // Get Mem... - try { - pointA = toParse.indexOf("PhysMem", pointB); - pointA += 8; - pointB = toParse.indexOf("M used", pointA); - pointA = toParse.lastIndexOf(",", pointB); - memUsed = toParse.substring(pointA + 1, pointB).trim(); - pointB = toParse.indexOf("M free", pointB); - pointA = toParse.lastIndexOf(",", pointB); - memFree = toParse.substring(pointA + 1, pointB).trim(); - // System.out.println("Mem Used:"+memUsed+"M Free:"+memFree+"M"); - sum = Double.parseDouble(memUsed) + Double.parseDouble(memFree); - double percentage = Integer.parseInt(memUsed) / sum * 100; - memUsage = String.valueOf(percentage); - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - - // Pages In/Out... - try { - pointA = toParse.indexOf("VirtMem:", pointB + 6); - pointB = toParse.indexOf("pagein", pointA); - pointA = toParse.lastIndexOf(",", pointB); - pagesIn = toParse.substring(pointA + 1, pointB).trim(); - pointA = toParse.indexOf("pageout", pointB); - pointB = toParse.lastIndexOf(",", pointA); - pagesOut = toParse.substring(pointB + 1, pointA).trim(); - // System.out.println("Pages In:" + pagesIn + " Out" + pagesOut); - } catch (java.lang.StringIndexOutOfBoundsException e) { - logger.warning("Can't find pages in :" + toParse); - } - ; - - // Get Network IO... - try { - pointA = toParse.indexOf("Networks:", pointB) + 9; - pointB = toParse.indexOf("data =", pointA) + 6; - pointA = toParse.indexOf("in", pointB); - unitPos = lastIndexOfUnitLetter(toParse, pointA); - netIn = toParse.substring(pointB, unitPos).trim(); - // System.out.print("Net In:" + netIn); - double factor = howMuchMegaBytes((toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); - netIn = String.valueOf(Double.parseDouble(netIn) * factor * 4); - pointB = toParse.indexOf("out", pointA); - unitPos = lastIndexOfUnitLetter(toParse, pointB); - factor = howMuchMegaBytes((toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); - netOut = toParse.substring(pointA + 3, unitPos).trim(); - // System.out.println("Net Out:" + netOut); - netOut = String.valueOf(Double.parseDouble(netOut) * factor); - // System.out.println("Network In:" + netIn + " OUT:" + netOut); - } catch (java.lang.StringIndexOutOfBoundsException e) { - logger.log(Level.INFO, "Got exception", e); - } - ; - - // Get Disks IO... - try { - pointB = toParse.indexOf("Disks:", pointA) + 6; - pointA = toParse.indexOf("data =", pointB) + 6; - pointB = toParse.indexOf("in,", pointA); - unitPos = lastIndexOfUnitLetter(toParse, pointB); - diskIn = toParse.substring(pointA, unitPos).trim(); - pointA = toParse.indexOf("out", pointB); - unitPos = lastIndexOfUnitLetter(toParse, pointA); - diskOut = toParse.substring(pointB + 3, unitPos).trim(); - - // System.out.println("diskIO In:" + diskIn + " Out:" + diskOut); - diskIO = diskOut; - } catch (java.lang.StringIndexOutOfBoundsException e) { - } - ; - } - - public String getCpuUsage() { - return cpuUsage; - } - - public String getCpuUSR() { - return cpuUSR; - } - - public String getCpuSYS() { - return cpuSYS; - } - - public String getCpuNICE() { - return "0"; - } - - public String getCpuIDLE() { - return cpuIDLE; - } - - public String getPagesIn() { - return pagesIn; - } - - public String getPagesOut() { - return pagesOut; - } - - public String getMemUsage() { - return memUsage; - } - - public String getMemUsed() { - return memUsed; - } - - public String getMemFree() { - return memFree; - } - - public String getDiskIO() { - return diskIO; - } - - public String getDiskTotal() { - return diskTotal; - } - - public String getDiskUsed() { - return diskUsed; - } - - public String getDiskFree() { - return diskFree; - } - - public String getNoProcesses() { - return nbProcesses; - } - - public String getLoad1() { - return load1; - } - - public String getLoad5() { - return load5; - } - - public String getLoad15() { - return load15; - } - - public String[] getNetInterfaces() { - return networkInterfaces; - } - - public String getNetIn(String ifName) { - if (ifName.equalsIgnoreCase(activeInterface)) - return netIn; - else - return "0"; - } - - public String getNetOut(String ifName) { - if (ifName.equalsIgnoreCase(activeInterface)) - return netOut; - return "0"; - } + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Get Mem... + try { + pointA = toParse.indexOf("PhysMem", pointB); + pointA += 8; + pointB = toParse.indexOf("M used", pointA); + pointA = toParse.lastIndexOf(",", pointB); + memUsed = toParse.substring(pointA + 1, pointB).trim(); + pointB = toParse.indexOf("M free", pointB); + pointA = toParse.lastIndexOf(",", pointB); + memFree = toParse.substring(pointA + 1, pointB).trim(); + // System.out.println("Mem Used:"+memUsed+"M Free:"+memFree+"M"); + sum = Double.parseDouble(memUsed) + Double.parseDouble(memFree); + double percentage = Integer.parseInt(memUsed) / sum * 100; + memUsage = String.valueOf(percentage); + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + + // Pages In/Out... + try { + pointA = toParse.indexOf("VirtMem:", pointB + 6); + pointB = toParse.indexOf("pagein", pointA); + pointA = toParse.lastIndexOf(",", pointB); + pagesIn = toParse.substring(pointA + 1, pointB).trim(); + pointA = toParse.indexOf("pageout", pointB); + pointB = toParse.lastIndexOf(",", pointA); + pagesOut = toParse.substring(pointB + 1, pointA).trim(); + // System.out.println("Pages In:" + pagesIn + " Out" + pagesOut); + } catch (java.lang.StringIndexOutOfBoundsException e) { + logger.warning("Can't find pages in :" + toParse); + } + ; + + // Get Network IO... + try { + pointA = toParse.indexOf("Networks:", pointB) + 9; + pointB = toParse.indexOf("data =", pointA) + 6; + pointA = toParse.indexOf("in", pointB); + unitPos = lastIndexOfUnitLetter(toParse, pointA); + netIn = toParse.substring(pointB, unitPos).trim(); + // System.out.print("Net In:" + netIn); + double factor = howMuchMegaBytes((toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); + netIn = String.valueOf(Double.parseDouble(netIn) * factor * 4); + pointB = toParse.indexOf("out", pointA); + unitPos = lastIndexOfUnitLetter(toParse, pointB); + factor = howMuchMegaBytes((toParse.substring(unitPos, unitPos + 1).toCharArray())[0]); + netOut = toParse.substring(pointA + 3, unitPos).trim(); + // System.out.println("Net Out:" + netOut); + netOut = String.valueOf(Double.parseDouble(netOut) * factor); + // System.out.println("Network In:" + netIn + " OUT:" + netOut); + } catch (java.lang.StringIndexOutOfBoundsException e) { + logger.log(Level.INFO, "Got exception", e); + } + ; + + // Get Disks IO... + try { + pointB = toParse.indexOf("Disks:", pointA) + 6; + pointA = toParse.indexOf("data =", pointB) + 6; + pointB = toParse.indexOf("in,", pointA); + unitPos = lastIndexOfUnitLetter(toParse, pointB); + diskIn = toParse.substring(pointA, unitPos).trim(); + pointA = toParse.indexOf("out", pointB); + unitPos = lastIndexOfUnitLetter(toParse, pointA); + diskOut = toParse.substring(pointB + 3, unitPos).trim(); + + // System.out.println("diskIO In:" + diskIn + " Out:" + diskOut); + diskIO = diskOut; + } catch (java.lang.StringIndexOutOfBoundsException e) { + } + ; + } + + public String getCpuUsage() { + return cpuUsage; + } + + public String getCpuUSR() { + return cpuUSR; + } + + public String getCpuSYS() { + return cpuSYS; + } + + public String getCpuNICE() { + return "0"; + } + + public String getCpuIDLE() { + return cpuIDLE; + } + + public String getPagesIn() { + return pagesIn; + } + + public String getPagesOut() { + return pagesOut; + } + + public String getMemUsage() { + return memUsage; + } + + public String getMemUsed() { + return memUsed; + } + + public String getMemFree() { + return memFree; + } + + public String getDiskIO() { + return diskIO; + } + + public String getDiskTotal() { + return diskTotal; + } + + public String getDiskUsed() { + return diskUsed; + } + + public String getDiskFree() { + return diskFree; + } + + public String getNoProcesses() { + return nbProcesses; + } + + public String getLoad1() { + return load1; + } + + public String getLoad5() { + return load5; + } + + public String getLoad15() { + return load15; + } + + public String[] getNetInterfaces() { + return networkInterfaces; + } + + public String getNetIn(String ifName) { + if (ifName.equalsIgnoreCase(activeInterface)) + return netIn; + else + return "0"; + } + + public String getNetOut(String ifName) { + if (ifName.equalsIgnoreCase(activeInterface)) + return netOut; + return "0"; + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/MonClient.java b/src/lia/util/net/copy/monitoring/lisa/MonClient.java index 3cb11e4..34ed93a 100644 --- a/src/lia/util/net/copy/monitoring/lisa/MonClient.java +++ b/src/lia/util/net/copy/monitoring/lisa/MonClient.java @@ -3,14 +3,13 @@ */ package lia.util.net.copy.monitoring.lisa; +import lia.util.net.copy.monitoring.lisa.xdr.XDRClient; + import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.monitoring.lisa.xdr.XDRClient; - /** - * * @author Adrian Muraru */ public class MonClient { @@ -21,10 +20,9 @@ public class MonClient { private int lisaPort; /** - * @param lisaHost: - * lisa host - * @param lisaPort : - * lisa XDR port + * @param lisaHost: lisa host + * @param lisaPort : + * lisa XDR port */ public MonClient(String lisaHost, int lisaPort) throws Exception { this.lisaHost = lisaHost; @@ -34,11 +32,9 @@ public MonClient(String lisaHost, int lisaPort) throws Exception { /** * Sends information related to FDT Client - * - * @param id: - * transfer identifier (in ML this will be the Node under which the values are reported) - * @param parameters: - * paramters map + * + * @param id: transfer identifier (in ML this will be the Node under which the values are reported) + * @param parameters: paramters map * @throws Exception */ public void sendClientParameters(String id, Map parameters) throws Exception { @@ -62,11 +58,9 @@ public void sendClientParameters(String id, Map parameters) thro /** * Sends information related to FDT Server - * - * @param id: - * transfer identifier (in ML this will be the Node under which the values are reported) - * @param parameters: - * paramters map + * + * @param id: transfer identifier (in ML this will be the Node under which the values are reported) + * @param parameters: paramters map * @throws Exception */ public void sendServerParameters(String id, Map parameters) throws Exception { @@ -99,7 +93,7 @@ public String sendDirectCommand(final String cmd) throws Exception { if (lisaClient != null) { return lisaClient.sendCommand(cmd); } - + throw new Exception("Unable to connect to LISA / ML modules"); } } diff --git a/src/lia/util/net/copy/monitoring/lisa/ProcReader.java b/src/lia/util/net/copy/monitoring/lisa/ProcReader.java index e711b35..ef82b2c 100644 --- a/src/lia/util/net/copy/monitoring/lisa/ProcReader.java +++ b/src/lia/util/net/copy/monitoring/lisa/ProcReader.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy.monitoring.lisa; +import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; + import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -11,27 +13,21 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.StringTokenizer; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; - /** * Utility class that monitors the internals of the Linux box... + * * @author Ciprian Dobre */ public class ProcReader { - /** - * PARAMETER UNITS - */ + final static NumberFormat nf = NumberFormat.getInstance(); + /** + * PARAMETER UNITS + */ // ( PARAM, UNIT): // ("CpuUsage", "%") @@ -62,312 +58,343 @@ public class ProcReader { // ("Load15", "") // ("In_"+ifName, "Mbps") // ("Out_"+ifName, "Mbps") - - private static final String SYS_EXTENDED_BIN_PATH = "/bin,/sbin,/usr/bin,/usr/sbin,/usr/local/bin"; - - protected final Logger logger; - private final cmdExec exec; - - private boolean hasCommonCPUStats; - private boolean hasCPUIOWaitStats; - private boolean hasCPUIntStats; - private boolean hasCPUStealStats; - private boolean hasPageProcStat; - private boolean hasSwapProcStat; - private boolean hasPageProcVmStat; - private boolean hasSwapProcVmStat; - /** is this a 64 bits arch ? */ - private boolean is64BitArch = false; - private boolean alreadyInitCPU = false; - - private String diskIO = null; - private String tps = null; - private final LinkedList devices = new LinkedList(); - private final HashMap diskIOReadKB = new HashMap(); - private final HashMap diskIOWriteKB = new HashMap(); - private final HashMap dKBRead = new HashMap(); - private final HashMap dKBWrite = new HashMap(); - private double diskTotal; - private double diskUsed; - private double diskFree; - private double memUsage; - private double memUsed; - private double memFree; - private double load1, load5, load15; - private final Hashtable netIn = new Hashtable(); - private final Hashtable dnetIn = new Hashtable(); - private final Hashtable netOut = new Hashtable(); - private final Hashtable dnetOut = new Hashtable(); - - static public String[] ResTypes; - protected String PROC_FILE_NAMES[]; - private String[] netInterfaces = null; - - protected FileReader fileReaders[]; - protected BufferedReader bufferedReaders[]; - - String[] old; // = new long[8]; - String[] cur; - String[] xtmp; - String[] diff; - - long last_time_diskIO; - long last_time_cpu; - long last_time_net; - - final static NumberFormat nf = NumberFormat.getInstance(); - - static { - nf.setMaximumFractionDigits(4); - nf.setMinimumFractionDigits(4); - } - - public ProcReader(final Logger logger) { - this.logger = logger; - exec = cmdExec.getInstance(); - } - - /** - * Method that returns the MAC hw address. - * @return The hw address or null - * @since java1.6 - */ - public final String getMACAddress() { - try { - Enumeration en = NetworkInterface.getNetworkInterfaces(); - while (en.hasMoreElements()) { - NetworkInterface ni = en.nextElement(); - // get the hardware address - if (!ni.isLoopback()) { - try { - final String hadr = getHexa(ni.getHardwareAddress()); - if (hadr != null) return hadr; - } catch (SocketException se) { } // nothing, just move on here - } - Enumeration en1 = ni.getSubInterfaces(); - while (en1.hasMoreElements()) { - ni = en1.nextElement(); - // get the hardware address - if (!ni.isLoopback()) { - try { - final String hadr = getHexa(ni.getHardwareAddress()); - if (hadr != null) return hadr; - } catch (SocketException se) { } // nothing, just move on here - } - } - } - } catch (Throwable se) { - logger.log(Level.INFO, "Got error retrieving the network interfaces", se); - } - // if we did not succeded in this way, then try using ifconfig instead. - - final String path = getIfConfigPath(); - CommandResult cmdRes = exec.executeCommandReality("ifconfig -a", "b", path); - String output = cmdRes.getOutput(); - if (cmdRes.failed()) - output = null; - if (output != null && output.length() != 0 && !output.contains("No such file or directory") && !output.contains("Segmentation fault")) { - StringTokenizer st = new StringTokenizer(output); - String line = st.nextToken("\n"); - while (line != null) { - if (line != null && (line.startsWith(" ") || line.startsWith("\t"))) { - line = st.nextToken("\n"); - continue; - } - if (line == null) - break; - StringTokenizer ast = new StringTokenizer(line); - // get the name - ast.nextToken(" \t\n"); // netName - // get the hw address - if (line.indexOf("HWaddr ") >= 0) return line.substring(line.indexOf("HWaddr ")+7); - line = st.nextToken("\n"); - } - } - return null; - } - - /** - * Method to retrieve CPU related parameters.. - * @return A hashmap, where String is the name of the param and Double is the current value - */ - public final HashMap getCPUVM() throws Exception { - initCPUReaders(); - - if (cur == null) - cur = new String[ResTypes.length]; - if (xtmp == null) - xtmp = new String[ResTypes.length]; - if (diff == null) - diff = new String[ResTypes.length]; - - - if (hasSwapProcVmStat || hasPageProcVmStat) { - PROC_FILE_NAMES = new String[] { "/proc/stat", "/proc/vmstat" }; - } else if (hasCommonCPUStats || hasSwapProcStat || hasPageProcStat) { - PROC_FILE_NAMES = new String[] { "/proc/stat" }; - } else - PROC_FILE_NAMES = null; - createReaders(); - - HashMap results = new HashMap(); - - long sTime = System.currentTimeMillis(); - int len = ResTypes.length; - BufferedReader br = bufferedReaders[0]; - int index = 0; - boolean parsedCPU = !hasCommonCPUStats; - - for (;;) { - String lin = br.readLine(); - - if (lin == null) - break; - - lin = lin.trim(); - if (lin.length() == 0) - continue; - - StringTokenizer tz = new StringTokenizer(lin); - - String item = tz.nextToken().trim(); - if (!parsedCPU && hasCommonCPUStats && item.equals("cpu")) { - parsedCPU = true; - cur[index++] = tz.nextToken(); - cur[index++] = tz.nextToken(); - cur[index++] = tz.nextToken(); - cur[index++] = tz.nextToken(); - - if (hasCPUIOWaitStats) { - cur[index++] = tz.nextToken(); - if (hasCPUIntStats) { - cur[index++] = tz.nextToken(); - cur[index++] = tz.nextToken(); - if (hasCPUStealStats) { - cur[index++] = tz.nextToken(); - } - } - } - if (hasPageProcVmStat && hasSwapProcVmStat) - break; - } else if (item.equals("page")) { - cur[index++] = tz.nextToken(); - cur[index++] = tz.nextToken(); - } else if (item.equals("swap")) { - cur[index++] = tz.nextToken(); - cur[index++] = tz.nextToken(); - } - } - - if (hasSwapProcVmStat || hasPageProcVmStat) { - br = bufferedReaders[1]; - int cCount = 0; - String lin = br.readLine(); - for (; cCount < 4 && lin != null; lin = br.readLine()) { - lin = lin.trim(); - if (lin.startsWith("pgpgin")) { - cur[len - 4] = lin.substring(7); - cCount++; - continue; - } - - if (lin.startsWith("pgpgout")) { - cur[len - 3] = lin.substring(8); - cCount++; - continue; - } - - if (lin.startsWith("pswpin")) { - cur[len - 2] = lin.substring(7); - cCount++; - continue; - } - - if (lin.startsWith("pswpout")) { - cur[len - 1] = lin.substring(8); - cCount++; - continue; - } - } - } - if (old == null) { - old = cur; - cur = xtmp; - last_time_cpu = System.currentTimeMillis(); - } else { - for (int i = 0; i < diff.length; i++) { - diff[i] = diffWithOverflowCheck(cur[i], old[i]); - } - - String sum = addWithOverflowCheck(diff[0], diff[1]); - sum = addWithOverflowCheck(sum, diff[2]); - sum = addWithOverflowCheck(sum, diff[3]); - if (hasCPUIOWaitStats) { - sum = addWithOverflowCheck(sum, diff[4]); - if (hasCPUIntStats) { - sum = addWithOverflowCheck(sum, diff[5]); - sum = addWithOverflowCheck(sum, diff[6]); - if (hasCPUStealStats) { - sum = addWithOverflowCheck(sum, diff[7]); - } - } - } - index = 0; - - String str1 = divideWithOverflowCheck(diff[0], sum); - double d = Double.parseDouble(str1) * 100D; - double totalP = d; - results.put(ResTypes[index++], d); - str1 = divideWithOverflowCheck(diff[1], sum); - d = Double.parseDouble(str1) * 100D; - totalP += d; - results.put(ResTypes[index++], d); - str1 = divideWithOverflowCheck(diff[2], sum); - d = Double.parseDouble(str1) * 100D; - totalP += d; - results.put(ResTypes[index++], d); - - if (hasCPUIOWaitStats) { - str1 = divideWithOverflowCheck(diff[4], sum); - d = Double.parseDouble(str1) * 100D; - totalP += d; - results.put(ResTypes[index++], d); - if (hasCPUIntStats) { - str1 = divideWithOverflowCheck(diff[5], sum); - d = Double.parseDouble(str1) * 100D; - totalP += d; - results.put(ResTypes[index++], d); - str1 = divideWithOverflowCheck(diff[6], sum); - d = Double.parseDouble(str1) * 100D; - totalP += d; - results.put(ResTypes[index++], d); - if (hasCPUStealStats) { - str1 = divideWithOverflowCheck(diff[7], sum); - d = Double.parseDouble(str1) * 100D; - totalP += d; - results.put(ResTypes[index++], d); - } - } - } - - str1 = divideWithOverflowCheck(diff[3], sum); - d = Double.parseDouble(str1) * 100D; - results.put(ResTypes[index++], d); - if (Math.abs(totalP + d - 0D) < 0.0001) - results.put("CPU_Usage", 1.0); - else - results.put("CPU_usage", (totalP / (totalP + d))); - double imp = ((sTime - last_time_cpu) / 1000D) * 1024D; + + private static final String SYS_EXTENDED_BIN_PATH = "/bin,/sbin,/usr/bin,/usr/sbin,/usr/local/bin"; + static public String[] ResTypes; + + static { + nf.setMaximumFractionDigits(4); + nf.setMinimumFractionDigits(4); + } + + protected final Logger logger; + private final cmdExec exec; + private final LinkedList devices = new LinkedList(); + private final HashMap diskIOReadKB = new HashMap(); + private final HashMap diskIOWriteKB = new HashMap(); + private final HashMap dKBRead = new HashMap(); + private final HashMap dKBWrite = new HashMap(); + private final Hashtable netIn = new Hashtable(); + private final Hashtable dnetIn = new Hashtable(); + private final Hashtable netOut = new Hashtable(); + private final Hashtable dnetOut = new Hashtable(); + protected String PROC_FILE_NAMES[]; + protected FileReader fileReaders[]; + protected BufferedReader bufferedReaders[]; + String[] old; // = new long[8]; + String[] cur; + String[] xtmp; + String[] diff; + long last_time_diskIO; + long last_time_cpu; + long last_time_net; + private boolean hasCommonCPUStats; + private boolean hasCPUIOWaitStats; + private boolean hasCPUIntStats; + private boolean hasCPUStealStats; + private boolean hasPageProcStat; + private boolean hasSwapProcStat; + private boolean hasPageProcVmStat; + private boolean hasSwapProcVmStat; + /** + * is this a 64 bits arch ? + */ + private boolean is64BitArch = false; + private boolean alreadyInitCPU = false; + private String diskIO = null; + private String tps = null; + private double diskTotal; + private double diskUsed; + private double diskFree; + private double memUsage; + private double memUsed; + private double memFree; + private double load1, load5, load15; + private String[] netInterfaces = null; + + public ProcReader(final Logger logger) { + this.logger = logger; + exec = cmdExec.getInstance(); + } + + public static void main(String args[]) { + ProcReader reader = new ProcReader(Logger.getLogger("lisa")); + System.out.println("***" + reader.getMACAddress()); +// try {HashMap h = reader.getCPUVM(); +// System.out.println(h);} catch (Throwable t) { t.printStackTrace(); } +// try { Thread.sleep(1000); } catch (Throwable t) { } +// try {HashMap h = reader.getCPUVM(); +// System.out.println(h);} catch (Throwable t) { t.printStackTrace(); } +// try { Thread.sleep(1000); } catch (Throwable t) { } +// try {HashMap h = reader.getCPUVM(); +// System.out.println(h);} catch (Throwable t) { t.printStackTrace(); } + try { + HashMap h = reader.getNet(); + System.out.println(h); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + Thread.sleep(1000); + } catch (Throwable t) { + } + try { + HashMap h = reader.getNet(); + System.out.println(h); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + Thread.sleep(1000); + } catch (Throwable t) { + } + } + + /** + * Method that returns the MAC hw address. + * + * @return The hw address or null + * @since java1.6 + */ + public final String getMACAddress() { + try { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + while (en.hasMoreElements()) { + NetworkInterface ni = en.nextElement(); + // get the hardware address + if (!ni.isLoopback()) { + try { + final String hadr = getHexa(ni.getHardwareAddress()); + if (hadr != null) return hadr; + } catch (SocketException se) { + } // nothing, just move on here + } + Enumeration en1 = ni.getSubInterfaces(); + while (en1.hasMoreElements()) { + ni = en1.nextElement(); + // get the hardware address + if (!ni.isLoopback()) { + try { + final String hadr = getHexa(ni.getHardwareAddress()); + if (hadr != null) return hadr; + } catch (SocketException se) { + } // nothing, just move on here + } + } + } + } catch (Throwable se) { + logger.log(Level.INFO, "Got error retrieving the network interfaces", se); + } + // if we did not succeded in this way, then try using ifconfig instead. + + final String path = getIfConfigPath(); + CommandResult cmdRes = exec.executeCommandReality("ifconfig -a", "b", path); + String output = cmdRes.getOutput(); + if (cmdRes.failed()) + output = null; + if (output != null && output.length() != 0 && !output.contains("No such file or directory") && !output.contains("Segmentation fault")) { + StringTokenizer st = new StringTokenizer(output); + String line = st.nextToken("\n"); + while (line != null) { + if (line != null && (line.startsWith(" ") || line.startsWith("\t"))) { + line = st.nextToken("\n"); + continue; + } + if (line == null) + break; + StringTokenizer ast = new StringTokenizer(line); + // get the name + ast.nextToken(" \t\n"); // netName + // get the hw address + if (line.indexOf("HWaddr ") >= 0) return line.substring(line.indexOf("HWaddr ") + 7); + line = st.nextToken("\n"); + } + } + return null; + } + + /** + * Method to retrieve CPU related parameters.. + * + * @return A hashmap, where String is the name of the param and Double is the current value + */ + public final HashMap getCPUVM() throws Exception { + initCPUReaders(); + + if (cur == null) + cur = new String[ResTypes.length]; + if (xtmp == null) + xtmp = new String[ResTypes.length]; + if (diff == null) + diff = new String[ResTypes.length]; + + + if (hasSwapProcVmStat || hasPageProcVmStat) { + PROC_FILE_NAMES = new String[]{"/proc/stat", "/proc/vmstat"}; + } else if (hasCommonCPUStats || hasSwapProcStat || hasPageProcStat) { + PROC_FILE_NAMES = new String[]{"/proc/stat"}; + } else + PROC_FILE_NAMES = null; + createReaders(); + + HashMap results = new HashMap(); + + long sTime = System.currentTimeMillis(); + int len = ResTypes.length; + BufferedReader br = bufferedReaders[0]; + int index = 0; + boolean parsedCPU = !hasCommonCPUStats; + + for (; ; ) { + String lin = br.readLine(); + + if (lin == null) + break; + + lin = lin.trim(); + if (lin.length() == 0) + continue; + + StringTokenizer tz = new StringTokenizer(lin); + + String item = tz.nextToken().trim(); + if (!parsedCPU && hasCommonCPUStats && item.equals("cpu")) { + parsedCPU = true; + cur[index++] = tz.nextToken(); + cur[index++] = tz.nextToken(); + cur[index++] = tz.nextToken(); + cur[index++] = tz.nextToken(); + + if (hasCPUIOWaitStats) { + cur[index++] = tz.nextToken(); + if (hasCPUIntStats) { + cur[index++] = tz.nextToken(); + cur[index++] = tz.nextToken(); + if (hasCPUStealStats) { + cur[index++] = tz.nextToken(); + } + } + } + if (hasPageProcVmStat && hasSwapProcVmStat) + break; + } else if (item.equals("page")) { + cur[index++] = tz.nextToken(); + cur[index++] = tz.nextToken(); + } else if (item.equals("swap")) { + cur[index++] = tz.nextToken(); + cur[index++] = tz.nextToken(); + } + } + + if (hasSwapProcVmStat || hasPageProcVmStat) { + br = bufferedReaders[1]; + int cCount = 0; + String lin = br.readLine(); + for (; cCount < 4 && lin != null; lin = br.readLine()) { + lin = lin.trim(); + if (lin.startsWith("pgpgin")) { + cur[len - 4] = lin.substring(7); + cCount++; + continue; + } + + if (lin.startsWith("pgpgout")) { + cur[len - 3] = lin.substring(8); + cCount++; + continue; + } + + if (lin.startsWith("pswpin")) { + cur[len - 2] = lin.substring(7); + cCount++; + continue; + } + + if (lin.startsWith("pswpout")) { + cur[len - 1] = lin.substring(8); + cCount++; + continue; + } + } + } + if (old == null) { + old = cur; + cur = xtmp; + last_time_cpu = System.currentTimeMillis(); + } else { + for (int i = 0; i < diff.length; i++) { + diff[i] = diffWithOverflowCheck(cur[i], old[i]); + } + + String sum = addWithOverflowCheck(diff[0], diff[1]); + sum = addWithOverflowCheck(sum, diff[2]); + sum = addWithOverflowCheck(sum, diff[3]); + if (hasCPUIOWaitStats) { + sum = addWithOverflowCheck(sum, diff[4]); + if (hasCPUIntStats) { + sum = addWithOverflowCheck(sum, diff[5]); + sum = addWithOverflowCheck(sum, diff[6]); + if (hasCPUStealStats) { + sum = addWithOverflowCheck(sum, diff[7]); + } + } + } + index = 0; + + String str1 = divideWithOverflowCheck(diff[0], sum); + double d = Double.parseDouble(str1) * 100D; + double totalP = d; + results.put(ResTypes[index++], d); + str1 = divideWithOverflowCheck(diff[1], sum); + d = Double.parseDouble(str1) * 100D; + totalP += d; + results.put(ResTypes[index++], d); + str1 = divideWithOverflowCheck(diff[2], sum); + d = Double.parseDouble(str1) * 100D; + totalP += d; + results.put(ResTypes[index++], d); + + if (hasCPUIOWaitStats) { + str1 = divideWithOverflowCheck(diff[4], sum); + d = Double.parseDouble(str1) * 100D; + totalP += d; + results.put(ResTypes[index++], d); + if (hasCPUIntStats) { + str1 = divideWithOverflowCheck(diff[5], sum); + d = Double.parseDouble(str1) * 100D; + totalP += d; + results.put(ResTypes[index++], d); + str1 = divideWithOverflowCheck(diff[6], sum); + d = Double.parseDouble(str1) * 100D; + totalP += d; + results.put(ResTypes[index++], d); + if (hasCPUStealStats) { + str1 = divideWithOverflowCheck(diff[7], sum); + d = Double.parseDouble(str1) * 100D; + totalP += d; + results.put(ResTypes[index++], d); + } + } + } + + str1 = divideWithOverflowCheck(diff[3], sum); + d = Double.parseDouble(str1) * 100D; + results.put(ResTypes[index++], d); + if (Math.abs(totalP + d - 0D) < 0.0001) + results.put("CPU_Usage", 1.0); + else + results.put("CPU_usage", (totalP / (totalP + d))); + double imp = ((sTime - last_time_cpu) / 1000D) * 1024D; /* - * Little comment here ( hopefully both page|swap _in|out will be in MB/s Check this first: - http://lkml.org/lkml/2002/4/12/6 - + * Little comment here ( hopefully both page|swap _in|out will be in MB/s Check this first: - http://lkml.org/lkml/2002/4/12/6 - * http://marc.theaimsgroup.com/?l=linux-kernel&m=101770318012189&w=2 the page_[ in | out ] represents in 1KB values the swap_[ in | out ] represents in PAGE_SIZE, * usually 4KB values */ - results.put(ResTypes[len - 4], Double.valueOf(divideWithOverflowCheck(diff[len - 4], ""+imp))); - results.put(ResTypes[len - 3], Double.valueOf(divideWithOverflowCheck(diff[len - 3], ""+imp))); + results.put(ResTypes[len - 4], Double.valueOf(divideWithOverflowCheck(diff[len - 4], "" + imp))); + results.put(ResTypes[len - 3], Double.valueOf(divideWithOverflowCheck(diff[len - 3], "" + imp))); - // TODO - this can be buggy + // TODO - this can be buggy /* * To termine the PAGE_SIZE you can run this code cat << EOF | #include main() { printf ("%d bytes\n",getpagesize()); } EOF gcc -xc - -o /tmp/getpagesize * /tmp/getpagesize; rm -f /tmp/getpagesize PAGE_SIZE was 4096 bytes on all these machines ( though on SunOS the module does not work ! ): Linux pccit16 2.6.17-rc2 #2 @@ -375,937 +402,1057 @@ public final HashMap getCPUVM() throws Exception { * RH_7.3 ) Linux lxplus056.cern.ch 2.4.21-40.EL.cernsmp #1 SMP Fri Mar 17 00:53:42 CET 2006 i686 i686 i386 GNU/Linux ( SLC 3.0.6 ) SunOS vinci 5.10 Generic_118844-26 * i86pc i386 i86pc Linux pccil 2.6.5-7.252-smp #1 SMP Tue Feb 14 11:11:04 UTC 2006 i686 i686 i386 GNU/Linux ( SuSE Linux 9.1 ) */ - results.put(ResTypes[len - 2], Double.valueOf(divideWithOverflowCheck((mulWithOverflowCheck(diff[len - 2], "" + 4)), "" + imp))); - results.put(ResTypes[len - 1], Double.valueOf(divideWithOverflowCheck((mulWithOverflowCheck(diff[len - 1], "" + 4)), "" + imp))); - - last_time_cpu = sTime; - xtmp = old; - old = cur; - cur = xtmp; - } - - cleanup(); - return results; - } - - /** - * Method to retrieve memory information - * @return Mapping if the memory related params - */ - public HashMap getMEM() throws Exception { - - BufferedReader in = new BufferedReader(new FileReader("/proc/meminfo")); - String line = in.readLine(); - String sMemFree, sMemTotal; - HashMap results = new HashMap(); - double dmemTotal = 0.0, dmemFree = 0.0; - while (line != null) { - if (line.startsWith("MemTotal:")) { - line = line.substring(9); - StringTokenizer ast = new StringTokenizer(line); - try { sMemTotal = ast.nextToken(); } catch (Exception e) { sMemTotal = null; } - double d = 0.0; - try { - d = Double.parseDouble(sMemTotal); - } catch (Exception e) { - d = -1.0; - } - if (d >= 0.0) - dmemTotal = d; - } - if (line.startsWith("MemFree:")) { - line = line.substring(8); - StringTokenizer ast = new StringTokenizer(line); - try { sMemFree = ast.nextToken(); } catch (Exception e) { sMemFree = null; } - double d = 0.0; - try { - d = Double.parseDouble(sMemFree); - } catch (Exception e) { - d = -1.0; - } - if (d >= 0.0) - dmemFree = d; - } - line = in.readLine(); - } - memFree = (dmemFree / 1024.0); - memUsed = ((dmemTotal - dmemFree) / 1024.0); - memUsage = (100.0 * (dmemTotal - dmemFree) / dmemTotal); - results.put("MemUsage", memUsage); - results.put("MemUsed", memUsed); - results.put("MemFree", memFree); - in.close(); - return results; - } - - /** - * Method to retrieve information related to disk usage.. - * @return Mapping of disk params... - */ - public HashMap getDISK() throws Exception { - String output = executeIOStat(); - String line, str; - if (output != null && !output.equals("")) { - long now = System.currentTimeMillis(); - diskIO = "0.00"; - tps = "0.00"; - StringTokenizer st = new StringTokenizer(output); - try { line = st.nextToken("\n"); } catch (Exception e) { line = null; } - double imp = ((now - last_time_diskIO) / 1000D) * 1024D; - while (line != null && !line.contains("Device:")) { - try { line = st.nextToken("\n"); } catch (Exception e) { line = null; } - } - devices.clear(); - if (line != null) { - String diff1 = null, diff2 = null; - while (true) { - try { str = st.nextToken(" \t\n"); } catch (Exception e) { str = null; } - if (str == null) - break; - final String device = str; - devices.addLast(device); - try { str = st.nextToken(" \t\n"); } catch (Exception e) { str = null; } // tps - tps = addWithOverflowCheck(str, tps); - try { str = st.nextToken(" \t\n"); } catch (Exception e) { str = null; } // skip KB read / sec - try { str = st.nextToken(" \t\n"); } catch (Exception e) { str = null; } // skip KB write / sec - try { str = st.nextToken(" \t\n"); } catch (Exception e) { str = null; } // KB read - final String dKBr = (String)dKBRead.get(device); - diff1 = diffWithOverflowCheck(str, dKBr == null ? str : dKBr); - diskIO = addWithOverflowCheck(diff1, diskIO); - if (dKBRead.containsKey(device)) { - String s = diffWithOverflowCheck(str, dKBr); - diskIOReadKB.put(device, divideWithOverflowCheck(s, "" + imp)); - } - dKBRead.put(device, str); - // sec - try { str = st.nextToken(" \t\n"); } catch (Exception e) { str = null; } // skip KB write / - final String dKBw = (String)dKBWrite.get(device); - diff2 = diffWithOverflowCheck(str, dKBw == null ? str : dKBw); - diskIO = addWithOverflowCheck(diff2, diskIO); - if (dKBWrite.containsKey(device)) { - String s = diffWithOverflowCheck(str, dKBw); - diskIOWriteKB.put(device, divideWithOverflowCheck(s, "" + imp)); - } - dKBWrite.put(device, str); - } - } - - diskIO = divideWithOverflowCheck(diskIO, "" + imp); - last_time_diskIO = now; - } else return null; - - HashMap results = new HashMap(); - - LinkedList toRemove = new LinkedList(); - for (Iterator it = dKBRead.keySet().iterator(); it.hasNext();) { - String d = it.next(); - if (!devices.contains(d)) { - toRemove.addLast(d); - } - } - for (Iterator it = toRemove.iterator(); it.hasNext();) { - String d = it.next(); - dKBRead.remove(d); - dKBWrite.remove(d); - diskIOReadKB.remove(d); - diskIOWriteKB.remove(d); - } - - output = executeDF(); - double size = 0.0, used = 0.0, available = 0.0, usage = 0.0; - if (output != null && output != "") { - StringTokenizer st = new StringTokenizer(output); - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - int nr = 0; - for (int i = 0; i < 6 && line != null; i++) - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - while (true) { - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - if (line == null) - break; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // size - double d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d < 0.0) - break; - size += d; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // used - d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d < 0.0) - break; - used += d; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // available - d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d < 0.0) - break; - available += d; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // usage - try { line = line.substring(0,line.indexOf("%")); } catch (Exception e) { } - d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d < 0.0) - break; - usage += d; - nr++; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - if (line == null) - break; - } - diskTotal = (size / (1024.0 * 1024.0)); // total size (GB) - diskUsed = (used / (1024.0 * 1024.0)); // used size (GB) - diskFree = (available / (1024.0 * 1024.0)); // free size - // (GB) + results.put(ResTypes[len - 2], Double.valueOf(divideWithOverflowCheck((mulWithOverflowCheck(diff[len - 2], "" + 4)), "" + imp))); + results.put(ResTypes[len - 1], Double.valueOf(divideWithOverflowCheck((mulWithOverflowCheck(diff[len - 1], "" + 4)), "" + imp))); + + last_time_cpu = sTime; + xtmp = old; + old = cur; + cur = xtmp; + } + + cleanup(); + return results; + } + + /** + * Method to retrieve memory information + * + * @return Mapping if the memory related params + */ + public HashMap getMEM() throws Exception { + + BufferedReader in = new BufferedReader(new FileReader("/proc/meminfo")); + String line = in.readLine(); + String sMemFree, sMemTotal; + HashMap results = new HashMap(); + double dmemTotal = 0.0, dmemFree = 0.0; + while (line != null) { + if (line.startsWith("MemTotal:")) { + line = line.substring(9); + StringTokenizer ast = new StringTokenizer(line); + try { + sMemTotal = ast.nextToken(); + } catch (Exception e) { + sMemTotal = null; + } + double d = 0.0; + try { + d = Double.parseDouble(sMemTotal); + } catch (Exception e) { + d = -1.0; + } + if (d >= 0.0) + dmemTotal = d; + } + if (line.startsWith("MemFree:")) { + line = line.substring(8); + StringTokenizer ast = new StringTokenizer(line); + try { + sMemFree = ast.nextToken(); + } catch (Exception e) { + sMemFree = null; + } + double d = 0.0; + try { + d = Double.parseDouble(sMemFree); + } catch (Exception e) { + d = -1.0; + } + if (d >= 0.0) + dmemFree = d; + } + line = in.readLine(); + } + memFree = (dmemFree / 1024.0); + memUsed = ((dmemTotal - dmemFree) / 1024.0); + memUsage = (100.0 * (dmemTotal - dmemFree) / dmemTotal); + results.put("MemUsage", memUsage); + results.put("MemUsed", memUsed); + results.put("MemFree", memFree); + in.close(); + return results; + } + + /** + * Method to retrieve information related to disk usage.. + * + * @return Mapping of disk params... + */ + public HashMap getDISK() throws Exception { + String output = executeIOStat(); + String line, str; + if (output != null && !output.equals("")) { + long now = System.currentTimeMillis(); + diskIO = "0.00"; + tps = "0.00"; + StringTokenizer st = new StringTokenizer(output); + try { + line = st.nextToken("\n"); + } catch (Exception e) { + line = null; + } + double imp = ((now - last_time_diskIO) / 1000D) * 1024D; + while (line != null && !line.contains("Device:")) { + try { + line = st.nextToken("\n"); + } catch (Exception e) { + line = null; + } + } + devices.clear(); + if (line != null) { + String diff1 = null, diff2 = null; + while (true) { + try { + str = st.nextToken(" \t\n"); + } catch (Exception e) { + str = null; + } + if (str == null) + break; + final String device = str; + devices.addLast(device); + try { + str = st.nextToken(" \t\n"); + } catch (Exception e) { + str = null; + } // tps + tps = addWithOverflowCheck(str, tps); + try { + str = st.nextToken(" \t\n"); + } catch (Exception e) { + str = null; + } // skip KB read / sec + try { + str = st.nextToken(" \t\n"); + } catch (Exception e) { + str = null; + } // skip KB write / sec + try { + str = st.nextToken(" \t\n"); + } catch (Exception e) { + str = null; + } // KB read + final String dKBr = (String) dKBRead.get(device); + diff1 = diffWithOverflowCheck(str, dKBr == null ? str : dKBr); + diskIO = addWithOverflowCheck(diff1, diskIO); + if (dKBRead.containsKey(device)) { + String s = diffWithOverflowCheck(str, dKBr); + diskIOReadKB.put(device, divideWithOverflowCheck(s, "" + imp)); + } + dKBRead.put(device, str); + // sec + try { + str = st.nextToken(" \t\n"); + } catch (Exception e) { + str = null; + } // skip KB write / + final String dKBw = (String) dKBWrite.get(device); + diff2 = diffWithOverflowCheck(str, dKBw == null ? str : dKBw); + diskIO = addWithOverflowCheck(diff2, diskIO); + if (dKBWrite.containsKey(device)) { + String s = diffWithOverflowCheck(str, dKBw); + diskIOWriteKB.put(device, divideWithOverflowCheck(s, "" + imp)); + } + dKBWrite.put(device, str); + } + } + + diskIO = divideWithOverflowCheck(diskIO, "" + imp); + last_time_diskIO = now; + } else return null; + + HashMap results = new HashMap(); + + LinkedList toRemove = new LinkedList(); + for (Iterator it = dKBRead.keySet().iterator(); it.hasNext(); ) { + String d = it.next(); + if (!devices.contains(d)) { + toRemove.addLast(d); + } + } + for (Iterator it = toRemove.iterator(); it.hasNext(); ) { + String d = it.next(); + dKBRead.remove(d); + dKBWrite.remove(d); + diskIOReadKB.remove(d); + diskIOWriteKB.remove(d); + } + + output = executeDF(); + double size = 0.0, used = 0.0, available = 0.0, usage = 0.0; + if (output != null && output != "") { + StringTokenizer st = new StringTokenizer(output); + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + int nr = 0; + for (int i = 0; i < 6 && line != null; i++) + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + while (true) { + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + if (line == null) + break; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // size + double d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d < 0.0) + break; + size += d; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // used + d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d < 0.0) + break; + used += d; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // available + d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d < 0.0) + break; + available += d; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // usage + try { + line = line.substring(0, line.indexOf("%")); + } catch (Exception e) { + } + d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d < 0.0) + break; + usage += d; + nr++; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + if (line == null) + break; + } + diskTotal = (size / (1024.0 * 1024.0)); // total size (GB) + diskUsed = (used / (1024.0 * 1024.0)); // used size (GB) + diskFree = (available / (1024.0 * 1024.0)); // free size + // (GB) // diskUsage = (usage * 1.0 / nr); // usage (%) - } else { // read from /proc/ide - String files[] = listFiles("/proc/ide"); - if (files != null && files.length != 0) - for (int i = 0; i < files.length; i++) - if (files[i].startsWith("hd")) { - try { - BufferedReader in = new BufferedReader(new FileReader("/proc/ide/" + files[i] + "/capacity")); - line = in.readLine(); - double d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d >= 0.0) - size += d; - in.close(); - } catch (Exception e) { } - } - diskTotal = (size / (1024.0 * 1024.0)); // disk total (GB) - diskFree = diskTotal; - } - - try { - results.put("DiskIO", Double.valueOf(diskIO)); - } catch (Exception e) { - results.put("DiskIO", -1.0); - } - try { - results.put("TPS", Double.valueOf(tps)); - } catch (Exception e) { - results.put("TPS", -1.0); - } - if (devices != null) - for (String dev : devices) { - if (diskIOReadKB.containsKey(dev)) { - try { - results.put("DiskIORead_"+dev, Double.valueOf(diskIOReadKB.get(dev))); - } catch (Exception e) { - results.put("DiskIORead_"+dev, -1.0); - } - } - if (diskIOWriteKB.containsKey(dev)) { - try { - results.put("DiskIORead_"+dev, Double.valueOf(diskIOReadKB.get(dev))); - } catch (Exception e) { - results.put("DiskIORead_"+dev, -1.0); - } - } - } - results.put("DiskTotal", diskTotal); - results.put("DiskUsed", diskUsed); - results.put("DiskFree", diskFree); - results.put("DiskUsage", new Double(100.0 * diskUsed / (diskUsed + diskFree))); - toRemove.clear(); - return results; - } - - /** - * Retrieves the number of running processes or -1 if error - * @return The number of processes - */ - public int getProcesses() { - String[] files = listFiles("/proc"); - if (files != null && files.length != 0) { - int nr = 0; - for (int i = 0; i < files.length; i++) { - char[] chars = files[i].toCharArray(); - boolean isProc = true; - for (int j = 0; j < chars.length; j++) - if (!Character.isDigit(chars[j])) { - isProc = false; - break; - } - if (isProc) - nr++; - } - return nr; - } - return -1; - } - - /** - * Retrieves the current load values - * @return Mapping of load1, 5, 15 - */ - public HashMap getLOAD() throws Exception { - BufferedReader in = new BufferedReader(new FileReader("/proc/loadavg")); - String f = ""; - String line; - while ((line = in.readLine()) != null) { - f = f + "\n"+line; - } - in.close(); - HashMap results = new HashMap(); - StringTokenizer st = new StringTokenizer(f); - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // load1 - if (line != null) { - double d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d >= 0.0) - load1 = d; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; }// load5 - d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d >= 0.0) - load5 = d; - try { line = st.nextToken(" \t\n");} catch (Exception e) { line = null; } // load15 - d = 0.0; - try { - d = Double.parseDouble(line); - } catch (Exception e) { - d = -1.0; - } - if (d >= 0.0) - load15 = d; - } - results.put("Load1", load1); - results.put("Load5", load5); - results.put("Load15", load15); - return results; - } - - /** - * Method to retrieve traffic information - * @return Mapping of in and out traffic for each found interfaces - */ - public HashMap getNet() throws Exception { - String line; - BufferedReader in = new BufferedReader(new FileReader("/proc/net/dev")); - String f = ""; - while ((line = in.readLine()) != null) { - f = f + "\n"+line; - } - in.close(); - HashMap results = new HashMap(); - StringTokenizer st = new StringTokenizer(f); - long now = System.currentTimeMillis(); - if (netInterfaces == null) { - while (true) { - try { line = st.nextToken(":\n\t "); } catch (Exception e) { line = null; } - if (line == null) - break; - if (line.startsWith("eth") || line.startsWith("lo")) { - addNetInterface(line); - String name = line; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // bytes received - if (dnetIn.containsKey(name)) { - final String lastIn = dnetIn.get(name); - try { - double d = Double.parseDouble(diffWithOverflowCheck(line, lastIn)); - d = (d * 8.0D) / (now - last_time_net) / 1000.0D; - netIn.put(name, "" + d); - } catch (Exception e) { - } - dnetIn.put(name, line); - } else { - netIn.put(name, line); - dnetIn.put(name, line); - } - for (int i = 0; i < 7; i++) { - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - } - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // bytes sent - if (dnetOut.containsKey(name)) { - final String lastOut = dnetOut.get(name); - try { - double d = Double.parseDouble(diffWithOverflowCheck(line, lastOut)); - d = (d * 8.0D) / (now - last_time_net) / 1000.0D; - netOut.put(name, "" + d); - } catch (Exception e) { - } - dnetOut.put(name, line); - } else { - netOut.put(name, line); - dnetOut.put(name, line); - } - } - } - } else { - while (true) { - try { line = st.nextToken(":\n\t "); } catch (Exception e) { line = null; } - if (line == null) - break; - boolean found = false; - for (int i = 0; i < netInterfaces.length; i++) - if (line.equals(netInterfaces[i])) { - found = true; - break; - } - if (found) { - String name = line; - try { line = st.nextToken(" \t\n:"); } catch (Exception e) { line = null; }// bytes received - if (dnetIn.containsKey(name)) { - final String lastIn = dnetIn.get(name); - try { - double d = Double.parseDouble(diffWithOverflowCheck(line, lastIn)); - d = (d * 8.0D) / (now - last_time_net) / 1000.0D; - // logger.info("For "+name+"in = "+d+" old="+lastIn+" new="+line+" diff="+(now - last_time_net)); - netIn.put(name, "" + d); - } catch (Exception e) { - } - dnetIn.put(name, line); - } else { - netIn.put(name, line); - dnetIn.put(name, line); - } - for (int i = 0; i < 7; i++) { - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - } - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // bytes sent - if (dnetOut.containsKey(name)) { - final String lastOut = dnetOut.get(name); - try { - double d = Double.parseDouble(diffWithOverflowCheck(line, lastOut)); - d = (d * 8.0D) / (now - last_time_net) / 1000.0D; - // logger.info("For "+name+" out = "+d+" old="+lastOut+" new="+line+" diff="+(now - last_time_net)); - netOut.put(name, "" + d); - } catch (Exception e) { - } - dnetOut.put(name, line); - } else { - netOut.put(name, line); - dnetOut.put(name, line); - } - } else { - if (line.startsWith("eth") || line.startsWith("lo")) { - addNetInterface(line); - String name = line; - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // bytes received - if (dnetIn.containsKey(name)) { - final String lastIn = dnetIn.get(name); - try { - double d = Double.parseDouble(diffWithOverflowCheck(line, lastIn)); - d = (d * 8.0D) / (now - last_time_net) / 1000.0D; - netIn.put(name, "" + d); - } catch (Exception e) { - } - dnetIn.put(name, line); - } else { - netIn.put(name, line); - dnetIn.put(name, line); - } - for (int i = 0; i < 7; i++) { - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } - } - try { line = st.nextToken(" \t\n"); } catch (Exception e) { line = null; } // bytes sent - if (dnetOut.containsKey(name)) { - final String lastOut = dnetOut.get(name); - try { - double d = Double.parseDouble(diffWithOverflowCheck(line, lastOut)); - d = (d * 8.0D) / (now - last_time_net) / 1000.0D; - netOut.put(name, "" + d); - } catch (Exception e) { - } - dnetOut.put(name, line); - } else { - netOut.put(name, line); - dnetOut.put(name, line); - } - } - } - } - } - last_time_net = now; - if (netInterfaces != null && netInterfaces.length != 0) - for (int i=0; i> 4; - d2 += (d2 < 10) ? 48 : 55; - buf.append((char)d2).append((char)d1); - if (i < b.length-1) buf.append(':'); - } - return buf.toString(); - } - - private final synchronized String getIfConfigPath() { - String path = SYS_EXTENDED_BIN_PATH; - if (System.getProperty("ifconfig.path", null) != null) - path = System.getProperty("ifconfig.path"); - if (path == null || path.length() == 0) { - logger.warning("[Host - ifconfig can not be found in " + path + "]"); - return null; - } - return path.replace(',', ':').trim(); - } - - private final void initCPUReaders() { - if (alreadyInitCPU) return; - try { - ArrayList al = new ArrayList(15);// at least - // 12 - FileReader fr = null; - BufferedReader br = null; - File procFile = null; - // check for info that can be processed from /proc/stat - try { - procFile = new File("/proc/stat"); - if (procFile.exists() && procFile.canRead()) { - fr = new FileReader("/proc/stat"); - br = new BufferedReader(fr); - String line = br.readLine(); - boolean parsedCPU = false; - for (; line != null; line = br.readLine()) { - line = line.trim(); - if (!parsedCPU && line.startsWith("cpu")) { - parsedCPU = true; - String[] tokens = line.split("(\\s)+"); - int len = tokens.length; - if (len >= 5) { - al.add("CPU_usr"); - al.add("CPU_nice"); - al.add("CPU_sys"); - hasCommonCPUStats = true; - if (len >= 6) { - al.add("CPU_iowait"); - hasCPUIOWaitStats = true; - if (len >= 8) { - al.add("CPU_int"); - al.add("CPU_softint"); - hasCPUIntStats = true; - if (len >= 9) { - al.add("CPU_steal"); - hasCPUStealStats = true; - } - } - } - al.add("CPU_idle"); - }// if (len >= 5 ) - } else {// if ( "cpu" ) - if (line.startsWith("page")) { - hasPageProcStat = true; - } else if (line.startsWith("swap")) { - hasSwapProcStat = true; - } - } - }// for - }// if ( procStatF.exists() ) - } catch (Throwable pft) { - logger.log(Level.WARNING, "Checking for /proc/stat yield a caught exception ", pft); - } finally { - try { - if (fr != null) - fr.close(); - if (br != null) - br.close(); - } catch (Throwable ignore) { - } - fr = null; - br = null; - } - - // check for info that can be processed from /proc/vmstat - procFile = new File("/proc/vmstat"); - try { - if (procFile.exists() && procFile.canRead()) { - fr = new FileReader("/proc/vmstat"); - br = new BufferedReader(fr); - - String line = br.readLine(); - for (; line != null; line = br.readLine()) { - line = line.trim(); - if (line.startsWith("pgpgin")) { - hasPageProcVmStat = true; - continue; - } - if (line.startsWith("pswpin")) { - hasSwapProcVmStat = true; - continue; - } - }// for - }// if - exists && canRead - } catch (Throwable pft) { - logger.log(Level.WARNING, "Checking for /proc/vmstat yield a caught exception ", pft); - } finally { - try { - if (fr != null) - fr.close(); - if (br != null) - br.close(); - } catch (Throwable ignore) { - } - fr = null; - br = null; - } - - if (hasPageProcStat || hasPageProcVmStat) { - al.add("Page_in"); - al.add("Page_out"); - } - - if (hasSwapProcStat || hasSwapProcVmStat) { - al.add("Swap_in"); - al.add("Swap_out"); - } - ResTypes = (String[]) al.toArray(new String[al.size()]); - alreadyInitCPU = true; - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception in init. The module will not be used for local monitoring ", t); - } - } - - protected void resetReaders() throws Exception { - createReaders(); - } - - public void cleanup() { - if (bufferedReaders != null) { - for (int i = 0; i < bufferedReaders.length; i++) { - try { - if (bufferedReaders[i] != null) { - bufferedReaders[i].close(); - } - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception closing buffered reader [ " + i + " ] ", t); - } finally { - bufferedReaders[i] = null; // let GC do the job - } - } - }// if bufferedReaders - if (fileReaders != null) { - for (int i = 0; i < fileReaders.length; i++) { - try { - if (fileReaders[i] != null) { - fileReaders[i].close(); - } - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception closing file reader [ " + i + " ] ", t); - } finally { - fileReaders[i] = null; // let GC do the job - } - } - } - } - - private void createReaders() throws Exception { - cleanup(); - if (PROC_FILE_NAMES == null) - throw new Exception(" PROC_FILE_NAMES is null"); - - if (bufferedReaders == null || bufferedReaders.length != PROC_FILE_NAMES.length) { - bufferedReaders = new BufferedReader[PROC_FILE_NAMES.length]; - } - - if (fileReaders == null || fileReaders.length != PROC_FILE_NAMES.length) { - fileReaders = new FileReader[PROC_FILE_NAMES.length]; - } - - for (int i = 0; i < PROC_FILE_NAMES.length; i++) { - try { - if (PROC_FILE_NAMES[i] != null) { - fileReaders[i] = new FileReader(PROC_FILE_NAMES[i]); - bufferedReaders[i] = new BufferedReader(fileReaders[i]); - } else { - logger.warning("PROC_FILE_NAMES[" + i + "] is null"); - } - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exc creating Readers [ " + i + " ] :- " + PROC_FILE_NAMES[i] + " ", t); - } - } - } - - private String addWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { - - if (newVal == null) - return oldVal; - if (oldVal == null) - return newVal; - - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - return newv.add(oldv).toString(); - } - // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return addWithOverflowCheck(newVal, oldVal); - } - // so it's still 32 bits arch - return "" + (newv + oldv); - } - - private String divideWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { - - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - return newv.divide(oldv, BigDecimal.ROUND_FLOOR).toString(); - } - // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return divideWithOverflowCheck(newVal, oldVal); - } - // so it's still 32 bits arch - return "" + (newv / oldv); - } - - private String mulWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - return newv.multiply(oldv).toString(); - } - // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return mulWithOverflowCheck(newVal, oldVal); - } - // so it's still 32 bits arch - return "" + (newv * oldv); - } - - private String diffWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - if (newv.compareTo(oldv) >= 0) - return newv.subtract(oldv).toString(); - BigInteger overflow = new BigInteger("1").shiftLeft(64); - BigDecimal d = new BigDecimal(overflow.toString()); - return newv.add(d).subtract(oldv).toString(); - } - // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return diffWithOverflowCheck(newVal, oldVal); - } - // so it's still 32 bits arch - if (newv >= oldv) { - return "" + (newv - oldv); - } - long vmax = 1L << 32; // 32 bits - return "" + (newv - oldv + vmax); - } - - private final String prepareString(String str) { - - // first try to make it double - try { - double d = Double.parseDouble(str); - if (!Double.isInfinite(d) && !Double.isNaN(d)) { - String n = nf.format(d); - n = n.replaceAll(",", ""); - return n; - } - } catch (Throwable t) { - } - - if (!str.contains(".")) { - return str + ".0000"; - } - int nr = str.lastIndexOf('.') + 1; - nr = str.length() - nr; - for (int i = nr; i < 4; i++) - str += "0"; - return str; - } - - private final synchronized String executeIOStat() { - - String path = SYS_EXTENDED_BIN_PATH+"," + System.getProperty("user.home"); - if (System.getProperty("iostat.path", null) != null) - path = System.getProperty("iostat.path"); - if (path != null && path.length() != 0) { - path = path.replace(',', ':').trim(); - } - - CommandResult cmdRes = exec.executeCommandReality("iostat -k", "L", path); - final String output = cmdRes.getOutput(); - if (!cmdRes.failed() && output.length() != 0 && !output.contains("No such file or directory") && !output.contains("Segmentation fault")) - return output; - return null; - } - - private final synchronized String executeDF() { - - String path = SYS_EXTENDED_BIN_PATH; - if (System.getProperty("df.path", null) != null) - path = System.getProperty("df.path"); - if (path != null && path.length() != 0) { - path = path.replace(",", ":").trim(); - } - CommandResult cmdRes = exec.executeCommandReality("df -B 1024", "o", path); - String output = cmdRes.getOutput(); - if (!cmdRes.failed() && output.length() != 0 && !output.contains("No such file or directory") && !output.contains("Segmentation fault")) - return output; - return null; - } - - private String[] listFiles(String directory) { - String[] fileList = null; - try { - File dir = new File(directory); - if (!dir.isDirectory()) return null; - File[] list = dir.listFiles(); - if (list == null) return null; - fileList = new String[list.length]; - for (int i=0; i= 0.0) + size += d; + in.close(); + } catch (Exception e) { + } + } + diskTotal = (size / (1024.0 * 1024.0)); // disk total (GB) + diskFree = diskTotal; + } + + try { + results.put("DiskIO", Double.valueOf(diskIO)); + } catch (Exception e) { + results.put("DiskIO", -1.0); + } + try { + results.put("TPS", Double.valueOf(tps)); + } catch (Exception e) { + results.put("TPS", -1.0); + } + if (devices != null) + for (String dev : devices) { + if (diskIOReadKB.containsKey(dev)) { + try { + results.put("DiskIORead_" + dev, Double.valueOf(diskIOReadKB.get(dev))); + } catch (Exception e) { + results.put("DiskIORead_" + dev, -1.0); + } + } + if (diskIOWriteKB.containsKey(dev)) { + try { + results.put("DiskIORead_" + dev, Double.valueOf(diskIOReadKB.get(dev))); + } catch (Exception e) { + results.put("DiskIORead_" + dev, -1.0); + } + } + } + results.put("DiskTotal", diskTotal); + results.put("DiskUsed", diskUsed); + results.put("DiskFree", diskFree); + results.put("DiskUsage", new Double(100.0 * diskUsed / (diskUsed + diskFree))); + toRemove.clear(); + return results; + } + + /** + * Retrieves the number of running processes or -1 if error + * + * @return The number of processes + */ + public int getProcesses() { + String[] files = listFiles("/proc"); + if (files != null && files.length != 0) { + int nr = 0; + for (int i = 0; i < files.length; i++) { + char[] chars = files[i].toCharArray(); + boolean isProc = true; + for (int j = 0; j < chars.length; j++) + if (!Character.isDigit(chars[j])) { + isProc = false; + break; + } + if (isProc) + nr++; + } + return nr; + } + return -1; + } + + /** + * Retrieves the current load values + * + * @return Mapping of load1, 5, 15 + */ + public HashMap getLOAD() throws Exception { + BufferedReader in = new BufferedReader(new FileReader("/proc/loadavg")); + String f = ""; + String line; + while ((line = in.readLine()) != null) { + f = f + "\n" + line; + } + in.close(); + HashMap results = new HashMap(); + StringTokenizer st = new StringTokenizer(f); + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // load1 + if (line != null) { + double d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d >= 0.0) + load1 = d; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + }// load5 + d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d >= 0.0) + load5 = d; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // load15 + d = 0.0; + try { + d = Double.parseDouble(line); + } catch (Exception e) { + d = -1.0; + } + if (d >= 0.0) + load15 = d; + } + results.put("Load1", load1); + results.put("Load5", load5); + results.put("Load15", load15); + return results; + } + + /** + * Method to retrieve traffic information + * + * @return Mapping of in and out traffic for each found interfaces + */ + public HashMap getNet() throws Exception { + String line; + BufferedReader in = new BufferedReader(new FileReader("/proc/net/dev")); + String f = ""; + while ((line = in.readLine()) != null) { + f = f + "\n" + line; + } + in.close(); + HashMap results = new HashMap(); + StringTokenizer st = new StringTokenizer(f); + long now = System.currentTimeMillis(); + if (netInterfaces == null) { + while (true) { + try { + line = st.nextToken(":\n\t "); + } catch (Exception e) { + line = null; + } + if (line == null) + break; + if (line.startsWith("eth") || line.startsWith("lo")) { + addNetInterface(line); + String name = line; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // bytes received + if (dnetIn.containsKey(name)) { + final String lastIn = dnetIn.get(name); + try { + double d = Double.parseDouble(diffWithOverflowCheck(line, lastIn)); + d = (d * 8.0D) / (now - last_time_net) / 1000.0D; + netIn.put(name, "" + d); + } catch (Exception e) { + } + dnetIn.put(name, line); + } else { + netIn.put(name, line); + dnetIn.put(name, line); + } + for (int i = 0; i < 7; i++) { + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + } + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // bytes sent + if (dnetOut.containsKey(name)) { + final String lastOut = dnetOut.get(name); + try { + double d = Double.parseDouble(diffWithOverflowCheck(line, lastOut)); + d = (d * 8.0D) / (now - last_time_net) / 1000.0D; + netOut.put(name, "" + d); + } catch (Exception e) { + } + dnetOut.put(name, line); + } else { + netOut.put(name, line); + dnetOut.put(name, line); + } + } + } + } else { + while (true) { + try { + line = st.nextToken(":\n\t "); + } catch (Exception e) { + line = null; + } + if (line == null) + break; + boolean found = false; + for (int i = 0; i < netInterfaces.length; i++) + if (line.equals(netInterfaces[i])) { + found = true; + break; + } + if (found) { + String name = line; + try { + line = st.nextToken(" \t\n:"); + } catch (Exception e) { + line = null; + }// bytes received + if (dnetIn.containsKey(name)) { + final String lastIn = dnetIn.get(name); + try { + double d = Double.parseDouble(diffWithOverflowCheck(line, lastIn)); + d = (d * 8.0D) / (now - last_time_net) / 1000.0D; + // logger.info("For "+name+"in = "+d+" old="+lastIn+" new="+line+" diff="+(now - last_time_net)); + netIn.put(name, "" + d); + } catch (Exception e) { + } + dnetIn.put(name, line); + } else { + netIn.put(name, line); + dnetIn.put(name, line); + } + for (int i = 0; i < 7; i++) { + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + } + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // bytes sent + if (dnetOut.containsKey(name)) { + final String lastOut = dnetOut.get(name); + try { + double d = Double.parseDouble(diffWithOverflowCheck(line, lastOut)); + d = (d * 8.0D) / (now - last_time_net) / 1000.0D; + // logger.info("For "+name+" out = "+d+" old="+lastOut+" new="+line+" diff="+(now - last_time_net)); + netOut.put(name, "" + d); + } catch (Exception e) { + } + dnetOut.put(name, line); + } else { + netOut.put(name, line); + dnetOut.put(name, line); + } + } else { + if (line.startsWith("eth") || line.startsWith("lo")) { + addNetInterface(line); + String name = line; + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // bytes received + if (dnetIn.containsKey(name)) { + final String lastIn = dnetIn.get(name); + try { + double d = Double.parseDouble(diffWithOverflowCheck(line, lastIn)); + d = (d * 8.0D) / (now - last_time_net) / 1000.0D; + netIn.put(name, "" + d); + } catch (Exception e) { + } + dnetIn.put(name, line); + } else { + netIn.put(name, line); + dnetIn.put(name, line); + } + for (int i = 0; i < 7; i++) { + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } + } + try { + line = st.nextToken(" \t\n"); + } catch (Exception e) { + line = null; + } // bytes sent + if (dnetOut.containsKey(name)) { + final String lastOut = dnetOut.get(name); + try { + double d = Double.parseDouble(diffWithOverflowCheck(line, lastOut)); + d = (d * 8.0D) / (now - last_time_net) / 1000.0D; + netOut.put(name, "" + d); + } catch (Exception e) { + } + dnetOut.put(name, line); + } else { + netOut.put(name, line); + dnetOut.put(name, line); + } + } + } + } + } + last_time_net = now; + if (netInterfaces != null && netInterfaces.length != 0) + for (int i = 0; i < netInterfaces.length; i++) { + final String ifName = netInterfaces[i]; + try { + results.put("In_" + ifName, Double.valueOf(netIn.get(ifName))); + } catch (Exception e) { + } + try { + results.put("Out_" + ifName, Double.valueOf(netOut.get(ifName))); + } catch (Exception e) { + } + } + return results; + } + + /** + * UTILITY METHODS + */ + + private final String getHexa(byte[] b) { + if (b == null || b.length == 0) return null; + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < b.length; i++) { + byte value = b[i]; + int d1 = value & 0xF; + d1 += (d1 < 10) ? 48 : 55; + int d2 = (value & 0xF0) >> 4; + d2 += (d2 < 10) ? 48 : 55; + buf.append((char) d2).append((char) d1); + if (i < b.length - 1) buf.append(':'); + } + return buf.toString(); + } + + private final synchronized String getIfConfigPath() { + String path = SYS_EXTENDED_BIN_PATH; + if (System.getProperty("ifconfig.path", null) != null) + path = System.getProperty("ifconfig.path"); + if (path == null || path.length() == 0) { + logger.warning("[Host - ifconfig can not be found in " + path + "]"); + return null; + } + return path.replace(',', ':').trim(); + } + + private final void initCPUReaders() { + if (alreadyInitCPU) return; + try { + ArrayList al = new ArrayList(15);// at least + // 12 + FileReader fr = null; + BufferedReader br = null; + File procFile = null; + // check for info that can be processed from /proc/stat + try { + procFile = new File("/proc/stat"); + if (procFile.exists() && procFile.canRead()) { + fr = new FileReader("/proc/stat"); + br = new BufferedReader(fr); + String line = br.readLine(); + boolean parsedCPU = false; + for (; line != null; line = br.readLine()) { + line = line.trim(); + if (!parsedCPU && line.startsWith("cpu")) { + parsedCPU = true; + String[] tokens = line.split("(\\s)+"); + int len = tokens.length; + if (len >= 5) { + al.add("CPU_usr"); + al.add("CPU_nice"); + al.add("CPU_sys"); + hasCommonCPUStats = true; + if (len >= 6) { + al.add("CPU_iowait"); + hasCPUIOWaitStats = true; + if (len >= 8) { + al.add("CPU_int"); + al.add("CPU_softint"); + hasCPUIntStats = true; + if (len >= 9) { + al.add("CPU_steal"); + hasCPUStealStats = true; + } + } + } + al.add("CPU_idle"); + }// if (len >= 5 ) + } else {// if ( "cpu" ) + if (line.startsWith("page")) { + hasPageProcStat = true; + } else if (line.startsWith("swap")) { + hasSwapProcStat = true; + } + } + }// for + }// if ( procStatF.exists() ) + } catch (Throwable pft) { + logger.log(Level.WARNING, "Checking for /proc/stat yield a caught exception ", pft); + } finally { + try { + if (fr != null) + fr.close(); + if (br != null) + br.close(); + } catch (Throwable ignore) { + } + fr = null; + br = null; + } + + // check for info that can be processed from /proc/vmstat + procFile = new File("/proc/vmstat"); + try { + if (procFile.exists() && procFile.canRead()) { + fr = new FileReader("/proc/vmstat"); + br = new BufferedReader(fr); + + String line = br.readLine(); + for (; line != null; line = br.readLine()) { + line = line.trim(); + if (line.startsWith("pgpgin")) { + hasPageProcVmStat = true; + continue; + } + if (line.startsWith("pswpin")) { + hasSwapProcVmStat = true; + continue; + } + }// for + }// if - exists && canRead + } catch (Throwable pft) { + logger.log(Level.WARNING, "Checking for /proc/vmstat yield a caught exception ", pft); + } finally { + try { + if (fr != null) + fr.close(); + if (br != null) + br.close(); + } catch (Throwable ignore) { + } + fr = null; + br = null; + } + + if (hasPageProcStat || hasPageProcVmStat) { + al.add("Page_in"); + al.add("Page_out"); + } + + if (hasSwapProcStat || hasSwapProcVmStat) { + al.add("Swap_in"); + al.add("Swap_out"); + } + ResTypes = (String[]) al.toArray(new String[al.size()]); + alreadyInitCPU = true; + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception in init. The module will not be used for local monitoring ", t); + } + } + + protected void resetReaders() throws Exception { + createReaders(); + } + + public void cleanup() { + if (bufferedReaders != null) { + for (int i = 0; i < bufferedReaders.length; i++) { + try { + if (bufferedReaders[i] != null) { + bufferedReaders[i].close(); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception closing buffered reader [ " + i + " ] ", t); + } finally { + bufferedReaders[i] = null; // let GC do the job + } + } + }// if bufferedReaders + if (fileReaders != null) { + for (int i = 0; i < fileReaders.length; i++) { + try { + if (fileReaders[i] != null) { + fileReaders[i].close(); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception closing file reader [ " + i + " ] ", t); + } finally { + fileReaders[i] = null; // let GC do the job + } + } + } + } + + private void createReaders() throws Exception { + cleanup(); + if (PROC_FILE_NAMES == null) + throw new Exception(" PROC_FILE_NAMES is null"); + + if (bufferedReaders == null || bufferedReaders.length != PROC_FILE_NAMES.length) { + bufferedReaders = new BufferedReader[PROC_FILE_NAMES.length]; + } + + if (fileReaders == null || fileReaders.length != PROC_FILE_NAMES.length) { + fileReaders = new FileReader[PROC_FILE_NAMES.length]; + } + + for (int i = 0; i < PROC_FILE_NAMES.length; i++) { + try { + if (PROC_FILE_NAMES[i] != null) { + fileReaders[i] = new FileReader(PROC_FILE_NAMES[i]); + bufferedReaders[i] = new BufferedReader(fileReaders[i]); + } else { + logger.warning("PROC_FILE_NAMES[" + i + "] is null"); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exc creating Readers [ " + i + " ] :- " + PROC_FILE_NAMES[i] + " ", t); + } + } + } + + private String addWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { + + if (newVal == null) + return oldVal; + if (oldVal == null) + return newVal; + + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + return newv.add(oldv).toString(); + } + // otherwise we still assume 32 bits arch + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return addWithOverflowCheck(newVal, oldVal); + } + // so it's still 32 bits arch + return "" + (newv + oldv); + } + + private String divideWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { + + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + return newv.divide(oldv, BigDecimal.ROUND_FLOOR).toString(); + } + // otherwise we still assume 32 bits arch + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return divideWithOverflowCheck(newVal, oldVal); + } + // so it's still 32 bits arch + return "" + (newv / oldv); + } + + private String mulWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + return newv.multiply(oldv).toString(); + } + // otherwise we still assume 32 bits arch + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return mulWithOverflowCheck(newVal, oldVal); + } + // so it's still 32 bits arch + return "" + (newv * oldv); + } + + private String diffWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + if (newv.compareTo(oldv) >= 0) + return newv.subtract(oldv).toString(); + BigInteger overflow = new BigInteger("1").shiftLeft(64); + BigDecimal d = new BigDecimal(overflow.toString()); + return newv.add(d).subtract(oldv).toString(); + } + // otherwise we still assume 32 bits arch + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return diffWithOverflowCheck(newVal, oldVal); + } + // so it's still 32 bits arch + if (newv >= oldv) { + return "" + (newv - oldv); + } + long vmax = 1L << 32; // 32 bits + return "" + (newv - oldv + vmax); + } + + private final String prepareString(String str) { + + // first try to make it double + try { + double d = Double.parseDouble(str); + if (!Double.isInfinite(d) && !Double.isNaN(d)) { + String n = nf.format(d); + n = n.replaceAll(",", ""); + return n; + } + } catch (Throwable t) { + } + + if (!str.contains(".")) { + return str + ".0000"; + } + int nr = str.lastIndexOf('.') + 1; + nr = str.length() - nr; + for (int i = nr; i < 4; i++) + str += "0"; + return str; + } + + private final synchronized String executeIOStat() { + + String path = SYS_EXTENDED_BIN_PATH + "," + System.getProperty("user.home"); + if (System.getProperty("iostat.path", null) != null) + path = System.getProperty("iostat.path"); + if (path != null && path.length() != 0) { + path = path.replace(',', ':').trim(); + } + + CommandResult cmdRes = exec.executeCommandReality("iostat -k", "L", path); + final String output = cmdRes.getOutput(); + if (!cmdRes.failed() && output.length() != 0 && !output.contains("No such file or directory") && !output.contains("Segmentation fault")) + return output; + return null; + } + + private final synchronized String executeDF() { + + String path = SYS_EXTENDED_BIN_PATH; + if (System.getProperty("df.path", null) != null) + path = System.getProperty("df.path"); + if (path != null && path.length() != 0) { + path = path.replace(",", ":").trim(); + } + CommandResult cmdRes = exec.executeCommandReality("df -B 1024", "o", path); + String output = cmdRes.getOutput(); + if (!cmdRes.failed() && output.length() != 0 && !output.contains("No such file or directory") && !output.contains("Segmentation fault")) + return output; + return null; + } + + private String[] listFiles(String directory) { + String[] fileList = null; + try { + File dir = new File(directory); + if (!dir.isDirectory()) return null; + File[] list = dir.listFiles(); + if (list == null) return null; + fileList = new String[list.length]; + for (int i = 0; i < list.length; i++) + fileList[i] = list[i].getName(); + } catch (Exception e) { + return null; + } + return fileList; + } + + private void addNetInterface(String netInterface) { + if (netInterface == null || netInterface.equals("")) + return; + netInterface = netInterface.trim(); + if (netInterfaces == null) { + netInterfaces = new String[1]; + netInterfaces[0] = netInterface; + return; + } + for (int i = 0; i < netInterfaces.length; i++) + if (netInterface.equals(netInterfaces[i])) + return; + String[] tmpNetInterfaces = new String[netInterfaces.length + 1]; + System.arraycopy(netInterfaces, 0, tmpNetInterfaces, 0, netInterfaces.length); + tmpNetInterfaces[netInterfaces.length] = netInterface; + netInterfaces = tmpNetInterfaces; + } } // end of class ProcReader diff --git a/src/lia/util/net/copy/monitoring/lisa/cmdExec.java b/src/lia/util/net/copy/monitoring/lisa/cmdExec.java index 7c4f835..3e6ced3 100755 --- a/src/lia/util/net/copy/monitoring/lisa/cmdExec.java +++ b/src/lia/util/net/copy/monitoring/lisa/cmdExec.java @@ -13,6 +13,7 @@ /** * This class was taken more or less from MonALISA + * * @author Ciprian Dobre * @author ramiro */ @@ -20,1088 +21,1099 @@ // FIXME - UPDATE!!! this class with latest version from ML!!! public class cmdExec { - public final transient Logger logger = Logger.getLogger("monalisa.util.cmdExec"); - - public String full_cmd; - public Process pro; - String osname; - String exehome = ""; - - protected LinkedList streams = null; - protected LinkedList streamsReal = null; - - //protected boolean isError = false; - - /* These varibles are set to true when we want to destroy the streams pool */ - protected boolean stopStreams = false; - protected boolean stopStreamsReal = false; - - private static cmdExec _instance = null; - - /** - * structure for command output - */ - public static CommandResult NullCommandResult = new CommandResult(null,true); - public static class CommandResult{ - private final String output; - private final boolean failed; - public CommandResult(String output,boolean wasError) { - this.output = output; - this.failed = wasError; - } - /** - * @return Returns the output. - */ - public String getOutput() { - return output==null?"":output; - } - /** - * @return Returns the failed. - */ - public boolean failed() { - return failed; - } - - } - - private cmdExec() { - osname = System.getProperty("os.name"); - exehome = System.getProperty("user.home"); - streams = new LinkedList(); - streamsReal = new LinkedList(); - } - - public static synchronized cmdExec getInstance() { - if (_instance == null) - _instance = new cmdExec(); - return _instance; - } - - public void setCmd(String cmd) { - osname = System.getProperty("os.name"); - full_cmd = cmd; // local - } - - public BufferedReader procOutput(String cmd) { - try { - - if (osname.startsWith("Linux") || osname.startsWith("Mac")) { - pro = - Runtime.getRuntime().exec( - new String[] { "/bin/sh", "-c", cmd }); - } else if (osname.startsWith("Windows")) { - pro = Runtime.getRuntime().exec(exehome + cmd); - } - - InputStream out = pro.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(out)); - BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); - - String buffer = ""; - String ret = ""; - while((buffer = err.readLine())!= null) { - ret += buffer+"\n'"; - } - - if (ret.length() != 0){ - return null; - } - - return br; - - } catch (Exception e) { - logger.warning("FAILED to execute cmd = " + exehome + cmd); - Thread.currentThread().interrupt(); - } - - return null; - } - - public BufferedReader exeHomeOutput(String cmd) { - - try { - pro = - Runtime.getRuntime().exec( - new String[] { "/bin/sh", "-c", exehome + cmd}); - InputStream out = pro.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(out)); - - BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); - - String buffer = ""; - String ret = ""; - while((buffer = err.readLine())!= null) { - ret += buffer+"\n'"; - } - - if (ret.length() != 0){ - return null; - } - return br; - } catch (Exception e) { - logger.warning("FAILED to execute cmd = " + exehome + cmd); - Thread.currentThread().interrupt(); - } - return null; - } - - public void stopModule() { - if (this.pro != null) - this.pro.destroy(); - } - - public BufferedReader readProc(String filePath) { - try { - return new BufferedReader(new FileReader(filePath)); - } catch (Exception e) { - - return null; - } - } - public CommandResult executeCommand(String command, String expect) { - return executeCommand(command, expect, 60 * 1000); - } - - public CommandResult executeCommand(String command, String expect, long timeout) { - - StreamGobbler output = null; - StreamGobbler error = null; - boolean isError = false; - try { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - isError = true; - return null; - } - - error = getStreamGobbler(); - output = getStreamGobbler(); - - // any error message? - error.setInputStream(proc.getErrorStream()); - - // any output? - output.setInputStream(proc.getInputStream()); - - String out = ""; - - // any error??? - long startTime = System.currentTimeMillis(); - while (true) { - try { - out = error.getOutput(); - if (out!=null && out.length() != 0 && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.getOutput(); - if (out != null && out.length() != 0 && out.indexOf(expect) != -1) { - isError = false; - break; - } - } - long endTime = System.currentTimeMillis(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.length() == 0 || proc.exitValue() == 0) - out = output.getOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamGobbler(error); - addStreamGobbler(output); - - error = null; - output = null; - - return new CommandResult(out,isError); - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamGobbler(output); - output.stopIt(); - output = null; - } - isError = true; - return new CommandResult("",true); - } - } - - public CommandResult executeCommand(String command, Pattern expect) { - return executeCommand(command, expect, 60*1000); - } - - public CommandResult executeCommand(String command, Pattern expect, long timeout) { - - StreamGobbler output = null; - StreamGobbler error = null; - boolean isError = false; - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - isError = true; - return null; - } - - error = getStreamGobbler(); - output = getStreamGobbler(); - - // any error message? - error.setInputStream(proc.getErrorStream()); - - // any output? - output.setInputStream(proc.getInputStream()); - - String out = ""; - - // any error??? - long startTime = System.currentTimeMillis(); - while (true) { - try { - out = error.getOutput(); - if (out!=null && out.length() != 0 && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.getOutput(); - if (out != null && out.length() != 0) { - if (expect.matcher(out).matches()) { - isError = false; - break; - } - } - } - long endTime = System.currentTimeMillis(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.length() == 0 || proc.exitValue() == 0) - out = output.getOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamGobbler(error); - addStreamGobbler(output); - - error = null; - output = null; - - return new CommandResult(out,isError); - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamGobbler(output); - output.stopIt(); - output = null; - } - return new CommandResult("",true); - } - } - - public CommandResult executeCommand(String command, String expect, int howManyTimes) { - return executeCommand(command, expect, howManyTimes, 60*1000); - } - - public CommandResult executeCommand(String command, String expect, int howManyTimes, long timeout) { - - StreamGobbler output = null; - StreamGobbler error = null; - int nr = 0; // how many times the expect string occured - boolean isError = false; - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - proc = Runtime.getRuntime().exec(cmd); - } else { - return NullCommandResult; - } - - error = getStreamGobbler(); - output = getStreamGobbler(); - - error.setInputStream(proc.getErrorStream()); - - output.setInputStream(proc.getInputStream()); - - String out = ""; - - long startTime = System.currentTimeMillis(); - while (true) { - try { - out = error.getOutput(); - if (out!=null && out.length() != 0 && proc.exitValue() != 0) { - isError = true; - break; - } - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.getOutput(); - if (out!=null && out.length() != 0 && out.indexOf(expect) != -1) { - nr = getStringOccurences(out, expect); - if (nr >= howManyTimes) { - isError = false; - break; - } - } - } - long endTime = System.currentTimeMillis(); - if (endTime - startTime > timeout) { - isError = true; - break; - } - Thread.sleep(100); - } - - proc.destroy(); - proc.waitFor(); - - if (out.length() == 0 || proc.exitValue() == 0) - out = output.getOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamGobbler(error); - addStreamGobbler(output); - - error = null; - output = null; - - return new CommandResult(out,isError); - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamGobbler(output); - output.stopIt(); - output = null; - } - return NullCommandResult; - } - } - - protected int getStringOccurences(String text, String token) { - - if (text.indexOf(token) < 0) return 0; - int nr = 0; - String str = text; - while (str.indexOf(token) >= 0) { - str = str.substring(str.indexOf(token)+token.length()); - nr++; - } - return nr; - } - - public CommandResult executeCommandReality(String command, String expect, String path) { - return executeCommandReality(command, expect, 60*1000, path); - } - - public CommandResult executeCommandReality(String command, String expect, long timeout, String path) { - - StreamRealGobbler error = null; - StreamRealGobbler output = null; - boolean isError = false; - try { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - if (path != null && path.length() != 0) - proc = Runtime.getRuntime().exec(cmd, new String[] { "PATH="+path }); - else - proc = Runtime.getRuntime().exec(cmd); - } else { - return NullCommandResult; - } - - error = getStreamRealGobbler(timeout); - output = getStreamRealGobbler(timeout); - - // any error message? - error.setInputStream(proc.getErrorStream()); - - // any output? - output.setInputStream(proc.getInputStream()); - - String out = ""; - - // any error??? - long startTime = System.currentTimeMillis(); - boolean timeoutOccured = false; - while (true) { - try { - out = error.forceAllOutput(); - if (proc.exitValue() != 0) { - isError = true; - } - break; // also if exitValue did not throw exception than we're done running - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.forceAllOutput(); - if (out!=null && out.length() != 0 && out.indexOf(expect) != -1) { - isError = false; - proc.destroy(); - break; - } - } - long endTime = System.currentTimeMillis(); - if (endTime - startTime > timeout) { - isError = true; - timeoutOccured = true; - proc.destroy(); - break; - } - Thread.sleep(100); - } - - if (!timeoutOccured) { - proc.waitFor(); - } - else { - try { - Thread.sleep(2000); - proc.getOutputStream().close(); - proc.getInputStream().close(); - proc.getErrorStream().close(); - } catch (Exception ex) { - } - } - - if (out!=null && out.length() == 0 || proc.exitValue() == 0) { - out = output.forceAllOutput(); - } - - if (timeoutOccured) - out += "...Timeout"; - - error.stopIt(); - output.stopIt(); - - addStreamRealGobbler(error); - addStreamRealGobbler(output); - - error = null; - output = null; - - return new CommandResult(out,isError); - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamRealGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamRealGobbler(output); - output.stopIt(); - output = null; - } - return NullCommandResult; - } - } - - public void executeCommandRealityForFinish(String command, String path) { - executeCommandRealityForFinish(command, false, path); - } - - public void executeCommandRealityForFinish(String command, final boolean showOutput, String path) { - executeCommandRealityForFinish(command, showOutput, 60*60*1000, path); - } - - public boolean executeCommandRealityForFinish(String command, final boolean showOutput, long timeout, String path) { - - StreamRealGobbler error = null; - StreamRealGobbler output = null; - boolean isError = false; - try { - String osName = System.getProperty("os.name" ); - Process proc = null; - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - if (path != null && path.length() != 0) - proc = Runtime.getRuntime().exec(cmd, new String[] { "PATH="+path }); - else - proc = Runtime.getRuntime().exec(cmd); - } else { - return true; - } - - if (showOutput) { - error = getStreamRealGobbler(timeout); - output = getStreamRealGobbler(timeout); - error.setProgress(true); - output.setProgress(true); - // any error message? - error.setInputStream(proc.getErrorStream()); - // any output? - output.setInputStream(proc.getInputStream()); - error.setProgress(false); - output.setProgress(false); - } - - long startTime = System.currentTimeMillis(); - boolean timeoutOccured = false; - while (true) { - try { - if (proc.exitValue() != 0) { - isError = true; - } - break; // also if exitValue did not throw exception than we're done running - } catch (IllegalThreadStateException ex) { } - long endTime = System.currentTimeMillis(); - if (endTime - startTime > timeout) { - isError = true; - timeoutOccured = true; - proc.destroy(); - break; - } - Thread.sleep(100); - } - - if (!timeoutOccured) { - proc.waitFor(); - } - else { - try { - Thread.sleep(2000); - proc.getOutputStream().close(); - proc.getInputStream().close(); - proc.getErrorStream().close(); - } catch (Exception ex) { - } - } - - if (showOutput) { - error.setProgress(false); - output.setProgress(false); - error.stopIt(); - output.stopIt(); - addStreamRealGobbler(error); - addStreamRealGobbler(output); - error = null; - output = null; - } - - } catch (Exception e) { - e.printStackTrace(); - isError = true; - if (error != null) { - addStreamRealGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamRealGobbler(output); - output.stopIt(); - output = null; - } - - } - return isError; - } - - public CommandResult executeCommandReality(String command, String expect, int howManyTimes, String path) { - return executeCommandReality(command, expect, howManyTimes, 60*1000, path); - } - - public CommandResult executeCommandReality(String command, String expect, int howManyTimes, long timeout, String path) { - - StreamRealGobbler error = null; - StreamRealGobbler output = null; - boolean isError = false; - try - { - String osName = System.getProperty("os.name" ); - Process proc = null; - - if (osName.indexOf("Win") != -1) { - proc = Runtime.getRuntime().exec(command); - } else if (osName.indexOf("Linux") != -1) { - String[] cmd = new String[3]; - cmd[0] = "/bin/sh"; - cmd[1] = "-c"; - cmd[2] = command; - if (path != null && path.length() != 0) - proc = Runtime.getRuntime().exec(cmd, new String[] { "PATH="+path }); - else - proc = Runtime.getRuntime().exec(cmd); - } else { - return NullCommandResult; - } - - error = getStreamRealGobbler(timeout); - output = getStreamRealGobbler(timeout); - - error.setInputStream(proc.getErrorStream()); - - output.setInputStream(proc.getInputStream()); - - String out = ""; - - long startTime = System.currentTimeMillis(); - boolean timeoutOccured = false; - while (true) { - try { - out = error.forceAllOutput(); - if (out!=null && out.length() != 0 && proc.exitValue() != 0) { - isError = true; - } - break; - } catch (IllegalThreadStateException ex) { } - if (expect != null) { - out = output.forceAllOutput(); - if (out!=null && out.length() != 0 && out.indexOf(expect) != -1) { - int nr = getStringOccurences(out, expect); - if (nr >= howManyTimes) { - isError = false; - proc.destroy(); - break; - } - } - } - long endTime = System.currentTimeMillis(); - if (endTime - startTime > timeout) { - isError = true; - timeoutOccured = true; - proc.destroy(); - break; - } - Thread.sleep(100); - } - - if (!timeoutOccured) { - proc.waitFor(); - } else { - try { - Thread.sleep(2000); - proc.getOutputStream().close(); - proc.getInputStream().close(); - proc.getErrorStream().close(); - } catch (Exception ex) { } - } - - if (out!=null && out.length() == 0 || proc.exitValue() == 0) - out = output.forceAllOutput(); - - error.stopIt(); - output.stopIt(); - - addStreamRealGobbler(error); - addStreamRealGobbler(output); - - error = null; - output = null; - return new CommandResult(out,isError); - - } catch (Exception e) { - e.printStackTrace(); - - if (error != null) { - addStreamRealGobbler(error); - error.stopIt(); - error = null; - } - - if (output != null) { - addStreamRealGobbler(output); - output.stopIt(); - output = null; - } - return NullCommandResult; - } - } - - public StreamGobbler getStreamGobbler() { - - synchronized (streams) { - if (streams.size() == 0) { - StreamGobbler stream = new StreamGobbler(null); - stream.start(); - return stream; - } - return (StreamGobbler)streams.removeFirst(); - } - } - - public void addStreamGobbler(StreamGobbler stream) { - - synchronized (streams) { - if (!stopStreams) - streams.addLast(stream); - else - stream.stopItForever(); - } - } - - public StreamRealGobbler getStreamRealGobbler(long timeout) { - - synchronized (streamsReal) { - if (streamsReal.size() == 0) { - StreamRealGobbler stream = new StreamRealGobbler(null, timeout); - stream.start(); - return stream; - } - StreamRealGobbler st = (StreamRealGobbler)streamsReal.removeFirst(); - st.setTimeout(timeout); - return st; - } - } - - public void addStreamRealGobbler(StreamRealGobbler stream) { - - synchronized (streamsReal) { - if (!stopStreamsReal) - streamsReal.addLast(stream); - else - stream.stopItForever(); - } - } - - public void stopIt() { - synchronized(streams) { - stopStreams = true; - - while (streams.size() > 0) { - StreamGobbler sg = (StreamGobbler)(streams.removeFirst()); - sg.stopItForever(); - } - } - synchronized(streamsReal) { - stopStreamsReal = true; - - while (streamsReal.size() > 0) { - StreamRealGobbler sg = (StreamRealGobbler)(streamsReal.removeFirst()); - sg.stopItForever(); - } - } - } - - class StreamGobbler extends Thread { - - InputStream is; - StringBuilder output; - boolean stop = false; - boolean stopForever = false; - boolean doneReading = false; - - public StreamGobbler(InputStream is) { - - super("Stream Gobler"); - this.is = is; - this.output = new StringBuilder(); - this.setDaemon(true); - } - - public void setInputStream(InputStream is) { - - this.is = is; - output = new StringBuilder(); - stop = false; - synchronized (this) { - doneReading = false; - notify(); - } - } - - public String getOutput() { - return output==null?null:output.toString(); - } - - public synchronized String forceAllOutput() { - - if (!doneReading) - return null; - doneReading = false; - return output==null?null:output.toString(); - } - - public void stopIt() { - - stop = true; - } - - public void stopItForever() { - synchronized(this) { - stopForever = true; - notify(); - } - } - - public void run() { - - while (true) { - - synchronized (this) { - while (is == null && !stopForever) { - try { - wait(); - } catch (Exception e) { } - } - } - - if (stopForever) { - break; - } - - try { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - String line=null; - while (!stop && (line = br.readLine()) != null) { - output.append(line); - } - synchronized (this) { - doneReading = true; - } - is.close(); - } catch (Exception ioe) { - output = new StringBuilder(); - } - is = null; - } - } - } - - class StreamRealGobbler extends Thread { - - InputStream is; - InputStreamReader isr; - final char[] buf; - StringBuilder output = new StringBuilder(); - boolean stop = false; - boolean doneReading = false; - boolean stopForever = false; - - private Thread thread = null; - private boolean showProgress = false; - - private long timeout; - - public StreamRealGobbler(InputStream is, long timeout) { - - super("Stream Real Gobler"); - this.timeout = timeout; - this.is = is; - this.setDaemon(true); - buf = new char[32]; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - public void setInputStream(InputStream is) { - - this.is = is; - output = new StringBuilder(); - stop = false; - synchronized (buf) { - doneReading = false; - buf.notifyAll(); - } - } - - public void setProgress(boolean showProgress) { - this.showProgress = showProgress; - } - - public String getOutput() { - - return output==null?null:output.toString(); - } - - public String forceAllOutput() { - - if (!doneReading) { - try { - if (!isr.ready()) { - return null; - } - } catch (Exception ex) { } - - try { - thread.interrupt(); // force the thread out of sleep - } catch (Exception ex) { } - synchronized (buf) { - buf.notifyAll(); - } - // otherwise let's give the output a chance to complete - long start = System.currentTimeMillis(); - while (!doneReading) { - try { - Thread.sleep(200); - } catch (Exception ex) { } - if (doneReading) { - return output==null?null:output.toString(); - } - long now = System.currentTimeMillis(); - if ((now - start) >= timeout) { - return null; // last chance - } - } - } - return output==null?null:output.toString(); - } - - public void stopIt() { - try { - is.close(); - } catch (Exception ex) { } - try { - isr.close(); - } catch (Exception ex) { } - stop = true; - } - - public void stopItForever() { - synchronized(buf) { - stopIt(); - stopForever = true; - buf.notifyAll(); - } - } - - public void run() { - - thread = Thread.currentThread(); - - while (true) { - - synchronized (buf) { - while (is == null && !stopForever) { - try { - buf.wait(); - } catch (Exception e) { } - } - } - - if (stopForever) { - synchronized (buf) { - doneReading = true; - } - break; - } - - try { - isr = new InputStreamReader(is); - while (!stop) { - try { - final int ret = isr.read(buf); - if (ret > 0) { - final String nstr = new String(buf, 0, ret); - if (showProgress) { - logger.info(nstr); - } - output.append(nstr); - } else { - break; // and of stream - } - } catch (Exception ex) { - break; - } - } - - synchronized (buf) { - doneReading = true; - } - } catch (Exception ioe) { - output = new StringBuilder(); - } - try { - is.close(); - } catch (Exception ex) { } - is = null; - } - } - } - + /** + * structure for command output + */ + public static CommandResult NullCommandResult = new CommandResult(null, true); + private static cmdExec _instance = null; + public final transient Logger logger = Logger.getLogger("monalisa.util.cmdExec"); + public String full_cmd; + public Process pro; + protected LinkedList streams = null; + protected LinkedList streamsReal = null; + + //protected boolean isError = false; + + /* These varibles are set to true when we want to destroy the streams pool */ + protected boolean stopStreams = false; + protected boolean stopStreamsReal = false; + String osname; + String exehome = ""; + + private cmdExec() { + osname = System.getProperty("os.name"); + exehome = System.getProperty("user.home"); + streams = new LinkedList(); + streamsReal = new LinkedList(); + } + + public static synchronized cmdExec getInstance() { + if (_instance == null) + _instance = new cmdExec(); + return _instance; + } + + public void setCmd(String cmd) { + osname = System.getProperty("os.name"); + full_cmd = cmd; // local + } + + public BufferedReader procOutput(String cmd) { + try { + + if (osname.startsWith("Linux") || osname.startsWith("Mac")) { + pro = + Runtime.getRuntime().exec( + new String[]{"/bin/sh", "-c", cmd}); + } else if (osname.startsWith("Windows")) { + pro = Runtime.getRuntime().exec(exehome + cmd); + } + + InputStream out = pro.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(out)); + BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); + + String buffer = ""; + String ret = ""; + while ((buffer = err.readLine()) != null) { + ret += buffer + "\n'"; + } + + if (ret.length() != 0) { + return null; + } + + return br; + + } catch (Exception e) { + logger.warning("FAILED to execute cmd = " + exehome + cmd); + Thread.currentThread().interrupt(); + } + + return null; + } + + public BufferedReader exeHomeOutput(String cmd) { + + try { + pro = + Runtime.getRuntime().exec( + new String[]{"/bin/sh", "-c", exehome + cmd}); + InputStream out = pro.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(out)); + + BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream())); + + String buffer = ""; + String ret = ""; + while ((buffer = err.readLine()) != null) { + ret += buffer + "\n'"; + } + + if (ret.length() != 0) { + return null; + } + return br; + } catch (Exception e) { + logger.warning("FAILED to execute cmd = " + exehome + cmd); + Thread.currentThread().interrupt(); + } + return null; + } + + public void stopModule() { + if (this.pro != null) + this.pro.destroy(); + } + + public BufferedReader readProc(String filePath) { + try { + return new BufferedReader(new FileReader(filePath)); + } catch (Exception e) { + + return null; + } + } + + public CommandResult executeCommand(String command, String expect) { + return executeCommand(command, expect, 60 * 1000); + } + + public CommandResult executeCommand(String command, String expect, long timeout) { + + StreamGobbler output = null; + StreamGobbler error = null; + boolean isError = false; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + isError = true; + return null; + } + + error = getStreamGobbler(); + output = getStreamGobbler(); + + // any error message? + error.setInputStream(proc.getErrorStream()); + + // any output? + output.setInputStream(proc.getInputStream()); + + String out = ""; + + // any error??? + long startTime = System.currentTimeMillis(); + while (true) { + try { + out = error.getOutput(); + if (out != null && out.length() != 0 && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.getOutput(); + if (out != null && out.length() != 0 && out.indexOf(expect) != -1) { + isError = false; + break; + } + } + long endTime = System.currentTimeMillis(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.length() == 0 || proc.exitValue() == 0) + out = output.getOutput(); + + error.stopIt(); + output.stopIt(); + + addStreamGobbler(error); + addStreamGobbler(output); + + error = null; + output = null; + + return new CommandResult(out, isError); + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamGobbler(output); + output.stopIt(); + output = null; + } + isError = true; + return new CommandResult("", true); + } + } + + public CommandResult executeCommand(String command, Pattern expect) { + return executeCommand(command, expect, 60 * 1000); + } + + public CommandResult executeCommand(String command, Pattern expect, long timeout) { + + StreamGobbler output = null; + StreamGobbler error = null; + boolean isError = false; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + isError = true; + return null; + } + + error = getStreamGobbler(); + output = getStreamGobbler(); + + // any error message? + error.setInputStream(proc.getErrorStream()); + + // any output? + output.setInputStream(proc.getInputStream()); + + String out = ""; + + // any error??? + long startTime = System.currentTimeMillis(); + while (true) { + try { + out = error.getOutput(); + if (out != null && out.length() != 0 && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.getOutput(); + if (out != null && out.length() != 0) { + if (expect.matcher(out).matches()) { + isError = false; + break; + } + } + } + long endTime = System.currentTimeMillis(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.length() == 0 || proc.exitValue() == 0) + out = output.getOutput(); + + error.stopIt(); + output.stopIt(); + + addStreamGobbler(error); + addStreamGobbler(output); + + error = null; + output = null; + + return new CommandResult(out, isError); + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamGobbler(output); + output.stopIt(); + output = null; + } + return new CommandResult("", true); + } + } + + public CommandResult executeCommand(String command, String expect, int howManyTimes) { + return executeCommand(command, expect, howManyTimes, 60 * 1000); + } + + public CommandResult executeCommand(String command, String expect, int howManyTimes, long timeout) { + + StreamGobbler output = null; + StreamGobbler error = null; + int nr = 0; // how many times the expect string occured + boolean isError = false; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1 || osName.indexOf("Mac") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + proc = Runtime.getRuntime().exec(cmd); + } else { + return NullCommandResult; + } + + error = getStreamGobbler(); + output = getStreamGobbler(); + + error.setInputStream(proc.getErrorStream()); + + output.setInputStream(proc.getInputStream()); + + String out = ""; + + long startTime = System.currentTimeMillis(); + while (true) { + try { + out = error.getOutput(); + if (out != null && out.length() != 0 && proc.exitValue() != 0) { + isError = true; + break; + } + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.getOutput(); + if (out != null && out.length() != 0 && out.indexOf(expect) != -1) { + nr = getStringOccurences(out, expect); + if (nr >= howManyTimes) { + isError = false; + break; + } + } + } + long endTime = System.currentTimeMillis(); + if (endTime - startTime > timeout) { + isError = true; + break; + } + Thread.sleep(100); + } + + proc.destroy(); + proc.waitFor(); + + if (out.length() == 0 || proc.exitValue() == 0) + out = output.getOutput(); + + error.stopIt(); + output.stopIt(); + + addStreamGobbler(error); + addStreamGobbler(output); + + error = null; + output = null; + + return new CommandResult(out, isError); + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamGobbler(output); + output.stopIt(); + output = null; + } + return NullCommandResult; + } + } + + protected int getStringOccurences(String text, String token) { + + if (text.indexOf(token) < 0) return 0; + int nr = 0; + String str = text; + while (str.indexOf(token) >= 0) { + str = str.substring(str.indexOf(token) + token.length()); + nr++; + } + return nr; + } + + public CommandResult executeCommandReality(String command, String expect, String path) { + return executeCommandReality(command, expect, 60 * 1000, path); + } + + public CommandResult executeCommandReality(String command, String expect, long timeout, String path) { + + StreamRealGobbler error = null; + StreamRealGobbler output = null; + boolean isError = false; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + if (path != null && path.length() != 0) + proc = Runtime.getRuntime().exec(cmd, new String[]{"PATH=" + path}); + else + proc = Runtime.getRuntime().exec(cmd); + } else { + return NullCommandResult; + } + + error = getStreamRealGobbler(timeout); + output = getStreamRealGobbler(timeout); + + // any error message? + error.setInputStream(proc.getErrorStream()); + + // any output? + output.setInputStream(proc.getInputStream()); + + String out = ""; + + // any error??? + long startTime = System.currentTimeMillis(); + boolean timeoutOccured = false; + while (true) { + try { + out = error.forceAllOutput(); + if (proc.exitValue() != 0) { + isError = true; + } + break; // also if exitValue did not throw exception than we're done running + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.forceAllOutput(); + if (out != null && out.length() != 0 && out.indexOf(expect) != -1) { + isError = false; + proc.destroy(); + break; + } + } + long endTime = System.currentTimeMillis(); + if (endTime - startTime > timeout) { + isError = true; + timeoutOccured = true; + proc.destroy(); + break; + } + Thread.sleep(100); + } + + if (!timeoutOccured) { + proc.waitFor(); + } else { + try { + Thread.sleep(2000); + proc.getOutputStream().close(); + proc.getInputStream().close(); + proc.getErrorStream().close(); + } catch (Exception ex) { + } + } + + if (out != null && out.length() == 0 || proc.exitValue() == 0) { + out = output.forceAllOutput(); + } + + if (timeoutOccured) + out += "...Timeout"; + + error.stopIt(); + output.stopIt(); + + addStreamRealGobbler(error); + addStreamRealGobbler(output); + + error = null; + output = null; + + return new CommandResult(out, isError); + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamRealGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamRealGobbler(output); + output.stopIt(); + output = null; + } + return NullCommandResult; + } + } + + public void executeCommandRealityForFinish(String command, String path) { + executeCommandRealityForFinish(command, false, path); + } + + public void executeCommandRealityForFinish(String command, final boolean showOutput, String path) { + executeCommandRealityForFinish(command, showOutput, 60 * 60 * 1000, path); + } + + public boolean executeCommandRealityForFinish(String command, final boolean showOutput, long timeout, String path) { + + StreamRealGobbler error = null; + StreamRealGobbler output = null; + boolean isError = false; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + if (path != null && path.length() != 0) + proc = Runtime.getRuntime().exec(cmd, new String[]{"PATH=" + path}); + else + proc = Runtime.getRuntime().exec(cmd); + } else { + return true; + } + + if (showOutput) { + error = getStreamRealGobbler(timeout); + output = getStreamRealGobbler(timeout); + error.setProgress(true); + output.setProgress(true); + // any error message? + error.setInputStream(proc.getErrorStream()); + // any output? + output.setInputStream(proc.getInputStream()); + error.setProgress(false); + output.setProgress(false); + } + + long startTime = System.currentTimeMillis(); + boolean timeoutOccured = false; + while (true) { + try { + if (proc.exitValue() != 0) { + isError = true; + } + break; // also if exitValue did not throw exception than we're done running + } catch (IllegalThreadStateException ex) { + } + long endTime = System.currentTimeMillis(); + if (endTime - startTime > timeout) { + isError = true; + timeoutOccured = true; + proc.destroy(); + break; + } + Thread.sleep(100); + } + + if (!timeoutOccured) { + proc.waitFor(); + } else { + try { + Thread.sleep(2000); + proc.getOutputStream().close(); + proc.getInputStream().close(); + proc.getErrorStream().close(); + } catch (Exception ex) { + } + } + + if (showOutput) { + error.setProgress(false); + output.setProgress(false); + error.stopIt(); + output.stopIt(); + addStreamRealGobbler(error); + addStreamRealGobbler(output); + error = null; + output = null; + } + + } catch (Exception e) { + e.printStackTrace(); + isError = true; + if (error != null) { + addStreamRealGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamRealGobbler(output); + output.stopIt(); + output = null; + } + + } + return isError; + } + + public CommandResult executeCommandReality(String command, String expect, int howManyTimes, String path) { + return executeCommandReality(command, expect, howManyTimes, 60 * 1000, path); + } + + public CommandResult executeCommandReality(String command, String expect, int howManyTimes, long timeout, String path) { + + StreamRealGobbler error = null; + StreamRealGobbler output = null; + boolean isError = false; + try { + String osName = System.getProperty("os.name"); + Process proc = null; + + if (osName.indexOf("Win") != -1) { + proc = Runtime.getRuntime().exec(command); + } else if (osName.indexOf("Linux") != -1) { + String[] cmd = new String[3]; + cmd[0] = "/bin/sh"; + cmd[1] = "-c"; + cmd[2] = command; + if (path != null && path.length() != 0) + proc = Runtime.getRuntime().exec(cmd, new String[]{"PATH=" + path}); + else + proc = Runtime.getRuntime().exec(cmd); + } else { + return NullCommandResult; + } + + error = getStreamRealGobbler(timeout); + output = getStreamRealGobbler(timeout); + + error.setInputStream(proc.getErrorStream()); + + output.setInputStream(proc.getInputStream()); + + String out = ""; + + long startTime = System.currentTimeMillis(); + boolean timeoutOccured = false; + while (true) { + try { + out = error.forceAllOutput(); + if (out != null && out.length() != 0 && proc.exitValue() != 0) { + isError = true; + } + break; + } catch (IllegalThreadStateException ex) { + } + if (expect != null) { + out = output.forceAllOutput(); + if (out != null && out.length() != 0 && out.indexOf(expect) != -1) { + int nr = getStringOccurences(out, expect); + if (nr >= howManyTimes) { + isError = false; + proc.destroy(); + break; + } + } + } + long endTime = System.currentTimeMillis(); + if (endTime - startTime > timeout) { + isError = true; + timeoutOccured = true; + proc.destroy(); + break; + } + Thread.sleep(100); + } + + if (!timeoutOccured) { + proc.waitFor(); + } else { + try { + Thread.sleep(2000); + proc.getOutputStream().close(); + proc.getInputStream().close(); + proc.getErrorStream().close(); + } catch (Exception ex) { + } + } + + if (out != null && out.length() == 0 || proc.exitValue() == 0) + out = output.forceAllOutput(); + + error.stopIt(); + output.stopIt(); + + addStreamRealGobbler(error); + addStreamRealGobbler(output); + + error = null; + output = null; + return new CommandResult(out, isError); + + } catch (Exception e) { + e.printStackTrace(); + + if (error != null) { + addStreamRealGobbler(error); + error.stopIt(); + error = null; + } + + if (output != null) { + addStreamRealGobbler(output); + output.stopIt(); + output = null; + } + return NullCommandResult; + } + } + + public StreamGobbler getStreamGobbler() { + + synchronized (streams) { + if (streams.size() == 0) { + StreamGobbler stream = new StreamGobbler(null); + stream.start(); + return stream; + } + return (StreamGobbler) streams.removeFirst(); + } + } + + public void addStreamGobbler(StreamGobbler stream) { + + synchronized (streams) { + if (!stopStreams) + streams.addLast(stream); + else + stream.stopItForever(); + } + } + + public StreamRealGobbler getStreamRealGobbler(long timeout) { + + synchronized (streamsReal) { + if (streamsReal.size() == 0) { + StreamRealGobbler stream = new StreamRealGobbler(null, timeout); + stream.start(); + return stream; + } + StreamRealGobbler st = (StreamRealGobbler) streamsReal.removeFirst(); + st.setTimeout(timeout); + return st; + } + } + + public void addStreamRealGobbler(StreamRealGobbler stream) { + + synchronized (streamsReal) { + if (!stopStreamsReal) + streamsReal.addLast(stream); + else + stream.stopItForever(); + } + } + + public void stopIt() { + synchronized (streams) { + stopStreams = true; + + while (streams.size() > 0) { + StreamGobbler sg = (StreamGobbler) (streams.removeFirst()); + sg.stopItForever(); + } + } + synchronized (streamsReal) { + stopStreamsReal = true; + + while (streamsReal.size() > 0) { + StreamRealGobbler sg = (StreamRealGobbler) (streamsReal.removeFirst()); + sg.stopItForever(); + } + } + } + + public static class CommandResult { + private final String output; + private final boolean failed; + + public CommandResult(String output, boolean wasError) { + this.output = output; + this.failed = wasError; + } + + /** + * @return Returns the output. + */ + public String getOutput() { + return output == null ? "" : output; + } + + /** + * @return Returns the failed. + */ + public boolean failed() { + return failed; + } + + } + + class StreamGobbler extends Thread { + + InputStream is; + StringBuilder output; + boolean stop = false; + boolean stopForever = false; + boolean doneReading = false; + + public StreamGobbler(InputStream is) { + + super("Stream Gobler"); + this.is = is; + this.output = new StringBuilder(); + this.setDaemon(true); + } + + public void setInputStream(InputStream is) { + + this.is = is; + output = new StringBuilder(); + stop = false; + synchronized (this) { + doneReading = false; + notify(); + } + } + + public String getOutput() { + return output == null ? null : output.toString(); + } + + public synchronized String forceAllOutput() { + + if (!doneReading) + return null; + doneReading = false; + return output == null ? null : output.toString(); + } + + public void stopIt() { + + stop = true; + } + + public void stopItForever() { + synchronized (this) { + stopForever = true; + notify(); + } + } + + public void run() { + + while (true) { + + synchronized (this) { + while (is == null && !stopForever) { + try { + wait(); + } catch (Exception e) { + } + } + } + + if (stopForever) { + break; + } + + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line = null; + while (!stop && (line = br.readLine()) != null) { + output.append(line); + } + synchronized (this) { + doneReading = true; + } + is.close(); + } catch (Exception ioe) { + output = new StringBuilder(); + } + is = null; + } + } + } + + class StreamRealGobbler extends Thread { + + final char[] buf; + InputStream is; + InputStreamReader isr; + StringBuilder output = new StringBuilder(); + boolean stop = false; + boolean doneReading = false; + boolean stopForever = false; + + private Thread thread = null; + private boolean showProgress = false; + + private long timeout; + + public StreamRealGobbler(InputStream is, long timeout) { + + super("Stream Real Gobler"); + this.timeout = timeout; + this.is = is; + this.setDaemon(true); + buf = new char[32]; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public void setInputStream(InputStream is) { + + this.is = is; + output = new StringBuilder(); + stop = false; + synchronized (buf) { + doneReading = false; + buf.notifyAll(); + } + } + + public void setProgress(boolean showProgress) { + this.showProgress = showProgress; + } + + public String getOutput() { + + return output == null ? null : output.toString(); + } + + public String forceAllOutput() { + + if (!doneReading) { + try { + if (!isr.ready()) { + return null; + } + } catch (Exception ex) { + } + + try { + thread.interrupt(); // force the thread out of sleep + } catch (Exception ex) { + } + synchronized (buf) { + buf.notifyAll(); + } + // otherwise let's give the output a chance to complete + long start = System.currentTimeMillis(); + while (!doneReading) { + try { + Thread.sleep(200); + } catch (Exception ex) { + } + if (doneReading) { + return output == null ? null : output.toString(); + } + long now = System.currentTimeMillis(); + if ((now - start) >= timeout) { + return null; // last chance + } + } + } + return output == null ? null : output.toString(); + } + + public void stopIt() { + try { + is.close(); + } catch (Exception ex) { + } + try { + isr.close(); + } catch (Exception ex) { + } + stop = true; + } + + public void stopItForever() { + synchronized (buf) { + stopIt(); + stopForever = true; + buf.notifyAll(); + } + } + + public void run() { + + thread = Thread.currentThread(); + + while (true) { + + synchronized (buf) { + while (is == null && !stopForever) { + try { + buf.wait(); + } catch (Exception e) { + } + } + } + + if (stopForever) { + synchronized (buf) { + doneReading = true; + } + break; + } + + try { + isr = new InputStreamReader(is); + while (!stop) { + try { + final int ret = isr.read(buf); + if (ret > 0) { + final String nstr = new String(buf, 0, ret); + if (showProgress) { + logger.info(nstr); + } + output.append(nstr); + } else { + break; // and of stream + } + } catch (Exception ex) { + break; + } + } + + synchronized (buf) { + doneReading = true; + } + } catch (Exception ioe) { + output = new StringBuilder(); + } + try { + is.close(); + } catch (Exception ex) { + } + is = null; + } + } + } + } diff --git a/src/lia/util/net/copy/monitoring/lisa/net/PatternUtil.java b/src/lia/util/net/copy/monitoring/lisa/net/PatternUtil.java index 9d61698..0695bd5 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/PatternUtil.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/PatternUtil.java @@ -6,56 +6,64 @@ import java.util.HashMap; import java.util.regex.Pattern; -/** This module relies heavily on Pattern matching (it's safer that way), but also Pattern is a memory +/** + * This module relies heavily on Pattern matching (it's safer that way), but also Pattern is a memory * consumer, so please use this class in order to retrieve a given Pattern. The main purpose of this * is to instantiate a pattern once in memory. - * + * * @author Ciprian Dobre */ public class PatternUtil { - /** The mapping keys vs Patterns */ - protected final HashMap patterns = new HashMap(); - - /** We only want to instantiante this class once, if necessary */ - protected static PatternUtil _p; - - /** - * Call this method in order to retrieve the Pattern associated with a given key. - * @param key The key to identify the Pattern - * @param pattern The pattern to use if instantiation required - * @return The pattern - */ - public static synchronized Pattern getPattern(final String key, final String pattern) { - return getPattern(key, pattern, false); - } - - /** - * Call this method in order to retrieve the Pattern associated with a given key. - * @param key The key to identify the Pattern - * @param pattern The pattern to use if instantiation required - * @param takeEOL Should EndOfLine character be taken into consideration ? - * @return The pattern - */ - public static synchronized Pattern getPattern(final String key, final String pattern, final boolean takeEOL) { - - if (key == null) return null; // for null key return a null pattern - if (_p == null) { - _p = new PatternUtil(); - _p.patterns.put("Unknown command", _p.getNoSuchCommand()); - } - if (!_p.patterns.containsKey(key)) { - final Pattern p = takeEOL ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL) : Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - _p.patterns.put(key, p); - return p; - } - return _p.patterns.get(key); - } - - /** A high used pattern is the one used for recognizing of no such file pattern */ - protected Pattern getNoSuchCommand() { - return Pattern.compile("([No such file or directory|Operation not permitted|bad command line argument])+", Pattern.CASE_INSENSITIVE); - } - + /** + * We only want to instantiante this class once, if necessary + */ + protected static PatternUtil _p; + /** + * The mapping keys vs Patterns + */ + protected final HashMap patterns = new HashMap(); + + /** + * Call this method in order to retrieve the Pattern associated with a given key. + * + * @param key The key to identify the Pattern + * @param pattern The pattern to use if instantiation required + * @return The pattern + */ + public static synchronized Pattern getPattern(final String key, final String pattern) { + return getPattern(key, pattern, false); + } + + /** + * Call this method in order to retrieve the Pattern associated with a given key. + * + * @param key The key to identify the Pattern + * @param pattern The pattern to use if instantiation required + * @param takeEOL Should EndOfLine character be taken into consideration ? + * @return The pattern + */ + public static synchronized Pattern getPattern(final String key, final String pattern, final boolean takeEOL) { + + if (key == null) return null; // for null key return a null pattern + if (_p == null) { + _p = new PatternUtil(); + _p.patterns.put("Unknown command", _p.getNoSuchCommand()); + } + if (!_p.patterns.containsKey(key)) { + final Pattern p = takeEOL ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL) : Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + _p.patterns.put(key, p); + return p; + } + return _p.patterns.get(key); + } + + /** + * A high used pattern is the one used for recognizing of no such file pattern + */ + protected Pattern getNoSuchCommand() { + return Pattern.compile("([No such file or directory|Operation not permitted|bad command line argument])+", Pattern.CASE_INSENSITIVE); + } + } // end of class PatternUtil diff --git a/src/lia/util/net/copy/monitoring/lisa/net/Statistics.java b/src/lia/util/net/copy/monitoring/lisa/net/Statistics.java index 07e29fb..7514479 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/Statistics.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/Statistics.java @@ -7,30 +7,32 @@ /** * Super class of all networking statistics objects - * + * * @author Ciprian Dobre */ public class Statistics implements Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - - /** The time the statistics were generated */ - protected long time; - - public Statistics() { - time = System.currentTimeMillis(); - } - - public void updateTime() { - time = System.currentTimeMillis(); - } - - public final long getTime() { - return time; - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + + /** + * The time the statistics were generated + */ + protected long time; + + public Statistics() { + time = System.currentTimeMillis(); + } + + public void updateTime() { + time = System.currentTimeMillis(); + } + + public final long getTime() { + return time; + } + } // end of class Statistics diff --git a/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceHandler.java b/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceHandler.java index 28963ef..4a03898 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceHandler.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceHandler.java @@ -3,1042 +3,1047 @@ */ package lia.util.net.copy.monitoring.lisa.net.dev; +import lia.util.net.copy.monitoring.lisa.cmdExec; +import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; +import lia.util.net.copy.monitoring.lisa.net.PatternUtil; + import java.io.BufferedReader; import java.io.FileReader; import java.io.PrintStream; import java.math.BigDecimal; import java.math.BigInteger; import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lia.util.net.copy.monitoring.lisa.cmdExec; -import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; -import lia.util.net.copy.monitoring.lisa.net.PatternUtil; - /** * This class constructs a list containing all the network interfaces available, together with their properties - * + * * @author Ciprian Dobre */ public class InterfaceHandler { - /** Utility object used for running different commands */ - protected final cmdExec exec; - - /** The stream to write output to */ - protected final PrintStream out; - - protected final Hashtable lastRXPackets = new Hashtable(); - protected final Hashtable lastRXErrors = new Hashtable(); - protected final Hashtable lastRXDropped = new Hashtable(); - protected final Hashtable lastRXOverruns = new Hashtable(); - protected final Hashtable lastRXFrame = new Hashtable(); - protected final Hashtable lastRXBytes = new Hashtable(); + /** + * Different patterns used for output parsing + **/ + + protected static final String ifNamePattern = "^(\\S+)\\s+"; + protected static final String hwAddrPattern = "HWaddr\\s+(\\S+)"; + protected static final String ipv4Pattern = "inet addr:(\\S+)"; + protected static final String mask4Pattern = "Mask:(\\S+)"; + protected static final String bcast4Pattern = "Bcast:(\\S+)"; + protected static final String mtuPattern = "MTU:(\\S+)"; + protected static final String rxPackets = "RX[\\s\\S]+packets:(\\S+)"; + protected static final String rxErrors = "RX[\\s\\S]+errors:(\\S+)"; + protected static final String rxDropped = "RX[\\s\\S]+dropped:(\\S+)"; + protected static final String rxOverruns = "RX[\\s\\S]+overruns:(\\S+)"; + protected static final String rxFrame = "RX[\\s\\S]+frame:(\\S+)"; + protected static final String rxBytes = "RX\\s+bytes:(\\S+)"; + protected static final String txPackets = "TX[\\s\\S]+packets:(\\S+)"; + protected static final String txErrors = "TX[\\s\\S]+errors:(\\S+)"; + protected static final String txDropped = "TX[\\s\\S]+dropped:(\\S+)"; + protected static final String txOverruns = "TX[\\s\\S]+overruns:(\\S+)"; + protected static final String txCarrier = "TX[\\s\\S]+carrier:(\\S+)"; + protected static final String txBytes = "TX\\s+bytes:(\\S+)"; + protected static final String collisions = "\\s+collisions:(\\S+)"; + protected static final String compressed = "\\s+compressed:(\\S+)"; + protected static final String txqueuelen = "\\s+txqueuelen:(\\S+)"; + protected static final String netDevPattern = "(\\S+):\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+"; + protected static final String supportedPortsPattern = "Supported ports:\\s*\\[(.*)\\]"; + protected static final String supportedLinkModes = "Supported link modes:\\s*(.*?)Supports"; + protected static final String supportsAutoNegotiation = "Supports auto-negotiation:\\s*(\\S+)"; + protected static final String speed = "Speed:\\s*(.*)"; + protected static final String duplex = "Duplex:\\s*(.*)"; + protected static final String port = "Port:\\s*(.*)"; + final static NumberFormat nf = NumberFormat.getInstance(); - protected final Hashtable lastTXPackets = new Hashtable(); - protected final Hashtable lastTXErrors = new Hashtable(); - protected final Hashtable lastTXDropped = new Hashtable(); - protected final Hashtable lastTXOverruns = new Hashtable(); - protected final Hashtable lastTXCarrier = new Hashtable(); - protected final Hashtable lastTXBytes = new Hashtable(); - - protected final Hashtable lastCollisions = new Hashtable(); - protected final Hashtable lastCompressed = new Hashtable(); + static { + nf.setMaximumFractionDigits(4); + nf.setMinimumFractionDigits(4); + } - protected long lastCall = 0; - - /** The interfaces of the system toghether with the monitored properties */ - protected final HashMap ifs = new HashMap(); + /** + * Utility object used for running different commands + */ + protected final cmdExec exec; + /** + * The stream to write output to + */ + protected final PrintStream out; + protected final Hashtable lastRXPackets = new Hashtable(); + protected final Hashtable lastRXErrors = new Hashtable(); + protected final Hashtable lastRXDropped = new Hashtable(); + protected final Hashtable lastRXOverruns = new Hashtable(); + protected final Hashtable lastRXFrame = new Hashtable(); + protected final Hashtable lastRXBytes = new Hashtable(); + protected final Hashtable lastTXPackets = new Hashtable(); + protected final Hashtable lastTXErrors = new Hashtable(); + protected final Hashtable lastTXDropped = new Hashtable(); + protected final Hashtable lastTXOverruns = new Hashtable(); + protected final Hashtable lastTXCarrier = new Hashtable(); + protected final Hashtable lastTXBytes = new Hashtable(); + protected final Hashtable lastCollisions = new Hashtable(); + protected final Hashtable lastCompressed = new Hashtable(); + /** + * The interfaces of the system toghether with the monitored properties + */ + protected final HashMap ifs = new HashMap(); + protected final HashMap lastGoodStatic = new HashMap(); + protected final HashMap staticifs = new HashMap(); + protected final Logger logger; + /** + * some temporary variables ... we only want them to be instantiated once because of the latency involved + */ + final List ifArray = new ArrayList(); + final List toRemove = new ArrayList(); + protected long lastCall = 0; + /** + * is this a 64 bits arch ? + */ + private boolean is64BitArch = false; - protected final HashMap lastGoodStatic = new HashMap(); + /** + * The constructor + * + * @param out The stream to write output to + */ + public InterfaceHandler(final PrintStream out, final Logger logger) { + this.logger = logger; + this.out = out; + exec = cmdExec.getInstance(); + } - protected final HashMap staticifs = new HashMap(); - - /** Different patterns used for output parsing **/ - - protected static final String ifNamePattern = "^(\\S+)\\s+"; - protected static final String hwAddrPattern = "HWaddr\\s+(\\S+)"; - protected static final String ipv4Pattern = "inet addr:(\\S+)"; - protected static final String mask4Pattern = "Mask:(\\S+)"; - protected static final String bcast4Pattern = "Bcast:(\\S+)"; - protected static final String mtuPattern = "MTU:(\\S+)"; - - protected static final String rxPackets = "RX[\\s\\S]+packets:(\\S+)"; - protected static final String rxErrors = "RX[\\s\\S]+errors:(\\S+)"; - protected static final String rxDropped = "RX[\\s\\S]+dropped:(\\S+)"; - protected static final String rxOverruns = "RX[\\s\\S]+overruns:(\\S+)"; - protected static final String rxFrame = "RX[\\s\\S]+frame:(\\S+)"; - protected static final String rxBytes = "RX\\s+bytes:(\\S+)"; + public static void main(String args[]) { - protected static final String txPackets = "TX[\\s\\S]+packets:(\\S+)"; - protected static final String txErrors = "TX[\\s\\S]+errors:(\\S+)"; - protected static final String txDropped = "TX[\\s\\S]+dropped:(\\S+)"; - protected static final String txOverruns = "TX[\\s\\S]+overruns:(\\S+)"; - protected static final String txCarrier = "TX[\\s\\S]+carrier:(\\S+)"; - protected static final String txBytes = "TX\\s+bytes:(\\S+)"; + String line = "Speed: Unknown (100000)\nTest\n"; + Pattern pattern = PatternUtil.getPattern("supportedLinkModes", speed); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + System.out.println(matcher.group(1)); + } else + System.out.println("no match"); + } - protected static final String collisions = "\\s+collisions:(\\S+)"; - protected static final String compressed = "\\s+compressed:(\\S+)"; - protected static final String txqueuelen = "\\s+txqueuelen:(\\S+)"; - - protected static final String netDevPattern = "(\\S+):\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+"; - - protected static final String supportedPortsPattern = "Supported ports:\\s*\\[(.*)\\]"; - protected static final String supportedLinkModes = "Supported link modes:\\s*(.*?)Supports"; - protected static final String supportsAutoNegotiation = "Supports auto-negotiation:\\s*(\\S+)"; - protected static final String speed = "Speed:\\s*(.*)"; - protected static final String duplex = "Duplex:\\s*(.*)"; - protected static final String port = "Port:\\s*(.*)"; - - protected final Logger logger; - - /** is this a 64 bits arch ? */ - private boolean is64BitArch = false; + /** + * Return the current declared path of the ifconfig utility (default to /sbin) + * + * @return The path to ifconfig utility. + */ + private synchronized final String getIfconfigPath() { + String path = System.getProperty("net.ifconfig.path", "/bin,/sbin,/usr/bin,/usr/sbin"); + if (path == null || path.length() == 0) { + logger.warning("[Net - iconfig can not be found in " + path + "]"); + return null; + } + return path.replace(',', ':').trim(); + } - final static NumberFormat nf = NumberFormat.getInstance(); - - static { - nf.setMaximumFractionDigits(4); - nf.setMinimumFractionDigits(4); - } - - /** - * The constructor - * @param properties We need the properties of the module in order to interogate for paths - * @param out The stream to write output to - */ - public InterfaceHandler(final PrintStream out, final Logger logger) { - this.logger = logger; - this.out = out; - exec = cmdExec.getInstance(); - } - - /** - * Return the current declared path of the ifconfig utility (default to /sbin) - * @return The path to ifconfig utility. - */ - private synchronized final String getIfconfigPath() { - String path = System.getProperty("net.ifconfig.path", "/bin,/sbin,/usr/bin,/usr/sbin"); - if (path == null || path.length() == 0) { - logger.warning("[Net - iconfig can not be found in " + path+ "]"); - return null; - } - return path.replace(',', ':').trim(); - } - - /** A method that can be used to check for the encapsulation type of a link - * @param props (Option) The properties to put the type into - * @param ifOutput The output of the ifconfig utility - * @return False if the encapsulation type is of no interest (for example loopback interface) - * */ - private final boolean checkLinkEncap(final InterfaceStatisticsStatic props, final String ifOutput) { - - if (ifOutput == null || ifOutput.length() == 0) return false; - if (ifOutput.contains("Link encap:Ethernet")) { - if (props != null) props.setEncap(InterfaceStatisticsStatic.TYPE_ETHERNET); - return true; - } - if (ifOutput.contains("Link encap:Fiber Distributed Data Interface")) { - if (props != null) props.setEncap(InterfaceStatisticsStatic.TYPE_FIBER); - return true; - } - // TODO maybe we should consider: - // HIPPI: http://www.rfc-editor.org/rfc/rfc1374.txt - // AX.25, X.25, Token Ring, Frame Relay, ISDN, HDLC, PPP, Econet - return false; - } - - /** some temporary variables ... we only want them to be instantiated once because of the latency involved */ - final List ifArray = new ArrayList(); - final List toRemove = new ArrayList(); + /** + * A method that can be used to check for the encapsulation type of a link + * + * @param props (Option) The properties to put the type into + * @param ifOutput The output of the ifconfig utility + * @return False if the encapsulation type is of no interest (for example loopback interface) + */ + private final boolean checkLinkEncap(final InterfaceStatisticsStatic props, final String ifOutput) { - /** The part used for ifconfig */ - private final void checkIfConfig() { - final String ifc = getIfconfigPath(); - final String command = "ifconfig"; - CommandResult cmdRes =exec.executeCommandReality(command, (String)null, ifc); - final String ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - return; - } + if (ifOutput == null || ifOutput.length() == 0) return false; + if (ifOutput.contains("Link encap:Ethernet")) { + if (props != null) props.setEncap(InterfaceStatisticsStatic.TYPE_ETHERNET); + return true; + } + if (ifOutput.contains("Link encap:Fiber Distributed Data Interface")) { + if (props != null) props.setEncap(InterfaceStatisticsStatic.TYPE_FIBER); + return true; + } + // TODO maybe we should consider: + // HIPPI: http://www.rfc-editor.org/rfc/rfc1374.txt + // AX.25, X.25, Token Ring, Frame Relay, ISDN, HDLC, PPP, Econet + return false; + } - double diff = 0D; - long now = System.currentTimeMillis(); - if (lastCall != 0) { - diff = (now - lastCall) / 1000D; - } - lastCall = now; + /** + * The part used for ifconfig + */ + private final void checkIfConfig() { + final String ifc = getIfconfigPath(); + final String command = "ifconfig"; + CommandResult cmdRes = exec.executeCommandReality(command, (String) null, ifc); + final String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + return; + } - // otherwise we have ifconfig and can also be used - final String interfaces[] = ret.split("\n\n"); - ifArray.clear(); - for (int i=0; i 0) { - String res = diffWithOverflowCheck(newVal, lastRXPackets.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setRXPackets((double)difRes / diff); - } catch (Throwable t) { - ifProp.setRXPackets(-1D); - } - } - lastRXPackets.put(name, newVal); - } - pattern = PatternUtil.getPattern("rxerrors", rxErrors); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastRXErrors.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setRXErrors((double)difRes / diff); - } catch (Throwable t) { - ifProp.setRXErrors(-1D); - } - } - lastRXErrors.put(name, newVal); - } - pattern = PatternUtil.getPattern("rxdropped", rxDropped); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastRXDropped.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setRXDropped((double)difRes / diff); - } catch (Throwable t) { - ifProp.setRXDropped(-1D); - } - } - lastRXDropped.put(name, newVal); - } - pattern = PatternUtil.getPattern("rxoverruns", rxOverruns); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastRXOverruns.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setRXOverruns((double)difRes / diff); - } catch (Throwable t) { - ifProp.setRXOverruns(-1D); - } - } - lastRXOverruns.put(name, newVal); - } - pattern = PatternUtil.getPattern("rxframe", rxFrame); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastRXFrame.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setRXFrame((double)difRes / diff); - } catch (Throwable t) { - ifProp.setRXFrame(-1D); - } - } - lastRXFrame.put(name, newVal); - } - pattern = PatternUtil.getPattern("rxBytes", rxBytes); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastRXBytes.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setRX((double)difRes * 8D / diff); - } catch (Throwable t) { - ifProp.setRX(-1D); - } - } - lastRXBytes.put(name, newVal); - } + double diff = 0D; + long now = System.currentTimeMillis(); + if (lastCall != 0) { + diff = (now - lastCall) / 1000D; + } + lastCall = now; - pattern = PatternUtil.getPattern("txpackets", txPackets); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastTXPackets.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setTXPackets((double)difRes / diff); - } catch (Throwable t) { - ifProp.setTXPackets(-1D); - } - } - lastTXPackets.put(name, newVal); - } - pattern = PatternUtil.getPattern("txerrors", txErrors); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastTXErrors.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setTXErrors((double)difRes / diff); - } catch (Throwable t) { - ifProp.setTXErrors(-1D); - } - } - lastTXErrors.put(name, newVal); - } - pattern = PatternUtil.getPattern("txdropped", txDropped); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastTXDropped.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setTXDropped((double)difRes / diff); - } catch (Throwable t) { - ifProp.setTXDropped(-1D); - } - } - lastTXDropped.put(name, newVal); - } - pattern = PatternUtil.getPattern("txoverruns", txOverruns); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastTXOverruns.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setTXOverruns((double)difRes / diff); - } catch (Throwable t) { - ifProp.setTXOverruns(-1D); - } - } - lastTXOverruns.put(name, newVal); - } - pattern = PatternUtil.getPattern("txcarrier", txCarrier); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastTXCarrier.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setTXCarrier((double)difRes / diff); - } catch (Throwable t) { - ifProp.setTXCarrier(-1D); - } - } - lastTXCarrier.put(name, newVal); - } - pattern = PatternUtil.getPattern("txBytes", txBytes); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); + // otherwise we have ifconfig and can also be used + final String interfaces[] = ret.split("\n\n"); + ifArray.clear(); + for (int i = 0; i < interfaces.length; i++) { + Pattern pattern = PatternUtil.getPattern("ifname", ifNamePattern); + Matcher matcher = pattern.matcher(interfaces[i]); + InterfaceStatistics ifProp = null; + InterfaceStatisticsStatic ifPropStatic = null; + String name = null; + if (matcher.find()) { + name = matcher.group(1); + ifArray.add(name); + if (!ifs.containsKey(name)) { + ifProp = new InterfaceStatistics(name); + ifs.put(name, ifProp); + } else { + ifProp = ifs.get(name); + ifProp.updateTime(); + } + if (!staticifs.containsKey(name)) { + ifPropStatic = new InterfaceStatisticsStatic(name); + staticifs.put(name, ifPropStatic); + } else { + ifPropStatic = staticifs.get(name); + ifPropStatic.updateTime(); + } + if (!checkLinkEncap(ifPropStatic, interfaces[i])) { // if not an interface to be interested in... + ifArray.remove(name); // to be removed + continue; // continue with the next interface + } + } else { + continue; // continue with the next interface + } + pattern = PatternUtil.getPattern("hwaddr", hwAddrPattern); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + ifPropStatic.setHwAddr(matcher.group(1)); + } else + ifPropStatic.setHwAddr("unknown"); + pattern = PatternUtil.getPattern("ipv4", ipv4Pattern); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + ifProp.setIPv4(matcher.group(1)); + } else + ifProp.setIPv4("unknown"); + pattern = PatternUtil.getPattern("mask4", mask4Pattern); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + ifProp.setMaskv4(matcher.group(1)); + } else + ifProp.setMaskv4("unknown"); + pattern = PatternUtil.getPattern("bcast4", bcast4Pattern); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + ifProp.setBcastv4(matcher.group(1)); + } else + ifProp.setBcastv4("unknown"); + // TODO also add information regarding ipv6 + pattern = PatternUtil.getPattern("mtu", mtuPattern); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + try { + ifProp.setMTU(Integer.parseInt(matcher.group(1))); + } catch (Exception ex) { + ifProp.setMTU(-1); + } + } else + ifProp.setMTU(-1); + pattern = PatternUtil.getPattern("rxpackets", rxPackets); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastRXPackets.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setRXPackets((double) difRes / diff); + } catch (Throwable t) { + ifProp.setRXPackets(-1D); + } + } + lastRXPackets.put(name, newVal); + } + pattern = PatternUtil.getPattern("rxerrors", rxErrors); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastRXErrors.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setRXErrors((double) difRes / diff); + } catch (Throwable t) { + ifProp.setRXErrors(-1D); + } + } + lastRXErrors.put(name, newVal); + } + pattern = PatternUtil.getPattern("rxdropped", rxDropped); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastRXDropped.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setRXDropped((double) difRes / diff); + } catch (Throwable t) { + ifProp.setRXDropped(-1D); + } + } + lastRXDropped.put(name, newVal); + } + pattern = PatternUtil.getPattern("rxoverruns", rxOverruns); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastRXOverruns.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setRXOverruns((double) difRes / diff); + } catch (Throwable t) { + ifProp.setRXOverruns(-1D); + } + } + lastRXOverruns.put(name, newVal); + } + pattern = PatternUtil.getPattern("rxframe", rxFrame); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastRXFrame.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setRXFrame((double) difRes / diff); + } catch (Throwable t) { + ifProp.setRXFrame(-1D); + } + } + lastRXFrame.put(name, newVal); + } + pattern = PatternUtil.getPattern("rxBytes", rxBytes); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastRXBytes.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setRX((double) difRes * 8D / diff); + } catch (Throwable t) { + ifProp.setRX(-1D); + } + } + lastRXBytes.put(name, newVal); + } - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastTXBytes.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setTX((double)difRes * 8D / diff); - } catch (Throwable t) { - ifProp.setTX(-1D); - } - } - lastTXBytes.put(name, newVal); - } + pattern = PatternUtil.getPattern("txpackets", txPackets); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastTXPackets.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setTXPackets((double) difRes / diff); + } catch (Throwable t) { + ifProp.setTXPackets(-1D); + } + } + lastTXPackets.put(name, newVal); + } + pattern = PatternUtil.getPattern("txerrors", txErrors); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastTXErrors.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setTXErrors((double) difRes / diff); + } catch (Throwable t) { + ifProp.setTXErrors(-1D); + } + } + lastTXErrors.put(name, newVal); + } + pattern = PatternUtil.getPattern("txdropped", txDropped); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastTXDropped.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setTXDropped((double) difRes / diff); + } catch (Throwable t) { + ifProp.setTXDropped(-1D); + } + } + lastTXDropped.put(name, newVal); + } + pattern = PatternUtil.getPattern("txoverruns", txOverruns); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastTXOverruns.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setTXOverruns((double) difRes / diff); + } catch (Throwable t) { + ifProp.setTXOverruns(-1D); + } + } + lastTXOverruns.put(name, newVal); + } + pattern = PatternUtil.getPattern("txcarrier", txCarrier); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastTXCarrier.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setTXCarrier((double) difRes / diff); + } catch (Throwable t) { + ifProp.setTXCarrier(-1D); + } + } + lastTXCarrier.put(name, newVal); + } + pattern = PatternUtil.getPattern("txBytes", txBytes); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); - pattern = PatternUtil.getPattern("collisions", collisions); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastCollisions.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setCollisions((double)difRes / diff); - } catch (Throwable t) { - ifProp.setCollisions(-1D); - } - } - lastCollisions.put(name, newVal); - } + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastTXBytes.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setTX((double) difRes * 8D / diff); + } catch (Throwable t) { + ifProp.setTX(-1D); + } + } + lastTXBytes.put(name, newVal); + } - pattern = PatternUtil.getPattern("compressed", compressed); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - if (diff > 0) { - String res = diffWithOverflowCheck(newVal, lastCompressed.get(name)); - try { - double difRes = Double.parseDouble(res); - ifProp.setCompressed((double)difRes / diff); - ifProp.setCanCompress(true); - } catch (Throwable t) { - ifProp.setCompressed(-1D); - } - } - lastCompressed.put(name, newVal); - } else - ifProp.setCanCompress(false); + pattern = PatternUtil.getPattern("collisions", collisions); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastCollisions.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setCollisions((double) difRes / diff); + } catch (Throwable t) { + ifProp.setCollisions(-1D); + } + } + lastCollisions.put(name, newVal); + } - pattern = PatternUtil.getPattern("txqueuelen", txqueuelen); - matcher = pattern.matcher(interfaces[i]); - if (matcher.find()) { - String newVal = matcher.group(1); - try { - ifProp.setTXQueueLen(Integer.parseInt(newVal)); - } catch (Exception e) { - ifProp.setTXQueueLen(-1); - } - } + pattern = PatternUtil.getPattern("compressed", compressed); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + if (diff > 0) { + String res = diffWithOverflowCheck(newVal, lastCompressed.get(name)); + try { + double difRes = Double.parseDouble(res); + ifProp.setCompressed((double) difRes / diff); + ifProp.setCanCompress(true); + } catch (Throwable t) { + ifProp.setCompressed(-1D); + } + } + lastCompressed.put(name, newVal); + } else + ifProp.setCanCompress(false); + + pattern = PatternUtil.getPattern("txqueuelen", txqueuelen); + matcher = pattern.matcher(interfaces[i]); + if (matcher.find()) { + String newVal = matcher.group(1); + try { + ifProp.setTXQueueLen(Integer.parseInt(newVal)); + } catch (Exception e) { + ifProp.setTXQueueLen(-1); + } + } + + } + // finnaly if some interfaces were removed, than better remove their properties also + toRemove.clear(); + for (Iterator it = ifs.keySet().iterator(); it.hasNext(); ) { + String ifn = (String) it.next(); + if (!ifArray.contains(ifn)) toRemove.add(ifn); + } + for (String ifn : toRemove) { + ifs.remove(ifn); + staticifs.remove(ifn); + } - } - // finnaly if some interfaces were removed, than better remove their properties also - toRemove.clear(); - for (Iterator it = ifs.keySet().iterator(); it.hasNext(); ) { - String ifn = (String)it.next(); - if (!ifArray.contains(ifn)) toRemove.add(ifn); - } - for (String ifn : toRemove) { - ifs.remove(ifn); - staticifs.remove(ifn); - } - // for (Map.Entry entry : ifs.entrySet()) { // logger.info(entry.getValue().toString()); // } - } - - public void modify(TXQueueLenSet set) { + } + + public void modify(TXQueueLenSet set) { + + String ifc = getIfconfigPath(); + + // get the previous value first + String previoustx = null; + String command = "ifconfig " + set.ifName; + CommandResult cmdRes = exec.executeCommandReality(command, (String) null, ifc); + String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + } else { + final Pattern pattern = PatternUtil.getPattern("txqueuelen", txqueuelen); + final Matcher matcher = pattern.matcher(ret); + if (matcher.find()) { + previoustx = matcher.group(1); + } + } + + // try to change it while running first.... + command = "ifconfig " + set.ifName + " txqueuelen " + set.txqueuelen; + cmdRes = exec.executeCommandReality(command, (String) null, ifc); + ret = cmdRes.getOutput(); + boolean done = true; + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + done = false; + } + if (done) { + // check the result... + command = "ifconfig " + set.ifName; + cmdRes = exec.executeCommandReality(command, (String) null, ifc); + ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + done = false; + } + if (done) { + final Pattern pattern = PatternUtil.getPattern("txqueuelen", txqueuelen); + final Matcher matcher = pattern.matcher(ret); + if (matcher.find()) { + String newVal = matcher.group(1); + if (previoustx == null) return; // everything is set + done = !previoustx.equals(newVal); + } + } + } + if (done) + return; + + // now change it by doing ifdown, ifup + doIfDown(set.ifName); + command = "ifconfig " + set.ifName + " txqueuelen " + set.txqueuelen; + cmdRes = exec.executeCommandReality(command, (String) null, ifc); + ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + } + doIfUp(set.ifName); + } + + public void modify(MTUSet set) { + + String ifc = getIfconfigPath(); + + // get the previous value first + String previousmtu = null; + String command = "ifconfig " + set.ifName; + CommandResult cmdRes = exec.executeCommandReality(command, (String) null, ifc); + String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + } else { + final Pattern pattern = PatternUtil.getPattern("mtu", mtuPattern); + final Matcher matcher = pattern.matcher(ret); + if (matcher.find()) { + previousmtu = matcher.group(1); + } + } + + // try to change it while running first.... + command = "ifconfig " + set.ifName + " mtu " + set.mtu; + cmdRes = exec.executeCommandReality(command, (String) null, ifc); + ret = cmdRes.getOutput(); + boolean done = true; + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + done = false; + } + if (done) { + // check the result... + command = "ifconfig " + set.ifName; + cmdRes = exec.executeCommandReality(command, (String) null, ifc); + ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + done = false; + } + if (done) { + final Pattern pattern = PatternUtil.getPattern("mtu", mtuPattern); + final Matcher matcher = pattern.matcher(ret); + if (matcher.find()) { + String newVal = matcher.group(1); + if (previousmtu == null) return; // everything is set + done = !previousmtu.equals(newVal); + } + } + } + if (done) + return; + + // now change it by doing ifdown, ifup + doIfDown(set.ifName); + command = "ifconfig " + set.ifName + " mtu " + set.mtu; + cmdRes = exec.executeCommandReality(command, (String) null, ifc); + ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + } + doIfUp(set.ifName); + } + + private void doIfDown(String ifName) { + + final String ifc = getIfconfigPath(); + final String command = "ifconfig down"; + final CommandResult cmdRes = exec.executeCommandReality(command, (String) null, ifc); + final String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + + return; + } + } + + private void doIfUp(String ifName) { + + final String ifc = getIfconfigPath(); + final String command = "ifconfig up"; + final CommandResult cmdRes = exec.executeCommandReality(command, (String) null, ifc); + final String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) + out.println(ret); + else + logger.info(ret); + + return; + } + } + + /** + * Called in order to check if we should use ethtool utility + * + * @return False if net.ethtool.use is set to false + */ + private final boolean shouldRunEthTool() { + String prop = System.getProperty("net.ethtool.use", null); + if (prop == null) + return true; + try { + return Boolean.getBoolean(prop); + } catch (Throwable t) { + } + return true; + } + + private synchronized final String getEthtoolPath() { + String path = System.getProperty("net.ethtool.path", "/bin,/sbin,/usr/bin,/usr/sbin"); + if (path == null || path.length() == 0) { + logger + .warning("[Net - ethtool can not be found in " + path + + "]"); + return null; + } + return path.replace(',', ':').trim(); + } + + /** + * The part used for mii-tool + */ + private final void checkEthtool() { + + final String eth = getEthtoolPath(); + final String command = "ethtool "; + for (Map.Entry entry : staticifs.entrySet()) { + final String ifName = entry.getKey(); + final InterfaceStatisticsStatic stat = entry.getValue(); + if (ifName == null || ifName.length() == 0 || stat == null) continue; + final CommandResult cmdRes = exec.executeCommandReality(command + ifName, null, eth); + final String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) { + out.println(ret); + } else { + if (logger.isLoggable(Level.FINER)) + logger.log(Level.FINE, ret); + } + continue; + } + stat.setSupportedPorts((byte) 0); + + Pattern pattern = PatternUtil.getPattern("supportedPorts", supportedPortsPattern); + Matcher matcher = pattern.matcher(ret); + if (matcher.find()) { + final String mm = matcher.group(1); + if (mm.trim().length() != 0) { + final String sp[] = mm.trim().split(" "); + for (int k = 0; k < sp.length; k++) { + if (sp[k].equals("TP")) + stat.setSupportedPorts(InterfaceStatisticsStatic.PORT_TP); + if (sp[k].equals("AUI")) + stat.setSupportedPorts(InterfaceStatisticsStatic.PORT_AUI); + if (sp[k].equals("BNC")) + stat.setSupportedPorts(InterfaceStatisticsStatic.PORT_BNC); + if (sp[k].equals("MII")) + stat.setSupportedPorts(InterfaceStatisticsStatic.PORT_MII); + if (sp[k].equals("FIBRE")) + stat.setSupportedPorts(InterfaceStatisticsStatic.PORT_FIBRE); + } + } + } + pattern = PatternUtil.getPattern("supportedLinkModes", supportedLinkModes, true); + matcher = pattern.matcher(ret); + if (matcher.find()) { + final String mm = matcher.group(1); + if (mm.trim().length() != 0) { + final String sp[] = mm.trim().split("[ \n]+"); + for (int k = 0; k < sp.length; k++) { + if (sp[k].trim().length() == 0) continue; + final String ss = sp[k].trim(); + if (ss.equals("10baseT/Half")) + stat.setSupportedLinkModes(InterfaceStatisticsStatic.SUPPORTED_10BaseT_Half); + if (ss.equals("10baseT/Full")) + stat.setSupportedLinkModes(InterfaceStatisticsStatic.SUPPORTED_10BaseT_Full); + if (ss.equals("100baseT/Half")) + stat.setSupportedLinkModes(InterfaceStatisticsStatic.SUPPORTED_100BaseT_Half); + if (ss.equals("100baseT/Full")) + stat.setSupportedLinkModes(InterfaceStatisticsStatic.SUPPORTED_100BaseT_Full); + if (ss.equals("1000baseT/Half")) + stat.setSupportedLinkModes(InterfaceStatisticsStatic.SUPPORTED_1000BaseT_Half); + if (ss.equals("1000baseT/Full")) + stat.setSupportedLinkModes(InterfaceStatisticsStatic.SUPPORTED_1000BaseT_Full); + } + } + } + pattern = PatternUtil.getPattern("supportsAutoNegotiation", supportsAutoNegotiation); + matcher = pattern.matcher(ret); + if (matcher.find()) { + final String mm = matcher.group(1); + if (mm.trim().length() != 0) { + if (mm.trim().equals("Yes")) + stat.setSupportsAutoNegotiation(true); + } + } + pattern = PatternUtil.getPattern("linkSpeed", speed); + matcher = pattern.matcher(ret); + if (matcher.find()) { + final String mm = matcher.group(1); + if (mm != null && mm.trim().length() != 0) { + final String ss = mm.trim(); + if (ss.equals("10Mb/s")) + stat.setMaxSpeed(10); + else if (ss.equals("100Mb/s")) + stat.setMaxSpeed(100); + else if (ss.equals("1000Mb/s")) + stat.setMaxSpeed(1000); + else if (ss.equals("10000Mb/s")) + stat.setMaxSpeed(10000); + else if (ss.startsWith("Unknown")) { + final String sp = ss.substring(ss.indexOf('(') + 1, ss.lastIndexOf(')')); + try { + stat.setMaxSpeed(Integer.parseInt(sp)); + } catch (Exception ex) { + logger.warning("Can not determine speed " + ss + " for " + ifName); + } + } + } + } + pattern = PatternUtil.getPattern("linkDuplex", duplex); + matcher = pattern.matcher(ret); + if (matcher.find()) { + final String mm = matcher.group(1); + if (mm != null && mm.trim().length() != 0) { + final String ss = mm.trim(); + if (ss.compareToIgnoreCase("Half") == 0) { + stat.setDuplex(InterfaceStatisticsStatic.LINK_HALF); + } else if (ss.compareToIgnoreCase("Full") == 0) { + stat.setDuplex(InterfaceStatisticsStatic.LINK_DUPLEX); + } + } + } + pattern = PatternUtil.getPattern("linkPort", port); + matcher = pattern.matcher(ret); + if (matcher.find()) { + final String mm = matcher.group(1); + if (mm != null && mm.trim().length() != 0) { + final String ss = mm.trim(); + if (ss.compareTo("Twisted Pair") == 0) { + stat.setPort(InterfaceStatisticsStatic.PORT_TP); + } else if (ss.compareToIgnoreCase("FIBRE") == 0) { + stat.setPort(InterfaceStatisticsStatic.PORT_FIBRE); + } else if (ss.compareToIgnoreCase("AUI") == 0) { + stat.setPort(InterfaceStatisticsStatic.PORT_AUI); + } else if (ss.compareToIgnoreCase("BNC") == 0) { + stat.setPort(InterfaceStatisticsStatic.PORT_BNC); + } else if (ss.compareToIgnoreCase("MII") == 0) { + stat.setPort(InterfaceStatisticsStatic.PORT_MII); + } + } + } + } + } + + /** + * This is used to check for network interface devices locally by looking into + * /proc/net/dev - is used only if ifconfig fails to speed up things + */ + private final void getFromProcDev() { + ifArray.clear(); + final Pattern pattern = PatternUtil.getPattern("/proc/net/dev", netDevPattern); + try { + BufferedReader reader = new BufferedReader(new FileReader("/proc/net/dev")); + String line; + while ((line = reader.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + final String ifName = matcher.group(1); + ifArray.add(ifName); + if (!ifs.containsKey(ifName)) { + ifs.put(ifName, new InterfaceStatistics(ifName)); + } else + ifs.get(ifName).updateTime(); + } + } + reader.close(); + } catch (Exception e) { + } + + // finnaly if some interfaces were removed, than better remove their properties also + toRemove.clear(); + for (Iterator it = ifs.keySet().iterator(); it.hasNext(); ) { + String ifn = (String) it.next(); + if (!ifArray.contains(ifn)) toRemove.add(ifn); + } + for (String ifn : toRemove) { + ifs.remove(ifn); + } + } - String ifc = getIfconfigPath(); + private final boolean compare(InterfaceStatisticsStatic olds, InterfaceStatisticsStatic news) { + if (olds.getEncap() != news.getEncap()) return false; + if (olds.getHwAddr() == null && news.getHwAddr() != null) return false; + if (olds.getHwAddr() != null && news.getHwAddr() == null) return false; + if (olds.getHwAddr() != null && news.getHwAddr() != null) + if (!olds.getHwAddr().equals(news.getHwAddr())) return false; + if (olds.getSupportedPorts() != news.getSupportedPorts()) return false; + if (olds.getSupportedLinkModes() != news.getSupportedLinkModes()) return false; + if (olds.supportsAutoNegotiation() != news.supportsAutoNegotiation()) return false; + if (olds.getMaxSpeed() != news.getMaxSpeed()) return false; + if (olds.getDuplex() != news.getDuplex()) return false; + if (olds.getPort() != news.getPort()) return false; + return true; + } - // get the previous value first - String previoustx = null; - String command = "ifconfig "+set.ifName; - CommandResult cmdRes =exec.executeCommandReality(command, (String)null, ifc); - String ret =cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - } else { - final Pattern pattern = PatternUtil.getPattern("txqueuelen", txqueuelen); - final Matcher matcher = pattern.matcher(ret); - if (matcher.find()) { - previoustx = matcher.group(1); - } - } - - // try to change it while running first.... - command = "ifconfig "+set.ifName+" txqueuelen "+set.txqueuelen; - cmdRes =exec.executeCommandReality(command, (String)null, ifc); - ret = cmdRes.getOutput(); - boolean done = true; - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - done = false; - } - if (done) { - // check the result... - command = "ifconfig "+set.ifName; - cmdRes = exec.executeCommandReality(command, (String)null, ifc); - ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - done = false; - } - if (done) { - final Pattern pattern = PatternUtil.getPattern("txqueuelen", txqueuelen); - final Matcher matcher = pattern.matcher(ret); - if (matcher.find()) { - String newVal = matcher.group(1); - if (previoustx == null) return; // everything is set - done = !previoustx.equals(newVal); - } - } - } - if (done) - return; + /** + * Action method for this task... retrieves the network interfaces together with their properties ... + */ + public final void check() { + checkIfConfig(); + if (ifs.size() == 0) { // no interface yet? let's read /proc/net/dev also + getFromProcDev(); + } + if (ifs.size() == 0) return; // so there really is no net device installed into the system + if (shouldRunEthTool()) + checkEthtool(); - // now change it by doing ifdown, ifup - doIfDown(set.ifName); - command = "ifconfig "+set.ifName+" txqueuelen "+set.txqueuelen; - cmdRes =exec.executeCommandReality(command, (String)null, ifc); - ret = cmdRes.getOutput(); - if (cmdRes.failed()|| ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - } - doIfUp(set.ifName); - } - - public void modify(MTUSet set) { + final LinkedList names = new LinkedList(); + for (Map.Entry entry : lastGoodStatic.entrySet()) { + names.addLast(entry.getKey()); + } + for (String name : names) { + if (!staticifs.containsKey(name)) + lastGoodStatic.remove(name); + } + names.clear(); + for (Map.Entry entry : staticifs.entrySet()) { + names.addLast(entry.getKey()); + } + for (String name : names) { + if (!lastGoodStatic.containsKey(name)) { + lastGoodStatic.put(name, staticifs.get(name)); + continue; + } + final InterfaceStatisticsStatic olds = lastGoodStatic.get(name); + final InterfaceStatisticsStatic news = staticifs.get(name); - String ifc = getIfconfigPath(); + if (!compare(olds, news)) { + lastGoodStatic.put(name, news); + } else { + staticifs.remove(name); + } + } + } - // get the previous value first - String previousmtu = null; - String command = "ifconfig "+set.ifName; - CommandResult cmdRes =exec.executeCommandReality(command, (String)null, ifc); - String ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - } else { - final Pattern pattern = PatternUtil.getPattern("mtu", mtuPattern); - final Matcher matcher = pattern.matcher(ret); - if (matcher.find()) { - previousmtu = matcher.group(1); - } - } - - // try to change it while running first.... - command = "ifconfig "+set.ifName+" mtu "+set.mtu; - cmdRes =exec.executeCommandReality(command, (String)null, ifc); - ret = cmdRes.getOutput(); - boolean done = true; - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - done = false; - } - if (done) { - // check the result... - command = "ifconfig "+set.ifName; - cmdRes =exec.executeCommandReality(command, (String)null, ifc); - ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - done = false; - } - if (done) { - final Pattern pattern = PatternUtil.getPattern("mtu", mtuPattern); - final Matcher matcher = pattern.matcher(ret); - if (matcher.find()) { - String newVal = matcher.group(1); - if (previousmtu == null) return; // everything is set - done = !previousmtu.equals(newVal); - } - } - } - if (done) - return; + public final HashMap getIfStatistics() { + return ifs; + } - // now change it by doing ifdown, ifup - doIfDown(set.ifName); - command = "ifconfig "+set.ifName+" mtu "+set.mtu; - cmdRes =exec.executeCommandReality(command, (String)null, ifc); - ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - } - doIfUp(set.ifName); - } - - private void doIfDown(String ifName) { + public final HashMap getOldIfStatisticsStatic() { + return lastGoodStatic; + } - final String ifc = getIfconfigPath(); - final String command = "ifconfig down"; - final CommandResult cmdRes =exec.executeCommandReality(command, (String)null, ifc); - final String ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - - return; - } - } + public final HashMap getIfStatisticsStatic() { + return staticifs; + } - private void doIfUp(String ifName) { + public String diffWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + if (newv.compareTo(oldv) >= 0) + return newv.subtract(oldv).toString(); + BigInteger overflow = new BigInteger("1").shiftLeft(64); + BigDecimal d = new BigDecimal(overflow.toString()); + return newv.add(d).subtract(oldv).toString(); + } + // otherwise we still assume 32 bits arch + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return diffWithOverflowCheck(newVal, oldVal); + } + // so it's still 32 bits arch + if (newv >= oldv) { + return "" + (newv - oldv); + } + long vmax = 1L << 32; // 32 bits + return "" + (newv - oldv + vmax); + } - final String ifc = getIfconfigPath(); - final String command = "ifconfig up"; - final CommandResult cmdRes =exec.executeCommandReality(command, (String)null, ifc); - final String ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) - out.println(ret); - else - logger.info(ret); - - return; - } - } + public String divideWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { - /** - * Called in order to check if we should use ethtool utility - * @return False if net.ethtool.use is set to false - */ - private final boolean shouldRunEthTool() { - String prop = System.getProperty("net.ethtool.use", null); - if (prop == null) - return true; - try { - return Boolean.getBoolean(prop); - } catch (Throwable t) { } - return true; - } - - private synchronized final String getEthtoolPath() { - String path = System.getProperty("net.ethtool.path", "/bin,/sbin,/usr/bin,/usr/sbin"); - if (path == null || path.length() == 0) { - logger - .warning("[Net - ethtool can not be found in " + path - + "]"); - return null; - } - return path.replace(',', ':').trim(); - } - - /** The part used for mii-tool */ - private final void checkEthtool() { - - final String eth = getEthtoolPath(); - final String command = "ethtool "; - for (Map.Entry entry : staticifs.entrySet()) { - final String ifName = entry.getKey(); - final InterfaceStatisticsStatic stat = entry.getValue(); - if (ifName == null || ifName.length() == 0 || stat == null) continue; - final CommandResult cmdRes= exec.executeCommandReality(command+ifName, null, eth); - final String ret= cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) { - out.println(ret); - } else { - if (logger.isLoggable(Level.FINER)) - logger.log(Level.FINE, ret); - } - continue; - } - stat.setSupportedPorts((byte)0); - - Pattern pattern = PatternUtil.getPattern("supportedPorts", supportedPortsPattern); - Matcher matcher = pattern.matcher(ret); - if (matcher.find()) { - final String mm = matcher.group(1); - if (mm.trim().length() != 0) { - final String sp[] = mm.trim().split(" "); - for (int k=0; k= toCompare || oldv >= toCompare) { + is64BitArch = true; + return divideWithOverflowCheck(newVal, oldVal); + } + // so it's still 32 bits arch + return "" + (newv / oldv); + } - /** This is used to check for network interface devices locally by looking into - * /proc/net/dev - is used only if ifconfig fails to speed up things - */ - private final void getFromProcDev() { - ifArray.clear(); - final Pattern pattern = PatternUtil.getPattern("/proc/net/dev", netDevPattern); - try { - BufferedReader reader = new BufferedReader(new FileReader("/proc/net/dev")); - String line; - while ((line = reader.readLine()) != null) { - final Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - final String ifName = matcher.group(1); - ifArray.add(ifName); - if (!ifs.containsKey(ifName)) { - ifs.put(ifName, new InterfaceStatistics(ifName)); - } else - ifs.get(ifName).updateTime(); - } - } - reader.close(); - } catch (Exception e) { } - - // finnaly if some interfaces were removed, than better remove their properties also - toRemove.clear(); - for (Iterator it = ifs.keySet().iterator(); it.hasNext(); ) { - String ifn = (String)it.next(); - if (!ifArray.contains(ifn)) toRemove.add(ifn); - } - for (String ifn : toRemove) { - ifs.remove(ifn); - } - } - - private final boolean compare(InterfaceStatisticsStatic olds, InterfaceStatisticsStatic news) { - if (olds.getEncap() != news.getEncap()) return false; - if (olds.getHwAddr() == null && news.getHwAddr() != null) return false; - if (olds.getHwAddr() != null && news.getHwAddr() == null) return false; - if (olds.getHwAddr() != null && news.getHwAddr() != null) - if (!olds.getHwAddr().equals(news.getHwAddr())) return false; - if (olds.getSupportedPorts() != news.getSupportedPorts()) return false; - if (olds.getSupportedLinkModes() != news.getSupportedLinkModes()) return false; - if (olds.supportsAutoNegotiation() != news.supportsAutoNegotiation()) return false; - if (olds.getMaxSpeed() != news.getMaxSpeed()) return false; - if (olds.getDuplex() != news.getDuplex()) return false; - if (olds.getPort() != news.getPort()) return false; - return true; - } - - /** - * Action method for this task... retrieves the network interfaces together with their properties ... - */ - public final void check() { - checkIfConfig(); - if (ifs.size() == 0) { // no interface yet? let's read /proc/net/dev also - getFromProcDev(); - } - if (ifs.size() == 0) return; // so there really is no net device installed into the system - if (shouldRunEthTool()) - checkEthtool(); - - final LinkedList names = new LinkedList(); - for (Map.Entry entry : lastGoodStatic.entrySet()) { - names.addLast(entry.getKey()); - } - for (String name : names) { - if (!staticifs.containsKey(name)) - lastGoodStatic.remove(name); - } - names.clear(); - for (Map.Entry entry : staticifs.entrySet()) { - names.addLast(entry.getKey()); - } - for (String name : names) { - if (!lastGoodStatic.containsKey(name)) { - lastGoodStatic.put(name, staticifs.get(name)); - continue; - } - final InterfaceStatisticsStatic olds = lastGoodStatic.get(name); - final InterfaceStatisticsStatic news = staticifs.get(name); - - if (!compare(olds, news)) { - lastGoodStatic.put(name, news); - } else { - staticifs.remove(name); - } - } - } - - public final HashMap getIfStatistics() { - return ifs; - } - - public final HashMap getOldIfStatisticsStatic() { - return lastGoodStatic; - } - - public final HashMap getIfStatisticsStatic() { - return staticifs; - } + private final String prepareString(String str) { - public String diffWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - if (newv.compareTo(oldv) >= 0) - return newv.subtract(oldv).toString(); - BigInteger overflow = new BigInteger("1").shiftLeft(64); - BigDecimal d = new BigDecimal(overflow.toString()); - return newv.add(d).subtract(oldv).toString(); - } - // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return diffWithOverflowCheck(newVal, oldVal); - } - // so it's still 32 bits arch - if (newv >= oldv) { - return "" + (newv - oldv); - } - long vmax = 1L << 32; // 32 bits - return "" + (newv - oldv + vmax); - } + // first try to make it double + try { + double d = Double.parseDouble(str); + if (!Double.isInfinite(d) && !Double.isNaN(d)) { + String n = nf.format(d); + n = n.replaceAll(",", ""); + return n; + } + } catch (Throwable t) { + } - public String divideWithOverflowCheck(String newVal, String oldVal) throws NumberFormatException { + if (!str.contains(".")) { + return str + ".0000"; + } + int nr = str.lastIndexOf('.') + 1; + nr = str.length() - nr; + for (int i = nr; i < 4; i++) + str += "0"; + return str; + } - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception " + t + " for " + str); - } - return newv.divide(oldv, BigDecimal.ROUND_FLOOR).toString(); - } - // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return divideWithOverflowCheck(newVal, oldVal); - } - // so it's still 32 bits arch - return "" + (newv / oldv); - } - - private final String prepareString(String str) { - - // first try to make it double - try { - double d = Double.parseDouble(str); - if (!Double.isInfinite(d) && !Double.isNaN(d)) { - String n = nf.format(d); - n = n.replaceAll(",", ""); - return n; - } - } catch (Throwable t) { } - - if (!str.contains(".")) { - return str+".0000"; - } - int nr = str.lastIndexOf('.')+1; - nr = str.length() - nr; - for (int i=nr; i<4; i++) - str += "0"; - return str; - } - - /** Called in order to clear some memory when needed */ - public void clear() { - ifs.clear(); - staticifs.clear(); - } - - public static void main(String args[]) { - - String line = "Speed: Unknown (100000)\nTest\n"; - Pattern pattern = PatternUtil.getPattern("supportedLinkModes", speed); - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - System.out.println(matcher.group(1)); - } else - System.out.println("no match"); - } + /** + * Called in order to clear some memory when needed + */ + public void clear() { + ifs.clear(); + staticifs.clear(); + } } // end of class InterfaceHandler diff --git a/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatistics.java b/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatistics.java index a1ab1f4..6615740 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatistics.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatistics.java @@ -7,251 +7,290 @@ /** * Wrapper class that encapsulated the properties of a network interface.. + * * @author Ciprian Dobre */ public class InterfaceStatistics extends Statistics { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - /** The name of the interface */ - protected final String name; - - /** For IPv4 */ - - /** The IPv4 network address in use */ - protected String ipv4; - - /** The IPv4 broadcast domain */ - protected String bcastipv4; - - /** The IPV4 mask */ - protected String maskipv4; - - /** MTU */ - protected int mtu = 1500; - - /** For RX */ - - protected double rxPackets = 0D; - protected double rxErrors = 0D; - protected double rxDropped = 0D; - protected double rxOverruns = 0D; - protected double rxFrame = 0D; - protected double rx = 0D; - - /** For TX */ - - protected double txPackets = 0D; - protected double txErrors = 0D; - protected double txDropped = 0D; - protected double txOverruns = 0D; - protected double txCarrier = 0D; - protected double tx = 0D; - - /** General statistics */ - protected double collisions = 0D; - protected boolean canCompress = false; - protected double compressed = 0D; - protected int txqueuelen = 0; - - /** The constructor - only the name is final */ - public InterfaceStatistics(final String name) { - super(); - this.name = name; - } - - /** Method used to retrieve the name of the interface */ - public final String getName() { - return name; - } - - /** Setter for the network address IPv4 */ - public final void setIPv4(final String address) { - this.ipv4 = address; - } - - /** Getter for the network address IPv4 */ - public final String getIPv4() { - return ipv4; - } - - /** Setter for the bcast address IPv4 */ - public final void setBcastv4(final String bcast) { - this.bcastipv4 = bcast; - } - - /** Getter for the bcast address IPv4 */ - public final String getBcastv4() { - return bcastipv4; - } - - /** Setter for the network mask IPv4 */ - public final void setMaskv4(final String mask) { - this.maskipv4 = mask; - } - - /** Getter for the network mask IPv4 */ - public final String getMaskv4() { - return maskipv4; - } - - /** Setter for mtu */ - public final void setMTU(final int mtu) { - this.mtu = mtu; - } - - /** Getter for mtu */ - public final int getMTU() { - return mtu; - } - - public final void setRX(final double rx) { - this.rx = rx; - } - - public final double getRX() { - return rx; - } - - public final void setRXPackets(final double packets) { - this.rxPackets = packets; - } - - public final double getRXPackets() { - return rxPackets; - } - - public final void setRXErrors(final double errors) { - this.rxErrors = errors; - } - - public final double getRXErrors() { - return rxErrors; - } - - public final void setRXDropped(final double dropped) { - this.rxDropped = dropped; - } - - public final double getRXDropped() { - return rxDropped; - } - - public final void setRXOverruns(final double overruns) { - this.rxOverruns = overruns; - } - - public final double getRXOverruns() { - return rxOverruns; - } - - public final void setRXFrame(final double frame) { - this.rxFrame = frame; - } - - public final double getRXFrame() { - return rxFrame; - } - - public final void setTX(final double tx) { - this.tx = tx; - } - - public final double getTX() { - return tx; - } - - public final void setTXPackets(final double packets) { - this.txPackets = packets; - } - - public final double getTXPackets() { - return txPackets; - } - - public final void setTXErrors(final double errors) { - this.txErrors = errors; - } - - public final double getTXErrors() { - return txErrors; - } - - public final void setTXDropped(final double dropped) { - this.txDropped = dropped; - } - - public final double getTXDropped() { - return txDropped; - } - - public final void setTXOverruns(final double overruns) { - this.txOverruns = overruns; - } - - public final double getTXOverruns() { - return txOverruns; - } - - public final void setTXCarrier(final double carrier) { - this.txCarrier = carrier; - } - - public final double getTXCarrier() { - return txCarrier; - } - - public final void setCompressed(final double compressed) { - this.compressed = compressed; - } - - public final double getCompressed() { - return compressed; - } - - public final void setCanCompress(boolean canCompress) { - this.canCompress = canCompress; - } - - public final boolean getCanCompress() { - return canCompress; - } - - public final void setCollisions(final double collisions) { - this.collisions = collisions; - } - - public final double getCollisions() { - return collisions; - } - - public final void setTXQueueLen(final int txqueuelen) { - this.txqueuelen = txqueuelen; - } - - public final int getTXQueueLen() { - return txqueuelen; - } - - /** Utility method for printing out the properties */ - public String toString() { - - StringBuffer buf = new StringBuffer(); - buf.append("IFNAME [name=").append(name); - buf.append(",ipv4=").append(ipv4); - buf.append(",bcast4=").append(bcastipv4); - buf.append(",mask4=").append(maskipv4); - buf.append(",mtu=").append(mtu); - buf.append(",rx_packets=").append(rxPackets); - buf.append(",tx_packets=").append(txPackets); - buf.append("]"); - return buf.toString(); - } - - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + /** + * The name of the interface + */ + protected final String name; + + /** For IPv4 */ + + /** + * The IPv4 network address in use + */ + protected String ipv4; + + /** + * The IPv4 broadcast domain + */ + protected String bcastipv4; + + /** + * The IPV4 mask + */ + protected String maskipv4; + + /** + * MTU + */ + protected int mtu = 1500; + + /** + * For RX + */ + + protected double rxPackets = 0D; + protected double rxErrors = 0D; + protected double rxDropped = 0D; + protected double rxOverruns = 0D; + protected double rxFrame = 0D; + protected double rx = 0D; + + /** + * For TX + */ + + protected double txPackets = 0D; + protected double txErrors = 0D; + protected double txDropped = 0D; + protected double txOverruns = 0D; + protected double txCarrier = 0D; + protected double tx = 0D; + + /** + * General statistics + */ + protected double collisions = 0D; + protected boolean canCompress = false; + protected double compressed = 0D; + protected int txqueuelen = 0; + + /** + * The constructor - only the name is final + */ + public InterfaceStatistics(final String name) { + super(); + this.name = name; + } + + /** + * Method used to retrieve the name of the interface + */ + public final String getName() { + return name; + } + + /** + * Getter for the network address IPv4 + */ + public final String getIPv4() { + return ipv4; + } + + /** + * Setter for the network address IPv4 + */ + public final void setIPv4(final String address) { + this.ipv4 = address; + } + + /** + * Getter for the bcast address IPv4 + */ + public final String getBcastv4() { + return bcastipv4; + } + + /** + * Setter for the bcast address IPv4 + */ + public final void setBcastv4(final String bcast) { + this.bcastipv4 = bcast; + } + + /** + * Getter for the network mask IPv4 + */ + public final String getMaskv4() { + return maskipv4; + } + + /** + * Setter for the network mask IPv4 + */ + public final void setMaskv4(final String mask) { + this.maskipv4 = mask; + } + + /** + * Getter for mtu + */ + public final int getMTU() { + return mtu; + } + + /** + * Setter for mtu + */ + public final void setMTU(final int mtu) { + this.mtu = mtu; + } + + public final double getRX() { + return rx; + } + + public final void setRX(final double rx) { + this.rx = rx; + } + + public final double getRXPackets() { + return rxPackets; + } + + public final void setRXPackets(final double packets) { + this.rxPackets = packets; + } + + public final double getRXErrors() { + return rxErrors; + } + + public final void setRXErrors(final double errors) { + this.rxErrors = errors; + } + + public final double getRXDropped() { + return rxDropped; + } + + public final void setRXDropped(final double dropped) { + this.rxDropped = dropped; + } + + public final double getRXOverruns() { + return rxOverruns; + } + + public final void setRXOverruns(final double overruns) { + this.rxOverruns = overruns; + } + + public final double getRXFrame() { + return rxFrame; + } + + public final void setRXFrame(final double frame) { + this.rxFrame = frame; + } + + public final double getTX() { + return tx; + } + + public final void setTX(final double tx) { + this.tx = tx; + } + + public final double getTXPackets() { + return txPackets; + } + + public final void setTXPackets(final double packets) { + this.txPackets = packets; + } + + public final double getTXErrors() { + return txErrors; + } + + public final void setTXErrors(final double errors) { + this.txErrors = errors; + } + + public final double getTXDropped() { + return txDropped; + } + + public final void setTXDropped(final double dropped) { + this.txDropped = dropped; + } + + public final double getTXOverruns() { + return txOverruns; + } + + public final void setTXOverruns(final double overruns) { + this.txOverruns = overruns; + } + + public final double getTXCarrier() { + return txCarrier; + } + + public final void setTXCarrier(final double carrier) { + this.txCarrier = carrier; + } + + public final double getCompressed() { + return compressed; + } + + public final void setCompressed(final double compressed) { + this.compressed = compressed; + } + + public final boolean getCanCompress() { + return canCompress; + } + + public final void setCanCompress(boolean canCompress) { + this.canCompress = canCompress; + } + + public final double getCollisions() { + return collisions; + } + + public final void setCollisions(final double collisions) { + this.collisions = collisions; + } + + public final int getTXQueueLen() { + return txqueuelen; + } + + public final void setTXQueueLen(final int txqueuelen) { + this.txqueuelen = txqueuelen; + } + + /** + * Utility method for printing out the properties + */ + public String toString() { + + StringBuffer buf = new StringBuffer(); + buf.append("IFNAME [name=").append(name); + buf.append(",ipv4=").append(ipv4); + buf.append(",bcast4=").append(bcastipv4); + buf.append(",mask4=").append(maskipv4); + buf.append(",mtu=").append(mtu); + buf.append(",rx_packets=").append(rxPackets); + buf.append(",tx_packets=").append(txPackets); + buf.append("]"); + return buf.toString(); + } + + } // end of class InterfaceStatistics diff --git a/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatisticsStatic.java b/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatisticsStatic.java index ab8f04a..aa4889b 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatisticsStatic.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/dev/InterfaceStatisticsStatic.java @@ -3,197 +3,221 @@ */ package lia.util.net.copy.monitoring.lisa.net.dev; -import java.util.LinkedList; - import lia.util.net.copy.monitoring.lisa.net.Statistics; +import java.util.LinkedList; + /** - * * @author Ciprian Dobre */ public class InterfaceStatisticsStatic extends Statistics { - /********* Types of interface encapsulations *****************/ - - public static final byte TYPE_ETHERNET = 0; - public static final byte TYPE_FIBER = 1; - - public static final byte PORT_TP = 1; - public static final byte PORT_AUI = 2; - public static final byte PORT_BNC = 4; - public static final byte PORT_MII = 8; - public static final byte PORT_FIBRE = 16; - - public static final byte SUPPORTED_10BaseT_Half = 1; - public static final byte SUPPORTED_10BaseT_Full = 2; - public static final byte SUPPORTED_100BaseT_Half = 4; - public static final byte SUPPORTED_100BaseT_Full = 8; - public static final byte SUPPORTED_1000BaseT_Half = 16; - public static final byte SUPPORTED_1000BaseT_Full = 32; - - public static final byte LINK_HALF = 0; - public static final byte LINK_DUPLEX = 1; - - - /** The name of the interface */ - protected final String name; - - /** The encapsulation type */ - protected byte encap = 0; - - /** The hardware address of the interface */ - protected String hwAddr; - - protected byte supportedPorts = -1; // -1 means error - protected byte supportedLinkModes = -1; // -1 means error - protected boolean supportsAutoNegotiation = false; - - protected int maxSpeed = -1; // supported speed in Mb/s - protected byte duplex = -1; // -1 means unknown - protected byte port = -1; // -1 means unknown - - /** The constructor - only the name is final */ - public InterfaceStatisticsStatic(final String name) { - super(); - this.name = name; - } - - /** Method used to retrieve the name of the interface */ - public final String getName() { - return name; - } - - /** Setter for the type of encapsulation for the network interface */ - public final void setEncap(final byte type) { - this.encap = type; - } - - /** Getter for the type of encapsulation */ - public final byte getEncap() { - return encap; - } - - /** Getter for the type of encapsulation */ - public final String getEncapAsString() { - switch (encap) { - case TYPE_ETHERNET : return "Ethernet"; - case TYPE_FIBER : return "Fiber"; - } - return "Unknown"; - } - - /** Setter for the hardware address */ - public final void setHwAddr(final String hwAddr) { - this.hwAddr = hwAddr; - } - - /** Getter for the hardware address */ - public final String getHwAddr() { - return hwAddr; - } - - /** Utility method for printing out the properties */ - public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append("IFNAME [name=").append(name); - buf.append(",encap=").append(getEncapAsString()); - buf.append(",hwaddr=").append(hwAddr); - buf.append(",supportedPort=").append(supportedPorts); - buf.append(",supportedLinkModes=").append(supportedLinkModes); - buf.append(",supportsAutoNeg=").append(supportsAutoNegotiation); - buf.append(",maxSpeed=").append(maxSpeed); - buf.append(",duplex=").append(duplex); - buf.append(",port=").append(port); - buf.append("]"); - return buf.toString(); - } - public byte getSupportedPorts() { - return supportedPorts; - } - - private boolean flagSet(byte b, byte flag) { - return ((b & flag) == flag); - } - - public String[] getSupportedPortsAsString() { - - if (supportedPorts < 0) return new String[] { "UNKNOWN" }; - - final LinkedList list = new LinkedList(); - if (flagSet(supportedPorts, PORT_TP)) list.addLast("TP"); - if (flagSet(supportedPorts, PORT_AUI)) list.addLast("AUI"); - if (flagSet(supportedPorts, PORT_BNC)) list.addLast("BNC"); - if (flagSet(supportedPorts, PORT_MII)) list.addLast("MII"); - if (flagSet(supportedPorts, PORT_FIBRE)) list.addLast("FIBRE"); - - if (list.size() == 0) return new String[] { "NONE" }; - return list.toArray(new String[0]); - } - - public void setSupportedPorts(byte supportedPorts) { - if (this.supportedPorts < 0) this.supportedPorts = 0; - this.supportedPorts = (byte)(this.supportedPorts | supportedPorts); - } - - public byte getSupportedLinkModes() { - return supportedLinkModes; - } - - public String[] getSupportedLinkModesAsString() { - if (supportedLinkModes < 0) return new String[] { "UNKNOWN" }; - final LinkedList list = new LinkedList(); - if (flagSet(supportedLinkModes, SUPPORTED_10BaseT_Half)) list.add("10BaseT_Half"); - if (flagSet(supportedLinkModes, SUPPORTED_10BaseT_Full)) list.add("10BaseT_Full"); - if (flagSet(supportedLinkModes, SUPPORTED_100BaseT_Half)) list.add("100BaseT_Half"); - if (flagSet(supportedLinkModes, SUPPORTED_100BaseT_Full)) list.add("100BaseT_Full"); - if (flagSet(supportedLinkModes, SUPPORTED_1000BaseT_Half)) list.add("1000BaseT_Half"); - if (flagSet(supportedLinkModes, SUPPORTED_1000BaseT_Full)) list.add("1000BaseT_Full"); - if (list.size() == 0) return new String[] { "NONE" }; - return list.toArray(new String[0]); - } - - public void setSupportedLinkModes(byte supportedLinkModes) { - if (this.supportedLinkModes < 0) this.supportedLinkModes = 0; - this.supportedLinkModes = (byte)(this.supportedLinkModes | supportedLinkModes); - } - - public boolean supportsAutoNegotiation() { - return supportsAutoNegotiation; - } - - public void setSupportsAutoNegotiation(boolean supportsAutoNegotiation) { - this.supportsAutoNegotiation = supportsAutoNegotiation; - } - - public int getMaxSpeed() { - return maxSpeed; - } - - public String getMaxSpeedAsString() { - if (maxSpeed < 0) - return "UNKNOWN"; - return maxSpeed+" Mb/s"; - } - - public void setMaxSpeed(int maxSpeed) { - this.maxSpeed = maxSpeed; - } - - public byte getDuplex() { - return duplex; - } - - public void setDuplex(byte duplex) { - this.duplex = duplex; - } - - public byte getPort() { - return port; - } - - public void setPort(byte port) { - this.port = port; - } + /********* Types of interface encapsulations *****************/ + + public static final byte TYPE_ETHERNET = 0; + public static final byte TYPE_FIBER = 1; + + public static final byte PORT_TP = 1; + public static final byte PORT_AUI = 2; + public static final byte PORT_BNC = 4; + public static final byte PORT_MII = 8; + public static final byte PORT_FIBRE = 16; + + public static final byte SUPPORTED_10BaseT_Half = 1; + public static final byte SUPPORTED_10BaseT_Full = 2; + public static final byte SUPPORTED_100BaseT_Half = 4; + public static final byte SUPPORTED_100BaseT_Full = 8; + public static final byte SUPPORTED_1000BaseT_Half = 16; + public static final byte SUPPORTED_1000BaseT_Full = 32; + + public static final byte LINK_HALF = 0; + public static final byte LINK_DUPLEX = 1; + + + /** + * The name of the interface + */ + protected final String name; + + /** + * The encapsulation type + */ + protected byte encap = 0; + + /** + * The hardware address of the interface + */ + protected String hwAddr; + + protected byte supportedPorts = -1; // -1 means error + protected byte supportedLinkModes = -1; // -1 means error + protected boolean supportsAutoNegotiation = false; + + protected int maxSpeed = -1; // supported speed in Mb/s + protected byte duplex = -1; // -1 means unknown + protected byte port = -1; // -1 means unknown + + /** + * The constructor - only the name is final + */ + public InterfaceStatisticsStatic(final String name) { + super(); + this.name = name; + } + + /** + * Method used to retrieve the name of the interface + */ + public final String getName() { + return name; + } + + /** + * Getter for the type of encapsulation + */ + public final byte getEncap() { + return encap; + } + + /** + * Setter for the type of encapsulation for the network interface + */ + public final void setEncap(final byte type) { + this.encap = type; + } + + /** + * Getter for the type of encapsulation + */ + public final String getEncapAsString() { + switch (encap) { + case TYPE_ETHERNET: + return "Ethernet"; + case TYPE_FIBER: + return "Fiber"; + } + return "Unknown"; + } + + /** + * Getter for the hardware address + */ + public final String getHwAddr() { + return hwAddr; + } + + /** + * Setter for the hardware address + */ + public final void setHwAddr(final String hwAddr) { + this.hwAddr = hwAddr; + } + + /** + * Utility method for printing out the properties + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("IFNAME [name=").append(name); + buf.append(",encap=").append(getEncapAsString()); + buf.append(",hwaddr=").append(hwAddr); + buf.append(",supportedPort=").append(supportedPorts); + buf.append(",supportedLinkModes=").append(supportedLinkModes); + buf.append(",supportsAutoNeg=").append(supportsAutoNegotiation); + buf.append(",maxSpeed=").append(maxSpeed); + buf.append(",duplex=").append(duplex); + buf.append(",port=").append(port); + buf.append("]"); + return buf.toString(); + } + + public byte getSupportedPorts() { + return supportedPorts; + } + + public void setSupportedPorts(byte supportedPorts) { + if (this.supportedPorts < 0) this.supportedPorts = 0; + this.supportedPorts = (byte) (this.supportedPorts | supportedPorts); + } + + private boolean flagSet(byte b, byte flag) { + return ((b & flag) == flag); + } + + public String[] getSupportedPortsAsString() { + + if (supportedPorts < 0) return new String[]{"UNKNOWN"}; + + final LinkedList list = new LinkedList(); + if (flagSet(supportedPorts, PORT_TP)) list.addLast("TP"); + if (flagSet(supportedPorts, PORT_AUI)) list.addLast("AUI"); + if (flagSet(supportedPorts, PORT_BNC)) list.addLast("BNC"); + if (flagSet(supportedPorts, PORT_MII)) list.addLast("MII"); + if (flagSet(supportedPorts, PORT_FIBRE)) list.addLast("FIBRE"); + + if (list.size() == 0) return new String[]{"NONE"}; + return list.toArray(new String[0]); + } + + public byte getSupportedLinkModes() { + return supportedLinkModes; + } + + public void setSupportedLinkModes(byte supportedLinkModes) { + if (this.supportedLinkModes < 0) this.supportedLinkModes = 0; + this.supportedLinkModes = (byte) (this.supportedLinkModes | supportedLinkModes); + } + + public String[] getSupportedLinkModesAsString() { + if (supportedLinkModes < 0) return new String[]{"UNKNOWN"}; + final LinkedList list = new LinkedList(); + if (flagSet(supportedLinkModes, SUPPORTED_10BaseT_Half)) list.add("10BaseT_Half"); + if (flagSet(supportedLinkModes, SUPPORTED_10BaseT_Full)) list.add("10BaseT_Full"); + if (flagSet(supportedLinkModes, SUPPORTED_100BaseT_Half)) list.add("100BaseT_Half"); + if (flagSet(supportedLinkModes, SUPPORTED_100BaseT_Full)) list.add("100BaseT_Full"); + if (flagSet(supportedLinkModes, SUPPORTED_1000BaseT_Half)) list.add("1000BaseT_Half"); + if (flagSet(supportedLinkModes, SUPPORTED_1000BaseT_Full)) list.add("1000BaseT_Full"); + if (list.size() == 0) return new String[]{"NONE"}; + return list.toArray(new String[0]); + } + + public boolean supportsAutoNegotiation() { + return supportsAutoNegotiation; + } + + public void setSupportsAutoNegotiation(boolean supportsAutoNegotiation) { + this.supportsAutoNegotiation = supportsAutoNegotiation; + } + + public int getMaxSpeed() { + return maxSpeed; + } + + public void setMaxSpeed(int maxSpeed) { + this.maxSpeed = maxSpeed; + } + + public String getMaxSpeedAsString() { + if (maxSpeed < 0) + return "UNKNOWN"; + return maxSpeed + " Mb/s"; + } + + public byte getDuplex() { + return duplex; + } + + public void setDuplex(byte duplex) { + this.duplex = duplex; + } + + public byte getPort() { + return port; + } + + public void setPort(byte port) { + this.port = port; + } } // end of class InterfaceStatisticsStatic diff --git a/src/lia/util/net/copy/monitoring/lisa/net/dev/MTUSet.java b/src/lia/util/net/copy/monitoring/lisa/net/dev/MTUSet.java index 9a8223d..9d37e6f 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/dev/MTUSet.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/dev/MTUSet.java @@ -7,17 +7,18 @@ /** * Wrapper for sending request to set a new value for the mtu + * * @author Ciprian Dobre */ -public class MTUSet implements Serializable{ +public class MTUSet implements Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + public String ifName; + + public int mtu; - public String ifName; - - public int mtu; - } // end of class MTUSet diff --git a/src/lia/util/net/copy/monitoring/lisa/net/dev/TXQueueLenSet.java b/src/lia/util/net/copy/monitoring/lisa/net/dev/TXQueueLenSet.java index ee98c14..aa35d00 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/dev/TXQueueLenSet.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/dev/TXQueueLenSet.java @@ -7,18 +7,19 @@ /** * Wrapper for setting quelen... + * * @author Ciprian Dobre */ public class TXQueueLenSet implements Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - public String ifName; - - public int txqueuelen; - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + public String ifName; + + public int txqueuelen; + } // end of class TXQueueLenSet diff --git a/src/lia/util/net/copy/monitoring/lisa/net/netstat/Connection.java b/src/lia/util/net/copy/monitoring/lisa/net/netstat/Connection.java index fced7c0..6022546 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/netstat/Connection.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/netstat/Connection.java @@ -7,136 +7,155 @@ /** * Informations about a given connection - * + * * @author Ciprian Dobre */ public class Connection extends Statistics { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - /** Types of connection protocol ***/ - public static final byte TCP_CONNECTION = 0; - public static final byte UDP_CONNECTION = 1; - public static final byte RAW_CONNECTION = 2; - - /** The protocol of the connection (can be tcp, udp or raw) */ - protected byte protocol; - - /** The owner of the connection (username) */ - protected String powner; - - /** The pid of the owner process */ - protected int pid; - - /** The name of the program owning the connection */ - protected String pname; - - /** Local port */ - protected int localPort; - - /** Remote address of the connection */ - protected String remoteAddress; - - /** Remote port */ - protected int remotePort; - - /** Status of the connection */ - protected String status; - - public Connection() { - super(); - } - - public final void setProtocol(final byte protocol) { - this.protocol = protocol; - } - - public final byte getProtocol() { - return protocol; - } - - public final String getProtocolAsString() { - switch (protocol) { - case TCP_CONNECTION: return "TCP"; - case UDP_CONNECTION: return "UDP"; - case RAW_CONNECTION: return "RAW"; - } - return "UNKNOWN"; - } - - public final void setPOwner(final String owner) { - this.powner = owner; - } - - public final String getPOwner() { - return powner; - } - - public final void setPID(final int pid) { - this.pid = pid; - } - - public final int getPID() { - return pid; - } - - public final void setPName(final String pname) { - this.pname = pname; - } - - public final String getPName() { - return pname; - } - - public final void setLocalPort(final int localPort) { - this.localPort = localPort; - } - - public final int getLocalPort() { - return localPort; - } - - public final void setRemoteAddress(final String remoteAddress) { - this.remoteAddress = remoteAddress; - } - - public final String getRemoteAddress() { - return remoteAddress; - } - - public final void setRemotePort(final int remotePort) { - this.remotePort = remotePort; - } - - public final int getRemotePort() { - return remotePort; - } - - public final void setStatus(final String status) { - this.status = status; - } - - public final String getStatus() { - return status; - } - - public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append("[Prot=").append(getProtocolAsString()); - buf.append(",POwner=").append(powner); - buf.append(",PID=").append(pid); - buf.append(",PName=").append(pname); - buf.append(",LPort=").append(localPort); - buf.append(",RAddress=").append(remoteAddress); - buf.append(",RPort=").append(remotePort); - buf.append(",Status=").append(status); - buf.append("]"); - return buf.toString(); - } - + /** + * Types of connection protocol + ***/ + public static final byte TCP_CONNECTION = 0; + public static final byte UDP_CONNECTION = 1; + public static final byte RAW_CONNECTION = 2; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + /** + * The protocol of the connection (can be tcp, udp or raw) + */ + protected byte protocol; + + /** + * The owner of the connection (username) + */ + protected String powner; + + /** + * The pid of the owner process + */ + protected int pid; + + /** + * The name of the program owning the connection + */ + protected String pname; + + /** + * Local port + */ + protected int localPort; + + /** + * Remote address of the connection + */ + protected String remoteAddress; + + /** + * Remote port + */ + protected int remotePort; + + /** + * Status of the connection + */ + protected String status; + + public Connection() { + super(); + } + + public final byte getProtocol() { + return protocol; + } + + public final void setProtocol(final byte protocol) { + this.protocol = protocol; + } + + public final String getProtocolAsString() { + switch (protocol) { + case TCP_CONNECTION: + return "TCP"; + case UDP_CONNECTION: + return "UDP"; + case RAW_CONNECTION: + return "RAW"; + } + return "UNKNOWN"; + } + + public final String getPOwner() { + return powner; + } + + public final void setPOwner(final String owner) { + this.powner = owner; + } + + public final int getPID() { + return pid; + } + + public final void setPID(final int pid) { + this.pid = pid; + } + + public final String getPName() { + return pname; + } + + public final void setPName(final String pname) { + this.pname = pname; + } + + public final int getLocalPort() { + return localPort; + } + + public final void setLocalPort(final int localPort) { + this.localPort = localPort; + } + + public final String getRemoteAddress() { + return remoteAddress; + } + + public final void setRemoteAddress(final String remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public final int getRemotePort() { + return remotePort; + } + + public final void setRemotePort(final int remotePort) { + this.remotePort = remotePort; + } + + public final String getStatus() { + return status; + } + + public final void setStatus(final String status) { + this.status = status; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[Prot=").append(getProtocolAsString()); + buf.append(",POwner=").append(powner); + buf.append(",PID=").append(pid); + buf.append(",PName=").append(pname); + buf.append(",LPort=").append(localPort); + buf.append(",RAddress=").append(remoteAddress); + buf.append(",RPort=").append(remotePort); + buf.append(",Status=").append(status); + buf.append("]"); + return buf.toString(); + } + } // end of class Connection diff --git a/src/lia/util/net/copy/monitoring/lisa/net/netstat/Netstat.java b/src/lia/util/net/copy/monitoring/lisa/net/netstat/Netstat.java index ef105f8..2fc4784 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/netstat/Netstat.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/netstat/Netstat.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy.monitoring.lisa.net.netstat; +import lia.util.net.copy.monitoring.lisa.net.PatternUtil; + import java.io.BufferedReader; import java.io.FileReader; import java.io.PrintStream; @@ -12,108 +14,114 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import lia.util.net.copy.monitoring.lisa.net.PatternUtil; - /** * Replacer for the netstat utility, by reading the /proc filesystem it can find out the * open connections of the system * From http://www.ussg.iu.edu/hypermail/linux/kernel/0409.1/2166.html : * It will first list all listening TCP sockets, and next list all established - TCP connections. A typical entry of /proc/net/tcp would look like this (split - up into 3 parts because of the length of the line): - - 46: 010310AC:9C4C 030310AC:1770 01 - | | | | | |--> connection state - | | | | |------> remote TCP port number - | | | |-------------> remote IPv4 address - | | |--------------------> local TCP port number - | |---------------------------> local IPv4 address - |----------------------------------> number of entry - - 00000150:00000000 01:00000019 00000000 - | | | | |--> number of unrecovered RTO timeouts - | | | |----------> number of jiffies until timer expires - | | |----------------> timer_active (see below) - | |----------------------> receive-queue - |-------------------------------> transmit-queue - - 1000 0 54165785 4 cd1e6040 25 4 27 3 -1 - | | | | | | | | | |--> slow start size threshold, - | | | | | | | | | or -1 if the treshold - | | | | | | | | | is >= 0xFFFF - | | | | | | | | |----> sending congestion window - | | | | | | | |-------> (ack.quick<<1)|ack.pingpong - | | | | | | |---------> Predicted tick of soft clock - | | | | | | (delayed ACK control data) - | | | | | |------------> retransmit timeout - | | | | |------------------> location of socket in memory - | | | |-----------------------> socket reference count - | | |-----------------------------> inode - | |----------------------------------> unanswered 0-window probes - |---------------------------------------------> uid - * @deprecated - * + * TCP connections. A typical entry of /proc/net/tcp would look like this (split + * up into 3 parts because of the length of the line): + *

    + * 46: 010310AC:9C4C 030310AC:1770 01 + * | | | | | |--> connection state + * | | | | |------> remote TCP port number + * | | | |-------------> remote IPv4 address + * | | |--------------------> local TCP port number + * | |---------------------------> local IPv4 address + * |----------------------------------> number of entry + *

    + * 00000150:00000000 01:00000019 00000000 + * | | | | |--> number of unrecovered RTO timeouts + * | | | |----------> number of jiffies until timer expires + * | | |----------------> timer_active (see below) + * | |----------------------> receive-queue + * |-------------------------------> transmit-queue + *

    + * 1000 0 54165785 4 cd1e6040 25 4 27 3 -1 + * | | | | | | | | | |--> slow start size threshold, + * | | | | | | | | | or -1 if the treshold + * | | | | | | | | | is >= 0xFFFF + * | | | | | | | | |----> sending congestion window + * | | | | | | | |-------> (ack.quick<<1)|ack.pingpong + * | | | | | | |---------> Predicted tick of soft clock + * | | | | | | (delayed ACK control data) + * | | | | | |------------> retransmit timeout + * | | | | |------------------> location of socket in memory + * | | | |-----------------------> socket reference count + * | | |-----------------------------> inode + * | |----------------------------------> unanswered 0-window probes + * |---------------------------------------------> uid + * * @author Ciprian Dobre + * @deprecated */ public class Netstat { - /** Possible values for states in /proc/net/tcp */ - final String states[] = { "ESTBLSH", "SYNSENT", "SYNRECV", "FWAIT1", "FWAIT2", "TMEWAIT", - "CLOSED", "CLSWAIT", "LASTACK", "LISTEN", "CLOSING", "UNKNOWN" - }; - - /** Pattern used when parsing through /proc/net/tcp */ - private final String netPattern = "\\d+:\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+)\\s+"+ - "[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+\\s+([\\d]+)\\s+[\\d]+\\s+([\\d]+)"; - - /** The stream to write output to */ - protected final PrintStream out; - - protected final HashMap pids = new HashMap(); + /** + * The stream to write output to + */ + protected final PrintStream out; + protected final HashMap pids = new HashMap(); + /** + * Possible values for states in /proc/net/tcp + */ + final String states[] = {"ESTBLSH", "SYNSENT", "SYNRECV", "FWAIT1", "FWAIT2", "TMEWAIT", + "CLOSED", "CLSWAIT", "LASTACK", "LISTEN", "CLOSING", "UNKNOWN" + }; + /** + * Pattern used when parsing through /proc/net/tcp + */ + private final String netPattern = "\\d+:\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+)\\s+" + + "[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+\\s+([\\d]+)\\s+[\\d]+\\s+([\\d]+)"; // protected final HashMap inodes; - - /** The constructor - * @param properties We need the properties of the module in order to interogate for paths - * @param out The stream to write output to - */ - public Netstat(final PrintStream out) { - this.out = out; + + /** + * The constructor + * + * @param out The stream to write output to + */ + public Netstat(final PrintStream out) { + this.out = out; // inodes = new HashMap(); - } + } - /** Utility method that converts an address from a hexa representation as founded in /proc to String representation */ - private final String getAddress(final String hexa) { - try { - // first let's convert the address to Integer - final long v = Long.parseLong(hexa, 16); - // because in /proc the order is little endian and java uses big endian order we also need to invert the order - final long adr = (v >>> 24) | (v << 24) | - ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00); - // and now it's time to output the result - return ((adr >> 24) & 0xff) + "." + ((adr >> 16) & 0xff) + "." + ((adr >> 8) & 0xff) + "." + (adr & 0xff); - } catch (Exception ex) { - ex.printStackTrace(); - return "0.0.0.0"; - } - } + public static void main(String args[]) { + System.out.println("Start"); + Netstat n = new Netstat(System.out); + List c = n.getConnections(); + for (Connection conn : c) { + System.out.println(conn); + } + System.out.println("end"); + } - private final int getInt16(final String hexa) { - try { - return Integer.parseInt(hexa, 16); - } catch (Exception ex) { - ex.printStackTrace(); - return -1; - } - } - - /** Returns the path to stat utility as declared by the user */ - private final String getStatPath() { - String path = System.getProperty("net.stat.path", "/usr/bin/"); // if no properties than just use sbin - path = path.trim(); - if (!path.endsWith("/")) path = path + "/"; // make sure that path always ends in "/"; - return path; - } + /** + * Utility method that converts an address from a hexa representation as founded in /proc to String representation + */ + private final String getAddress(final String hexa) { + try { + // first let's convert the address to Integer + final long v = Long.parseLong(hexa, 16); + // because in /proc the order is little endian and java uses big endian order we also need to invert the order + final long adr = (v >>> 24) | (v << 24) | + ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00); + // and now it's time to output the result + return ((adr >> 24) & 0xff) + "." + ((adr >> 16) & 0xff) + "." + ((adr >> 8) & 0xff) + "." + (adr & 0xff); + } catch (Exception ex) { + ex.printStackTrace(); + return "0.0.0.0"; + } + } + + private final int getInt16(final String hexa) { + try { + return Integer.parseInt(hexa, 16); + } catch (Exception ex) { + ex.printStackTrace(); + return -1; + } + } // /** Method used to question the open processes on the system */ // private void init() { @@ -162,185 +170,193 @@ private final String getStatPath() { // // } - /** Utility method used to obtain the name of an user based on an uid */ - private final String getPUID(final String uid) { - if (pids.containsKey(uid)) - return pids.get(uid); - final String pat = "([\\S&&[^:]]+):[\\S&&[^:]]+:"+uid+":"; - final Pattern pattern = PatternUtil.getPattern("uid_"+uid, pat); - try { - BufferedReader in = new BufferedReader(new FileReader("/etc/passwd")); - String line; - while ((line = in.readLine()) != null) { - final Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - final String puid = matcher.group(1); - pids.put(uid, puid); - return puid; - } - } - in.close(); - } catch (Throwable t) { } - pids.put(uid, "UNKNOWN"); - return "UNKNOWN"; - } - - private final String getPName(final int pid) { - final String pat = "Name:\\s*(\\S+)"; - final Pattern pattern = PatternUtil.getPattern("pname", pat); - try { - BufferedReader in = new BufferedReader(new FileReader("/proc/"+pid+"/status")); - String line; - while ((line = in.readLine()) != null) { - final Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } - } - in.close(); - } catch (Throwable t) { } - return "UNKNOWN"; - } - - /** - * Method used to question for the connections currently openned - * @return The list of connections (as Connection objects) - */ - public List getConnections() { - - final ArrayList net = new ArrayList(); + /** + * Returns the path to stat utility as declared by the user + */ + private final String getStatPath() { + String path = System.getProperty("net.stat.path", "/usr/bin/"); // if no properties than just use sbin + path = path.trim(); + if (!path.endsWith("/")) path = path + "/"; // make sure that path always ends in "/"; + return path; + } + + /** + * Utility method used to obtain the name of an user based on an uid + */ + private final String getPUID(final String uid) { + if (pids.containsKey(uid)) + return pids.get(uid); + final String pat = "([\\S&&[^:]]+):[\\S&&[^:]]+:" + uid + ":"; + final Pattern pattern = PatternUtil.getPattern("uid_" + uid, pat); + try { + BufferedReader in = new BufferedReader(new FileReader("/etc/passwd")); + String line; + while ((line = in.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + final String puid = matcher.group(1); + pids.put(uid, puid); + return puid; + } + } + in.close(); + } catch (Throwable t) { + } + pids.put(uid, "UNKNOWN"); + return "UNKNOWN"; + } + + private final String getPName(final int pid) { + final String pat = "Name:\\s*(\\S+)"; + final Pattern pattern = PatternUtil.getPattern("pname", pat); + try { + BufferedReader in = new BufferedReader(new FileReader("/proc/" + pid + "/status")); + String line; + while ((line = in.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(1); + } + } + in.close(); + } catch (Throwable t) { + } + return "UNKNOWN"; + } + + /** + * Method used to question for the connections currently openned + * + * @return The list of connections (as Connection objects) + */ + public List getConnections() { + + final ArrayList net = new ArrayList(); // init(); - - // read from /proc/net/tcp the list of currently openned socket connections - try { - BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp")); - String line; - while ((line = in.readLine()) != null) { - Pattern pattern = PatternUtil.getPattern("/proc/net/tcp", netPattern); - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - final Connection c = new Connection(); - c.setProtocol(Connection.TCP_CONNECTION); - net.add(c); - final String localPortHexa = matcher.group(2); - final String remoteAddressHexa = matcher.group(3); - final String remotePortHexa = matcher.group(4); - final String statusHexa = matcher.group(5); - final String uid = matcher.group(6); - final String inode = matcher.group(7); - c.setLocalPort(getInt16(localPortHexa)); - c.setRemoteAddress(getAddress(remoteAddressHexa)); - c.setRemotePort(getInt16(remotePortHexa)); - try { - c.setStatus(states[Integer.parseInt(statusHexa, 16) - 1]); - } catch (Exception ex) { - c.setStatus(states[11]); // unknwon - } -// if (inodes.containsKey(inode)) { + + // read from /proc/net/tcp the list of currently openned socket connections + try { + BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp")); + String line; + while ((line = in.readLine()) != null) { + Pattern pattern = PatternUtil.getPattern("/proc/net/tcp", netPattern); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + final Connection c = new Connection(); + c.setProtocol(Connection.TCP_CONNECTION); + net.add(c); + final String localPortHexa = matcher.group(2); + final String remoteAddressHexa = matcher.group(3); + final String remotePortHexa = matcher.group(4); + final String statusHexa = matcher.group(5); + final String uid = matcher.group(6); + final String inode = matcher.group(7); + c.setLocalPort(getInt16(localPortHexa)); + c.setRemoteAddress(getAddress(remoteAddressHexa)); + c.setRemotePort(getInt16(remotePortHexa)); + try { + c.setStatus(states[Integer.parseInt(statusHexa, 16) - 1]); + } catch (Exception ex) { + c.setStatus(states[11]); // unknwon + } +// if (inodes.containsKey(inode)) { // c.setPID(inodes.get(inode)); // c.setPName(getPName(c.getPID())); // } else { - c.setPID(-1); // unknown - c.setPName("UNKNOWN"); + c.setPID(-1); // unknown + c.setPName("UNKNOWN"); // } - c.setPOwner(getPUID(uid)); - } - } - in.close(); - } catch (Throwable t) { } - - // read from /proc/net/udp the list of currently openned socket connections - try { - BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp")); - String line; - while ((line = in.readLine()) != null) { - Pattern pattern = PatternUtil.getPattern("/proc/net/tcp", netPattern); - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - final Connection c = new Connection(); - c.setProtocol(Connection.UDP_CONNECTION); - net.add(c); - final String localPortHexa = matcher.group(2); - final String remoteAddressHexa = matcher.group(3); - final String remotePortHexa = matcher.group(4); - final String statusHexa = matcher.group(5); - final String uid = matcher.group(6); - final String inode = matcher.group(7); - c.setLocalPort(getInt16(localPortHexa)); - c.setRemoteAddress(getAddress(remoteAddressHexa)); - c.setRemotePort(getInt16(remotePortHexa)); - try { - c.setStatus(states[Integer.parseInt(statusHexa, 16) - 1]); - } catch (Exception ex) { - c.setStatus(states[11]); // unknwon - } -// if (inodes.containsKey(inode)) { + c.setPOwner(getPUID(uid)); + } + } + in.close(); + } catch (Throwable t) { + } + + // read from /proc/net/udp the list of currently openned socket connections + try { + BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp")); + String line; + while ((line = in.readLine()) != null) { + Pattern pattern = PatternUtil.getPattern("/proc/net/tcp", netPattern); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + final Connection c = new Connection(); + c.setProtocol(Connection.UDP_CONNECTION); + net.add(c); + final String localPortHexa = matcher.group(2); + final String remoteAddressHexa = matcher.group(3); + final String remotePortHexa = matcher.group(4); + final String statusHexa = matcher.group(5); + final String uid = matcher.group(6); + final String inode = matcher.group(7); + c.setLocalPort(getInt16(localPortHexa)); + c.setRemoteAddress(getAddress(remoteAddressHexa)); + c.setRemotePort(getInt16(remotePortHexa)); + try { + c.setStatus(states[Integer.parseInt(statusHexa, 16) - 1]); + } catch (Exception ex) { + c.setStatus(states[11]); // unknwon + } +// if (inodes.containsKey(inode)) { // c.setPID(inodes.get(inode)); // c.setPName(getPName(c.getPID())); // } else { - c.setPID(-1); // unknown - c.setPName("UNKNOWN"); + c.setPID(-1); // unknown + c.setPName("UNKNOWN"); // } - c.setPOwner(getPUID(uid)); - } - } - in.close(); - } catch (Throwable t) { } - - // read from /proc/net/raw the list of currently openned socket connections - try { - BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw")); - String line; - while ((line = in.readLine()) != null) { - Pattern pattern = PatternUtil.getPattern("/proc/net/tcp", netPattern); - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - final Connection c = new Connection(); - c.setProtocol(Connection.RAW_CONNECTION); - net.add(c); + c.setPOwner(getPUID(uid)); + } + } + in.close(); + } catch (Throwable t) { + } + + // read from /proc/net/raw the list of currently openned socket connections + try { + BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw")); + String line; + while ((line = in.readLine()) != null) { + Pattern pattern = PatternUtil.getPattern("/proc/net/tcp", netPattern); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + final Connection c = new Connection(); + c.setProtocol(Connection.RAW_CONNECTION); + net.add(c); // final String localAddressHexa = matcher.group(1); - final String localPortHexa = matcher.group(2); - final String remoteAddressHexa = matcher.group(3); - final String remotePortHexa = matcher.group(4); - final String statusHexa = matcher.group(5); - final String uid = matcher.group(6); - final String inode = matcher.group(7); - c.setLocalPort(getInt16(localPortHexa)); - c.setRemoteAddress(getAddress(remoteAddressHexa)); - c.setRemotePort(getInt16(remotePortHexa)); - try { - c.setStatus(states[Integer.parseInt(statusHexa, 16) - 1]); - } catch (Exception ex) { - c.setStatus(states[11]); // unknwon - } -// if (inodes.containsKey(inode)) { + final String localPortHexa = matcher.group(2); + final String remoteAddressHexa = matcher.group(3); + final String remotePortHexa = matcher.group(4); + final String statusHexa = matcher.group(5); + final String uid = matcher.group(6); + final String inode = matcher.group(7); + c.setLocalPort(getInt16(localPortHexa)); + c.setRemoteAddress(getAddress(remoteAddressHexa)); + c.setRemotePort(getInt16(remotePortHexa)); + try { + c.setStatus(states[Integer.parseInt(statusHexa, 16) - 1]); + } catch (Exception ex) { + c.setStatus(states[11]); // unknwon + } +// if (inodes.containsKey(inode)) { // c.setPID(inodes.get(inode)); // c.setPName(getPName(c.getPID())); // } else { - c.setPID(-1); // unknown - c.setPName("UNKNOWN"); + c.setPID(-1); // unknown + c.setPName("UNKNOWN"); // } - c.setPOwner(getPUID(uid)); - } - } - in.close(); - } catch (Throwable t) { } - - return net; - } - - public static void main(String args[]) { - System.out.println("Start"); - Netstat n = new Netstat(System.out); - List c = n.getConnections(); - for (Connection conn : c) { - System.out.println(conn); - } - System.out.println("end"); - } - - + c.setPOwner(getPUID(uid)); + } + } + in.close(); + } catch (Throwable t) { + } + + return net; + } + + } // end of class Netstat diff --git a/src/lia/util/net/copy/monitoring/lisa/net/netstat/NetstatHandler.java b/src/lia/util/net/copy/monitoring/lisa/net/netstat/NetstatHandler.java index d086603..6e72171 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/netstat/NetstatHandler.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/netstat/NetstatHandler.java @@ -3,6 +3,10 @@ */ package lia.util.net.copy.monitoring.lisa.net.netstat; +import lia.util.net.copy.monitoring.lisa.cmdExec; +import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; +import lia.util.net.copy.monitoring.lisa.net.PatternUtil; + import java.io.BufferedReader; import java.io.FileReader; import java.io.PrintStream; @@ -12,273 +16,283 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import lia.util.net.copy.monitoring.lisa.cmdExec; -import lia.util.net.copy.monitoring.lisa.cmdExec.CommandResult; -import lia.util.net.copy.monitoring.lisa.net.PatternUtil; - /** - * * Handler task for monitoring the current connections - * - * @author Ciprian Dobre + * + * @author Ciprian Dobre */ public class NetstatHandler { - /** The stream to write output to */ - protected final PrintStream out; + /** + * The stream to write output to + */ + protected final PrintStream out; - /** Utility object used for running different commands */ - protected final cmdExec exec; + /** + * Utility object used for running different commands + */ + protected final cmdExec exec; - /** Pattern used to parse the output of netstat utility */ - protected final String netstatPattern = "\\S+\\s+\\d+\\s+\\d+\\s*(\\d*[\\.:]\\d*[\\.:]\\d*\\.*\\d*):(\\d+)\\s*" + - "(\\d*[\\.:]\\d*[\\.:]\\d*\\.*\\d*):([\\d\\*]+)\\s*([a-zA-Z]*)\\s*(\\d+)\\s*\\d+\\s*([\\d-]+)/*(\\S*)"; - - protected final Logger logger; + /** + * Pattern used to parse the output of netstat utility + */ + protected final String netstatPattern = "\\S+\\s+\\d+\\s+\\d+\\s*(\\d*[\\.:]\\d*[\\.:]\\d*\\.*\\d*):(\\d+)\\s*" + + "(\\d*[\\.:]\\d*[\\.:]\\d*\\.*\\d*):([\\d\\*]+)\\s*([a-zA-Z]*)\\s*(\\d+)\\s*\\d+\\s*([\\d-]+)/*(\\S*)"; - protected boolean netstatPathSetup = false; - protected String netstatPath = null; - - /** - * The constructor - * @param properties We need the properties of the module in order to interogate for paths - * @param out The stream to write output to - */ - public NetstatHandler(final PrintStream out, final Logger logger) { - this.logger = logger; - this.out = out; - exec = cmdExec.getInstance(); - } - - /** Returns the path to netstat utility as declared by the user */ - private synchronized final String getNetstatPath() { - String path = System.getProperty("net.netstat.path", "/bin,/sbin,/usr/bin,/usr/sbin"); - if (path != null && path.length() != 0) - path = path.replace(',', ':').trim(); - return path; - } - - /** Utility method used to obtain the name of an user based on an uid */ - private final String getPUID(final String uid) { - final String pat = "([\\S&&[^:]]+):[\\S&&[^:]]+:"+uid+":"; - final Pattern pattern = PatternUtil.getPattern("uid_"+uid, pat); - try { - BufferedReader reader = new BufferedReader(new FileReader("/etc/passwd")); - String line; - while ((line = reader.readLine()) != null) { - final Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } - } - } catch (Exception e) { - } - return "UNKNOWN"; - } + protected final Logger logger; - /** - * Called in order to check if we should use netstat utility - * @return False if net.netstat.use is set to false - */ - private final boolean shouldRunNetstatTool() { - String prop = System.getProperty("net.netstat.use", null); - if (prop == null) return true; - try { - return Boolean.getBoolean(prop); - } catch (Throwable t) { } - return true; - } + protected boolean netstatPathSetup = false; + protected String netstatPath = null; - /** - * Called in order to check if we should try and parse from proc the current connections - * @return False if net.netstat_dev.use is set to false - */ - private final boolean shouldRunNetstatDevTool() { - String prop = System.getProperty("net.netstat_dev.use", null); - if (prop == null) return true; - try { - return Boolean.getBoolean(prop); - } catch (Throwable t) { } - return true; - } + /** + * The constructor + * + * @param properties We need the properties of the module in order to interogate for paths + * @param out The stream to write output to + */ + public NetstatHandler(final PrintStream out, final Logger logger) { + this.logger = logger; + this.out = out; + exec = cmdExec.getInstance(); + } - - /** - * Method used to question for the connections currently openned - * @return The list of connections (as Connection objects) - */ - public final List getConnections() { - - if (!shouldRunNetstatTool()) { - return null; - } - final ArrayList net = new ArrayList(); - - String netp = getNetstatPath(); - CommandResult cmdRes = exec.executeCommandReality("netstat -antep", null, 5 * 60 * 1000L, netp); - String ret = cmdRes.getOutput(); - if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { - if (out != null) { - out.println(ret); - out.println("Error running netstat"); - } else { - logger.info(ret); - logger.info("Error running netstat"); - } - return null; - } - final Pattern pattern = PatternUtil.getPattern("netstat", netstatPattern); - String lines[] = ret.split("\n"); - for (int i=0; i c = n.getConnections(); - for (Connection conn : c) { - System.out.println(conn); - } - System.out.println("end"); - } - } + + for (int i = 0; i < 10; i++) { + System.out.println("Start"); + NetstatHandler n = new NetstatHandler(System.out, null); + List c = n.getConnections(); + for (Connection conn : c) { + System.out.println(conn); + } + System.out.println("end"); + } + } + + /** + * Returns the path to netstat utility as declared by the user + */ + private synchronized final String getNetstatPath() { + String path = System.getProperty("net.netstat.path", "/bin,/sbin,/usr/bin,/usr/sbin"); + if (path != null && path.length() != 0) + path = path.replace(',', ':').trim(); + return path; + } + + /** + * Utility method used to obtain the name of an user based on an uid + */ + private final String getPUID(final String uid) { + final String pat = "([\\S&&[^:]]+):[\\S&&[^:]]+:" + uid + ":"; + final Pattern pattern = PatternUtil.getPattern("uid_" + uid, pat); + try { + BufferedReader reader = new BufferedReader(new FileReader("/etc/passwd")); + String line; + while ((line = reader.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(1); + } + } + } catch (Exception e) { + } + return "UNKNOWN"; + } + + /** + * Called in order to check if we should use netstat utility + * + * @return False if net.netstat.use is set to false + */ + private final boolean shouldRunNetstatTool() { + String prop = System.getProperty("net.netstat.use", null); + if (prop == null) return true; + try { + return Boolean.getBoolean(prop); + } catch (Throwable t) { + } + return true; + } + + /** + * Called in order to check if we should try and parse from proc the current connections + * + * @return False if net.netstat_dev.use is set to false + */ + private final boolean shouldRunNetstatDevTool() { + String prop = System.getProperty("net.netstat_dev.use", null); + if (prop == null) return true; + try { + return Boolean.getBoolean(prop); + } catch (Throwable t) { + } + return true; + } + + /** + * Method used to question for the connections currently openned + * + * @return The list of connections (as Connection objects) + */ + public final List getConnections() { + + if (!shouldRunNetstatTool()) { + return null; + } + final ArrayList net = new ArrayList(); + + String netp = getNetstatPath(); + CommandResult cmdRes = exec.executeCommandReality("netstat -antep", null, 5 * 60 * 1000L, netp); + String ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) { + out.println(ret); + out.println("Error running netstat"); + } else { + logger.info(ret); + logger.info("Error running netstat"); + } + return null; + } + final Pattern pattern = PatternUtil.getPattern("netstat", netstatPattern); + String lines[] = ret.split("\n"); + for (int i = 0; i < lines.length; i++) { + final Matcher matcher = pattern.matcher(lines[i]); + if (matcher.find()) { + final Connection c = new Connection(); + net.add(c); + c.setProtocol(Connection.TCP_CONNECTION); + final String localPort = matcher.group(2); + final String remoteAddress = matcher.group(3); + final String remotePort = matcher.group(4); + final String state = matcher.group(5); + final String uid = matcher.group(6); + final String pid = matcher.group(7); + final String pname = matcher.group(8); + c.setPOwner(getPUID(uid)); + try { + c.setPID(Integer.parseInt(pid)); + } catch (Exception ex) { + c.setPID(-1); + } + c.setPName(pname); + try { + c.setLocalPort(Integer.parseInt(localPort)); + } catch (Exception ex) { + c.setLocalPort(0); + } + c.setRemoteAddress(remoteAddress); + try { + c.setRemotePort(Integer.parseInt(remotePort)); + } catch (Exception ex) { + c.setRemotePort(0); + } + c.setStatus(state); + } + } + cmdRes = exec.executeCommandReality("netstat -anuep", null, 5 * 60 * 1000L, netp); + ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) { + out.println(ret); + out.println("Error running netstat"); + } else { + logger.info(ret); + logger.info("Error running netstat"); + } + return net; + } + lines = ret.split("\n"); + for (int i = 0; i < lines.length; i++) { + final Matcher matcher = pattern.matcher(lines[i]); + if (matcher.find()) { + final Connection c = new Connection(); + net.add(c); + c.setProtocol(Connection.UDP_CONNECTION); + final String localPort = matcher.group(2); + final String remoteAddress = matcher.group(3); + final String remotePort = matcher.group(4); + final String state = matcher.group(5); + final String uid = matcher.group(6); + final String pid = matcher.group(7); + final String pname = matcher.group(8); + c.setPOwner(getPUID(uid)); + try { + c.setPID(Integer.parseInt(pid)); + } catch (Exception ex) { + c.setPID(-1); + } + c.setPName(pname); + try { + c.setLocalPort(Integer.parseInt(localPort)); + } catch (Exception ex) { + c.setLocalPort(0); + } + c.setRemoteAddress(remoteAddress); + try { + c.setRemotePort(Integer.parseInt(remotePort)); + } catch (Exception ex) { + c.setRemotePort(0); + } + c.setStatus(state); + } + } + cmdRes = exec.executeCommandReality("netstat -anwep", null, 5 * 60 * 1000L, netp); + ret = cmdRes.getOutput(); + if (cmdRes.failed() || ret == null || ret.length() == 0 || PatternUtil.getPattern("Unknown command", null).matcher(ret).matches()) { + if (out != null) { + out.println(ret); + out.println("Error running netstat"); + } else { + logger.info(ret); + logger.info("Error running netstat"); + } + return net; + } + lines = ret.split("\n"); + for (int i = 0; i < lines.length; i++) { + final Matcher matcher = pattern.matcher(lines[i]); + if (matcher.find()) { + + final Connection c = new Connection(); + net.add(c); + c.setProtocol(Connection.RAW_CONNECTION); + final String localPort = matcher.group(2); + final String remoteAddress = matcher.group(3); + final String remotePort = matcher.group(4); + final String state = matcher.group(5); + final String uid = matcher.group(6); + final String pid = matcher.group(7); + final String pname = matcher.group(8); + c.setPOwner(getPUID(uid)); + try { + c.setPID(Integer.parseInt(pid)); + } catch (Exception ex) { + c.setPID(-1); + } + c.setPName(pname); + try { + c.setLocalPort(Integer.parseInt(localPort)); + } catch (Exception ex) { + c.setLocalPort(0); + } + c.setRemoteAddress(remoteAddress); + try { + c.setRemotePort(Integer.parseInt(remotePort)); + } catch (Exception ex) { + c.setRemotePort(0); + } + c.setStatus(state); + } + } + + return net; + } } // end of class NetstatHandler diff --git a/src/lia/util/net/copy/monitoring/lisa/net/statistics/IPStatistics.java b/src/lia/util/net/copy/monitoring/lisa/net/statistics/IPStatistics.java index 73290fd..d7cf3f0 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/statistics/IPStatistics.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/statistics/IPStatistics.java @@ -3,444 +3,464 @@ */ package lia.util.net.copy.monitoring.lisa.net.statistics; -import java.text.NumberFormat; - import lia.util.net.copy.monitoring.lisa.net.Statistics; +import java.text.NumberFormat; + /** * Statistics regarding the ip protocol suite - * + * * @author Ciprian Dobre */ public class IPStatistics extends Statistics { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - /** Is ip forwarding enabled or disabled */ - protected boolean forwarding = false; - - /** Default TTL value */ - protected long defaultTTL = 0L; - - /** Total number of received packets */ - protected String inReceives = "0"; - protected double inReceivesI = 0D; - - /** Packets received with incorrect headers */ - protected String inHdrErrors = "0"; - protected double inHdrErrorsI = 0D; - - /** Packets received with invalid address field */ - protected String inAddrErrors = "0"; - protected double inAddrErrorsI = 0D; - - /** Number of forwarded datagrams */ - protected String forwDatagrams = "0"; - protected double forwDatagramsI = 0D; - - /** Number of packets with unknown protocol number */ - protected String inUnknownProtos = "0"; - protected double inUnknownProtosI = 0D; - - /** The number of discarded packets */ - protected String inDiscards = "0"; - protected double inDiscardsI = 0D; - - /** The number of packets delivered */ - protected String inDelivers = "0"; - protected double inDeliversI = 0D; - - /** Requests sent out */ - protected String outRequests = "0"; - protected double outRequestsI = 0D; - - /** Outgoing packets dropped */ - protected String outDiscards = "0"; - protected double outDiscardsI = 0D; - - /** Outgoing dropped packets because of missing route */ - protected String outNoRoutes = "0"; - protected double outNoRoutesI = 0D; - - /** Fragments dropped after time out */ - protected String reasmTimeout = "0"; - protected double reasmTimeoutI = 0D; - - /** Reassemblies requires */ - protected String reasmReqds = "0"; - protected double reasmReqdsI = 0D; - - /** Packets reassembled ok */ - protected String reasmOKs = "0"; - protected double reasmOKsI = 0D; - - /** Packets reassembled with failure */ - protected String reasmFails = "0"; - protected double reasmFailsI = 0D; - - /** Fragments received ok */ - protected String fragOKs = "0"; - protected double fragOKsI = 0D; - - /** Fragments failed */ - protected String fragFails = "0"; - protected double fragFailsI = 0D; - - /** Created fragments */ - protected String fragCreates = "0"; - protected double fragCreatesI = 0D; - - public IPStatistics() { - super(); - } - - public final void setForwarding(final boolean forwarding) { - this.forwarding = forwarding; - } - - public final boolean getForwarding() { - return forwarding; - } - - public final String getForwardingAsString() { - return "Forwarding is "+(forwarding ? "enabled" : "disabled"); - } - - public final void setDefaultTTL(final long defaultTTL) { - this.defaultTTL = defaultTTL; - } - - public final long getDefaultTTL() { - return defaultTTL; - } - - public final String getDefaultTTLAsString() { - return "Default TTL is "+defaultTTL; - } - - public final void setInReceived(final String packetsReceived, final double packetsReceivedI) { - this.inReceives = packetsReceived; - this.inReceivesI = packetsReceivedI; - } - - public final String getInReceived() { - return inReceives; - } - - public final double getInReceivedI() { - return inReceivesI; - } - - static final NumberFormat nf = NumberFormat.getInstance(); - static { - nf.setMaximumFractionDigits(2); - nf.setMinimumFractionDigits(2); - } - - public final String getInReceivedAsString() { - return inReceives+" total packets received ["+nf.format(inReceivesI)+"]"; - } - - public final void setInHdrErrors(final String hdrErrors, final double hdrErrorsI) { - this.inHdrErrors = hdrErrors; - this.inHdrErrorsI = hdrErrorsI; - } - - public final String getInHdrErrors() { - return inHdrErrors; - } - - public final double getInHdrErrorsI() { - return inHdrErrorsI; - } - - public final String getInHdrErrorsAsString() { - return inHdrErrors+" with invalid headers"; - } - - public final void setInAddrErrors(final String inAddrErrors, final double inAddrErrorsI) { - this.inAddrErrors = inAddrErrors; - this.inAddrErrorsI = inAddrErrorsI; - } - - public final String getInAddrErrors() { - return inAddrErrors; - } - - public final double getInAddrErrorsI() { - return inAddrErrorsI; - } - - public final String getInAddrErrorsAsString() { - return inAddrErrors+" with invalid addresses"; - } - - public final void setForwDatagrams(final String forwDatagrams, final double forwDatagramsI) { - this.forwDatagrams = forwDatagrams; - this.forwDatagramsI = forwDatagramsI; - } - - public final String getForwDatagrams() { - return forwDatagrams; - } - - public final double getForwDatagramsI() { - return forwDatagramsI; - } - - public final String getForwDatagramsAsString() { - return forwarding+" datagrams forwarded"; - } - - public final void setInUnknownProtos(final String inUnknownProtos, final double inUnknownProtosI) { - this.inUnknownProtos = inUnknownProtos; - this.inUnknownProtosI = inUnknownProtosI; - } - - public final String getInUnknownProtos() { - return inUnknownProtos; - } - - public final double getInUnknownProtosI() { - return inUnknownProtosI; - } - - public final String getInUnknownProtosAsString() { - return inUnknownProtos+" with unknown protocol"; - } - - public final void setInDiscards(final String inDiscards, final double inDiscardsI) { - this.inDiscards = inDiscards; - this.inDiscardsI = inDiscardsI; - } - - public final String getInDiscards() { - return inDiscards; - } - - public final double getInDiscardsI() { - return inDiscardsI; - } - - public final String getInDiscardsAsString() { - return inDiscards+" incoming packets discarded"; - } - - public final void setInDelivers(final String inDelivers, final double inDeliversI) { - this.inDelivers = inDelivers; - this.inDeliversI = inDeliversI; - } - - public final String getInDelivers() { - return inDelivers; - } - - public final double getInDeliversI() { - return inDeliversI; - } - - public final String getInDeliversAsString() { - return inDelivers+" incoming packets delivered"; - } - - public final void setOutRequests(final String outRequests, final double outRequestsI) { - this.outRequests = outRequests; - this.outRequestsI = outRequestsI; - } - - public final String getOutRequests() { - return outRequests; - } - - public final double getOutRequestsI() { - return outRequestsI; - } - - public final String getOutRequestsAsString() { - return outRequests+" requests sent out"; - } - - public final void setOutDiscards(final String outDiscards, final double outDiscardsI) { - this.outDiscards = outDiscards; - this.outDiscardsI = outDiscardsI; - } - - public final String getOutDiscards() { - return outDiscards; - } - - public final double getOutDiscardsI() { - return outDiscardsI; - } - - public final String getOutDiscardsAsString() { - return outDiscards+" outgoing packets dropped"; - } - - public final void setOutNoRoutes(final String outNoRoutes, final double outNoRoutesI) { - this.outNoRoutes = outNoRoutes; - this.outNoRoutesI = outNoRoutesI; - } - - public final String getOutNoRoutes() { - return outNoRoutes; - } - - public final double getOutNoRoutesI() { - return outNoRoutesI; - } - - public final String getOutNoRoutesAsString() { - return outNoRoutes+" dropped because of missing route"; - } - - public final void setReasmTimeout(final String reasmTimeout, final double reasmTimeoutI) { - this.reasmTimeout = reasmTimeout; - this.reasmTimeoutI = reasmTimeoutI; - } - - public final String getReasmTimeout() { - return reasmTimeout; - } - - public final double getReasmTimeoutI() { - return reasmTimeoutI; - } - - public final String getReasmTimeoutAsString() { - return reasmTimeout+" fragments dropped after timeout"; - } - - public final void setReasmReqds(final String reasmReqds, final double reasmReqdsI) { - this.reasmReqds = reasmReqds; - this.reasmReqdsI = reasmReqdsI; - } - - public final String getReasmReqds() { - return reasmReqds; - } - - public final double getReasmReqdsI() { - return reasmReqdsI; - } - - public final String getReasmReqdsAsString() { - return reasmReqds+" reassemblies required"; - } - - public final void setReasmOKs(final String reasmOKs, final double reasmOKsI) { - this.reasmOKs = reasmOKs; - this.reasmOKsI = reasmOKsI; - } - - public final String getReasmOKs() { - return reasmOKs; - } - - public final double getReasmOKsI() { - return reasmOKsI; - } - - public final String getReasmOKsAsString() { - return reasmOKs+" packets reassembled ok"; - } - - public final void setReasmFails(final String reasmFails, final double reasmFailsI) { - this.reasmFails = reasmFails; - this.reasmFailsI = reasmFailsI; - } - - public final String getReasmFails() { - return reasmFails; - } - - public final double getReasmFailsI() { - return reasmFailsI; - } - - public final String getReasmFailsAsString() { - return reasmFails+" packet reassembles failed"; - } - - public final void setFragOKs(final String fragOKs, final double fragOKsI) { - this.fragOKs = fragOKs; - this.fragOKsI = fragOKsI; - } - - public final String getFragOKs() { - return fragOKs; - } - - public final double getFragOKsI() { - return fragOKsI; - } - - public final String getFragOKsAsString() { - return fragOKs+" fragments received ok"; - } - - public final void setFragFails(final String fragFails, final double fragFailsI) { - this.fragFails = fragFails; - this.fragFailsI = fragFailsI; - } - - public final String getFragFails() { - return fragFails; - } - - public final double getFragFailsI() { - return fragFailsI; - } - - public final String getFragFailsAsString() { - return fragFails+" fragments failed"; - } - - public final void setFragCreates(final String fragCreates, final double fragCreatesI) { - this.fragCreates = fragCreates; - this.fragCreatesI = fragCreatesI; - } - - public final String getFragCreates() { - return fragCreates; - } - - public final double getFragCreatesI() { - return fragCreatesI; - } - - public final String getFragCreatesAsString() { - return fragCreates+" fragments created"; - } - - public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append("IP Statistics:\n"); - buf.append(getForwardingAsString()).append("\n"); - buf.append(getDefaultTTLAsString()).append("\n"); - buf.append(getInReceivedAsString()).append("\n"); - buf.append(getInHdrErrorsAsString()).append("\n"); - buf.append(getInAddrErrorsAsString()).append("\n"); - buf.append(getForwDatagramsAsString()).append("\n"); - buf.append(getInUnknownProtosAsString()).append("\n"); - buf.append(getInDiscardsAsString()).append("\n"); - buf.append(getInDeliversAsString()).append("\n"); - buf.append(getOutRequestsAsString()).append("\n"); - buf.append(getOutDiscardsAsString()).append("\n"); - buf.append(getOutNoRoutesAsString()).append("\n"); - buf.append(getReasmTimeoutAsString()).append("\n"); - buf.append(getReasmReqdsAsString()).append("\n"); - buf.append(getReasmOKsAsString()).append("\n"); - buf.append(getReasmFailsAsString()).append("\n"); - buf.append(getFragOKsAsString()).append("\n"); - buf.append(getFragFailsAsString()).append("\n"); - buf.append(getFragCreatesAsString()).append("\n"); - return buf.toString(); - } - + static final NumberFormat nf = NumberFormat.getInstance(); + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + static { + nf.setMaximumFractionDigits(2); + nf.setMinimumFractionDigits(2); + } + + /** + * Is ip forwarding enabled or disabled + */ + protected boolean forwarding = false; + /** + * Default TTL value + */ + protected long defaultTTL = 0L; + /** + * Total number of received packets + */ + protected String inReceives = "0"; + protected double inReceivesI = 0D; + /** + * Packets received with incorrect headers + */ + protected String inHdrErrors = "0"; + protected double inHdrErrorsI = 0D; + /** + * Packets received with invalid address field + */ + protected String inAddrErrors = "0"; + protected double inAddrErrorsI = 0D; + /** + * Number of forwarded datagrams + */ + protected String forwDatagrams = "0"; + protected double forwDatagramsI = 0D; + /** + * Number of packets with unknown protocol number + */ + protected String inUnknownProtos = "0"; + protected double inUnknownProtosI = 0D; + /** + * The number of discarded packets + */ + protected String inDiscards = "0"; + protected double inDiscardsI = 0D; + /** + * The number of packets delivered + */ + protected String inDelivers = "0"; + protected double inDeliversI = 0D; + /** + * Requests sent out + */ + protected String outRequests = "0"; + protected double outRequestsI = 0D; + /** + * Outgoing packets dropped + */ + protected String outDiscards = "0"; + protected double outDiscardsI = 0D; + /** + * Outgoing dropped packets because of missing route + */ + protected String outNoRoutes = "0"; + protected double outNoRoutesI = 0D; + /** + * Fragments dropped after time out + */ + protected String reasmTimeout = "0"; + protected double reasmTimeoutI = 0D; + /** + * Reassemblies requires + */ + protected String reasmReqds = "0"; + protected double reasmReqdsI = 0D; + /** + * Packets reassembled ok + */ + protected String reasmOKs = "0"; + protected double reasmOKsI = 0D; + /** + * Packets reassembled with failure + */ + protected String reasmFails = "0"; + protected double reasmFailsI = 0D; + /** + * Fragments received ok + */ + protected String fragOKs = "0"; + protected double fragOKsI = 0D; + /** + * Fragments failed + */ + protected String fragFails = "0"; + protected double fragFailsI = 0D; + /** + * Created fragments + */ + protected String fragCreates = "0"; + protected double fragCreatesI = 0D; + + public IPStatistics() { + super(); + } + + public final boolean getForwarding() { + return forwarding; + } + + public final void setForwarding(final boolean forwarding) { + this.forwarding = forwarding; + } + + public final String getForwardingAsString() { + return "Forwarding is " + (forwarding ? "enabled" : "disabled"); + } + + public final long getDefaultTTL() { + return defaultTTL; + } + + public final void setDefaultTTL(final long defaultTTL) { + this.defaultTTL = defaultTTL; + } + + public final String getDefaultTTLAsString() { + return "Default TTL is " + defaultTTL; + } + + public final void setInReceived(final String packetsReceived, final double packetsReceivedI) { + this.inReceives = packetsReceived; + this.inReceivesI = packetsReceivedI; + } + + public final String getInReceived() { + return inReceives; + } + + public final double getInReceivedI() { + return inReceivesI; + } + + public final String getInReceivedAsString() { + return inReceives + " total packets received [" + nf.format(inReceivesI) + "]"; + } + + public final void setInHdrErrors(final String hdrErrors, final double hdrErrorsI) { + this.inHdrErrors = hdrErrors; + this.inHdrErrorsI = hdrErrorsI; + } + + public final String getInHdrErrors() { + return inHdrErrors; + } + + public final double getInHdrErrorsI() { + return inHdrErrorsI; + } + + public final String getInHdrErrorsAsString() { + return inHdrErrors + " with invalid headers"; + } + + public final void setInAddrErrors(final String inAddrErrors, final double inAddrErrorsI) { + this.inAddrErrors = inAddrErrors; + this.inAddrErrorsI = inAddrErrorsI; + } + + public final String getInAddrErrors() { + return inAddrErrors; + } + + public final double getInAddrErrorsI() { + return inAddrErrorsI; + } + + public final String getInAddrErrorsAsString() { + return inAddrErrors + " with invalid addresses"; + } + + public final void setForwDatagrams(final String forwDatagrams, final double forwDatagramsI) { + this.forwDatagrams = forwDatagrams; + this.forwDatagramsI = forwDatagramsI; + } + + public final String getForwDatagrams() { + return forwDatagrams; + } + + public final double getForwDatagramsI() { + return forwDatagramsI; + } + + public final String getForwDatagramsAsString() { + return forwarding + " datagrams forwarded"; + } + + public final void setInUnknownProtos(final String inUnknownProtos, final double inUnknownProtosI) { + this.inUnknownProtos = inUnknownProtos; + this.inUnknownProtosI = inUnknownProtosI; + } + + public final String getInUnknownProtos() { + return inUnknownProtos; + } + + public final double getInUnknownProtosI() { + return inUnknownProtosI; + } + + public final String getInUnknownProtosAsString() { + return inUnknownProtos + " with unknown protocol"; + } + + public final void setInDiscards(final String inDiscards, final double inDiscardsI) { + this.inDiscards = inDiscards; + this.inDiscardsI = inDiscardsI; + } + + public final String getInDiscards() { + return inDiscards; + } + + public final double getInDiscardsI() { + return inDiscardsI; + } + + public final String getInDiscardsAsString() { + return inDiscards + " incoming packets discarded"; + } + + public final void setInDelivers(final String inDelivers, final double inDeliversI) { + this.inDelivers = inDelivers; + this.inDeliversI = inDeliversI; + } + + public final String getInDelivers() { + return inDelivers; + } + + public final double getInDeliversI() { + return inDeliversI; + } + + public final String getInDeliversAsString() { + return inDelivers + " incoming packets delivered"; + } + + public final void setOutRequests(final String outRequests, final double outRequestsI) { + this.outRequests = outRequests; + this.outRequestsI = outRequestsI; + } + + public final String getOutRequests() { + return outRequests; + } + + public final double getOutRequestsI() { + return outRequestsI; + } + + public final String getOutRequestsAsString() { + return outRequests + " requests sent out"; + } + + public final void setOutDiscards(final String outDiscards, final double outDiscardsI) { + this.outDiscards = outDiscards; + this.outDiscardsI = outDiscardsI; + } + + public final String getOutDiscards() { + return outDiscards; + } + + public final double getOutDiscardsI() { + return outDiscardsI; + } + + public final String getOutDiscardsAsString() { + return outDiscards + " outgoing packets dropped"; + } + + public final void setOutNoRoutes(final String outNoRoutes, final double outNoRoutesI) { + this.outNoRoutes = outNoRoutes; + this.outNoRoutesI = outNoRoutesI; + } + + public final String getOutNoRoutes() { + return outNoRoutes; + } + + public final double getOutNoRoutesI() { + return outNoRoutesI; + } + + public final String getOutNoRoutesAsString() { + return outNoRoutes + " dropped because of missing route"; + } + + public final void setReasmTimeout(final String reasmTimeout, final double reasmTimeoutI) { + this.reasmTimeout = reasmTimeout; + this.reasmTimeoutI = reasmTimeoutI; + } + + public final String getReasmTimeout() { + return reasmTimeout; + } + + public final double getReasmTimeoutI() { + return reasmTimeoutI; + } + + public final String getReasmTimeoutAsString() { + return reasmTimeout + " fragments dropped after timeout"; + } + + public final void setReasmReqds(final String reasmReqds, final double reasmReqdsI) { + this.reasmReqds = reasmReqds; + this.reasmReqdsI = reasmReqdsI; + } + + public final String getReasmReqds() { + return reasmReqds; + } + + public final double getReasmReqdsI() { + return reasmReqdsI; + } + + public final String getReasmReqdsAsString() { + return reasmReqds + " reassemblies required"; + } + + public final void setReasmOKs(final String reasmOKs, final double reasmOKsI) { + this.reasmOKs = reasmOKs; + this.reasmOKsI = reasmOKsI; + } + + public final String getReasmOKs() { + return reasmOKs; + } + + public final double getReasmOKsI() { + return reasmOKsI; + } + + public final String getReasmOKsAsString() { + return reasmOKs + " packets reassembled ok"; + } + + public final void setReasmFails(final String reasmFails, final double reasmFailsI) { + this.reasmFails = reasmFails; + this.reasmFailsI = reasmFailsI; + } + + public final String getReasmFails() { + return reasmFails; + } + + public final double getReasmFailsI() { + return reasmFailsI; + } + + public final String getReasmFailsAsString() { + return reasmFails + " packet reassembles failed"; + } + + public final void setFragOKs(final String fragOKs, final double fragOKsI) { + this.fragOKs = fragOKs; + this.fragOKsI = fragOKsI; + } + + public final String getFragOKs() { + return fragOKs; + } + + public final double getFragOKsI() { + return fragOKsI; + } + + public final String getFragOKsAsString() { + return fragOKs + " fragments received ok"; + } + + public final void setFragFails(final String fragFails, final double fragFailsI) { + this.fragFails = fragFails; + this.fragFailsI = fragFailsI; + } + + public final String getFragFails() { + return fragFails; + } + + public final double getFragFailsI() { + return fragFailsI; + } + + public final String getFragFailsAsString() { + return fragFails + " fragments failed"; + } + + public final void setFragCreates(final String fragCreates, final double fragCreatesI) { + this.fragCreates = fragCreates; + this.fragCreatesI = fragCreatesI; + } + + public final String getFragCreates() { + return fragCreates; + } + + public final double getFragCreatesI() { + return fragCreatesI; + } + + public final String getFragCreatesAsString() { + return fragCreates + " fragments created"; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("IP Statistics:\n"); + buf.append(getForwardingAsString()).append("\n"); + buf.append(getDefaultTTLAsString()).append("\n"); + buf.append(getInReceivedAsString()).append("\n"); + buf.append(getInHdrErrorsAsString()).append("\n"); + buf.append(getInAddrErrorsAsString()).append("\n"); + buf.append(getForwDatagramsAsString()).append("\n"); + buf.append(getInUnknownProtosAsString()).append("\n"); + buf.append(getInDiscardsAsString()).append("\n"); + buf.append(getInDeliversAsString()).append("\n"); + buf.append(getOutRequestsAsString()).append("\n"); + buf.append(getOutDiscardsAsString()).append("\n"); + buf.append(getOutNoRoutesAsString()).append("\n"); + buf.append(getReasmTimeoutAsString()).append("\n"); + buf.append(getReasmReqdsAsString()).append("\n"); + buf.append(getReasmOKsAsString()).append("\n"); + buf.append(getReasmFailsAsString()).append("\n"); + buf.append(getFragOKsAsString()).append("\n"); + buf.append(getFragFailsAsString()).append("\n"); + buf.append(getFragCreatesAsString()).append("\n"); + return buf.toString(); + } + } // end of class IPStatistics diff --git a/src/lia/util/net/copy/monitoring/lisa/net/statistics/StatisticsHandler.java b/src/lia/util/net/copy/monitoring/lisa/net/statistics/StatisticsHandler.java index c4726a3..0cbd060 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/statistics/StatisticsHandler.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/statistics/StatisticsHandler.java @@ -3,11 +3,7 @@ */ package lia.util.net.copy.monitoring.lisa.net.statistics; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintStream; +import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.text.NumberFormat; @@ -21,73 +17,108 @@ /** * Statistics monitoring task (based on statistics of Andi Kleen) * Handler for statistics based on /proc/net/snmp and /proc/net/netstat - * + * * @author Ciprian Dobre */ public class StatisticsHandler { - /** The stream to write output to */ - protected final PrintStream out; - - protected final Logger logger; - - /** The current obtained results... */ - - private IPStatistics ipStatistics = null; - private TCPStatistics tcpStatistics = null; - private UDPStatistics udpStatistics = null; - private TCPExtStatistics tcpExtStatistics = null; - - /** is this a 64 bits arch ? */ - private boolean is64BitArch = false; - - /** Auxiliary method that returns the value of a string */ - private final int getValueAsInt(final String val) { - try { - return Integer.parseInt(val.trim()); - } catch (Exception ex) { - return 0; - } - } - - /** Auxiliary method that returns the value of a string */ - private final long getValueAsLong(final String val) { - try { - return Long.parseLong(val.trim()); - } catch (Exception ex) { - return 0L; - } - } - - private final HashMap> lastValues = new HashMap>(); - - public final double getInstantValue(String key, long time, String newVal) { - - TreeMap h = null; - if (!lastValues.containsKey(key)) { - h = new TreeMap(); - lastValues.put(key, h); - } else - h = lastValues.get(key); - if (h.size() == 0) { - h.put(time, newVal); - return 0D; - } - if (h.size() == 2) - h.remove(h.firstKey()); - h.put(time, newVal); - long first = h.firstKey(); - long last = h.lastKey(); - String s1 = h.get(first); - String s2 = h.get(last); - double d = 0D; - try { - d = Double.parseDouble(diffWithOverflowCheck(s2, s1)); - } catch (Throwable t) { - d = 0D; - } - d = d / ((last - first) / 1000D); - return d; + final static NumberFormat nf = NumberFormat.getInstance(); + + static { + nf.setMaximumFractionDigits(4); + nf.setMinimumFractionDigits(4); + } + + /** + * The stream to write output to + */ + protected final PrintStream out; + protected final Logger logger; + private final HashMap> lastValues = new HashMap>(); + /** + * The current obtained results... + */ + + private IPStatistics ipStatistics = null; + private TCPStatistics tcpStatistics = null; + private UDPStatistics udpStatistics = null; + private TCPExtStatistics tcpExtStatistics = null; + /** + * is this a 64 bits arch ? + */ + private boolean is64BitArch = false; + + /** + * The constructor + * + * @param out The stream to write output to + */ + public StatisticsHandler(final PrintStream out, final Logger logger) { + this.out = out; + this.logger = logger; + } + + public static void main(String args[]) { + + StatisticsHandler h = new StatisticsHandler(System.out, null); + NumberFormat nf = NumberFormat.getInstance(); + nf.setMaximumFractionDigits(2); + nf.setMinimumFractionDigits(2); + + double d = h.getInstantValue("test", 1000, "105395052"); + d = h.getInstantValue("test", 6000, "105395986"); + System.out.println(nf.format(d)); + } + + /** + * Auxiliary method that returns the value of a string + */ + private final int getValueAsInt(final String val) { + try { + return Integer.parseInt(val.trim()); + } catch (Exception ex) { + return 0; + } + } + + /** + * Auxiliary method that returns the value of a string + */ + private final long getValueAsLong(final String val) { + try { + return Long.parseLong(val.trim()); + } catch (Exception ex) { + return 0L; + } + } + + public final double getInstantValue(String key, long time, String newVal) { + + TreeMap h = null; + if (!lastValues.containsKey(key)) { + h = new TreeMap(); + lastValues.put(key, h); + } else + h = lastValues.get(key); + if (h.size() == 0) { + h.put(time, newVal); + return 0D; + } + if (h.size() == 2) + h.remove(h.firstKey()); + h.put(time, newVal); + long first = h.firstKey(); + long last = h.lastKey(); + String s1 = h.get(first); + String s2 = h.get(last); + double d = 0D; + try { + d = Double.parseDouble(diffWithOverflowCheck(s2, s1)); + } catch (Throwable t) { + d = 0D; + } + d = d / ((last - first) / 1000D); + return d; // TreeMap h = null; @@ -106,7 +137,7 @@ public final double getInstantValue(String key, long time, String newVal) { // try { // d = Double.parseDouble(diffWithOverflowCheck(s2, s1)); // } catch (Throwable t) { -// d = 0D; +// d = 0D; // } // d = d / ((last - first) / 1000D); // long diffTime = 60 * 1000; @@ -128,758 +159,739 @@ public final double getInstantValue(String key, long time, String newVal) { // h.put(last, s2); // } // return d; - } - - /** Auxiliary method */ - private final boolean getBooleanOf(final String val) { - return getValueAsInt(val) == 2 ? true : false; - } - - /** Method used for parsing the values for IP */ - private final IPStatistics parseIPTable(final String headerLine, final String valuesLine, final IPStatistics ipstat) { - - final IPStatistics stat = (ipstat != null) ? ipstat : new IPStatistics(); - final StringTokenizer tok = new StringTokenizer(headerLine); - final StringTokenizer valTok = new StringTokenizer(valuesLine); - String el; - try { - while ((el = tok.nextToken(" \t\n")) != null) { - String valEl = valTok.nextToken(" \t\n"); - if (valEl == null) break; - if (el.equals("Forwarding")) { // ip forwarding - stat.setForwarding(getBooleanOf(valEl)); - continue; // advance to the next token - } - if (el.equals("DefaultTTL")) { // default ttl value - stat.setDefaultTTL(getValueAsLong(valEl)); - continue; - } - if (el.equals("InReceives")) { // in received packets - stat.setInReceived(valEl, getInstantValue("InReceives", stat.getTime(), valEl)); - continue; - } - if (el.equals("InHdrErrors")) { // in packets with header errors - stat.setInHdrErrors(valEl, getInstantValue("InHdrErrors", stat.getTime(), valEl)); - continue; - } - if (el.equals("InAddrErrors")) { // in packets with incorrect address field - stat.setInAddrErrors(valEl, getInstantValue("InAddrErrors", stat.getTime(), valEl)); - continue; - } - if (el.equals("ForwDatagrams")) { // number of forwarded datagrams - stat.setForwDatagrams(valEl, getInstantValue("ForwDatagrams", stat.getTime(), valEl)); - continue; - } - if (el.equals("InUnknownProtos")) { // number of packets with uknown protocol - stat.setInUnknownProtos(valEl, getInstantValue("InUnknownProtos", stat.getTime(), valEl)); - continue; - } - if (el.equals("InDiscards")) { // number of discarded packets - stat.setInDiscards(valEl, getInstantValue("InDiscards", stat.getTime(), valEl)); - continue; - } - if (el.equals("InDelivers")) { // number of delivered packets - stat.setInDelivers(valEl, getInstantValue("InDelivers", stat.getTime(), valEl)); - continue; - } - if (el.equals("OutRequests")) { // number of requests sent out - stat.setOutRequests(valEl, getInstantValue("OutRequest", stat.getTime(), valEl)); - continue; - } - if (el.equals("OutDiscards")) { // number of outgoing dropped packets - stat.setOutDiscards(valEl, getInstantValue("OutDiscards", stat.getTime(), valEl)); - continue; - } - if (el.equals("OutNoRoutes")) { // number of dropped because of no route found - stat.setOutNoRoutes(valEl, getInstantValue("OutNoRoutes", stat.getTime(), valEl)); - continue; - } - if (el.equals("ReasmTimeout")) { // number of fragments dropped after timeout - stat.setReasmTimeout(valEl, getInstantValue("ReasmTimeout", stat.getTime(), valEl)); - continue; - } - if (el.equals("ReasmReqds")) { // reassemblies required - stat.setReasmReqds(valEl, getInstantValue("ReasmReqds", stat.getTime(), valEl)); - continue; - } - if (el.equals("ReasmOKs")) { // reassembled ok - stat.setReasmOKs(valEl, getInstantValue("ReasmsOKs", stat.getTime(), valEl)); - continue; - } - if (el.equals("ReasmFails")) { // failed reassembled - stat.setReasmFails(valEl, getInstantValue("ReasmFails", stat.getTime(), valEl)); - continue; - } - if (el.equals("FragOKs")) { // fragments received ok - stat.setFragOKs(valEl, getInstantValue("FragOKs", stat.getTime(), valEl)); - continue; - } - if (el.equals("FragFails")) { // fragments failed - stat.setFragFails(valEl, getInstantValue("FragFails", stat.getTime(), valEl)); - continue; - } - if (el.equals("FragCreates")) { // created fragments - stat.setFragCreates(valEl, getInstantValue("FragCreates", stat.getTime(), valEl)); - } - } - } catch (NoSuchElementException nse) { // done parsing - } - return stat; - } - - /** Method used for parsing the statictical values for TCP stack */ - private final TCPStatistics parseTCPTable(final String headerLine, final String valuesLine, final TCPStatistics tcpstat) { - final TCPStatistics stat = (tcpstat != null) ? tcpstat : new TCPStatistics(); - final StringTokenizer tok = new StringTokenizer(headerLine); - final StringTokenizer valTok = new StringTokenizer(valuesLine); - String el; - try { - while ((el = tok.nextToken(" \t\n")) != null) { - String valEl = valTok.nextToken(" \t\n"); - if (valEl == null) break; - if (el.equals("RtoMin")) { // minimum retransmission time - stat.setRToMin(getValueAsLong(valEl)); - continue; // advance to the next token - } - if (el.equals("RtoMax")) { // maximum retransmission time - stat.setRToMax(getValueAsLong(valEl)); - continue; - } - if (el.equals("ActiveOpens")) { // active connections openned - stat.setActiveOpens(valEl, getInstantValue("ActiveOpens", stat.getTime(), valEl)); - continue; - } - if (el.equals("PassiveOpens")) { // passive connections openned - stat.setPassiveOpens(valEl, getInstantValue("PassiveOpens", stat.getTime(), valEl)); - continue; - } - if (el.equals("AttemptFails")) { // failed connection attempts - stat.setAttemptFails(valEl, getInstantValue("AttemptFails", stat.getTime(), valEl)); - continue; - } - if (el.equals("EstabResets")) { // connection resets received - stat.setEstabResets(valEl, getInstantValue("EstabResets", stat.getTime(), valEl)); - continue; - } - if (el.equals("CurrEstab")) { // connections currently established - stat.setCurrEstab(getValueAsLong(valEl)); - continue; - } - if (el.equals("InSegs")) { // received segments - stat.setInSegs(valEl, getInstantValue("InSegs", stat.getTime(), valEl)); - continue; - } - if (el.equals("OutSegs")) { // segments sent out - stat.setOutSegs(valEl, getInstantValue("OutSegs", stat.getTime(), valEl)); - continue; - } - if (el.equals("RetransSegs")) { // retransmitted segments - stat.setRetransSegs(valEl, getInstantValue("RetransSegs", stat.getTime(), valEl)); - continue; - } - if (el.equals("InErrs")) { // bad segments receied - stat.setInErrs(valEl, getInstantValue("InErrs", stat.getTime(), valEl)); - continue; - } - if (el.equals("OutRsts")) { // resets sent - stat.setOutRsts(valEl, getInstantValue("OutRsts", stat.getTime(), valEl)); - } - } - } catch (NoSuchElementException nse) { // done parsing - } - return stat; - } - - /** Method used for parsing the statictical values for UDP stack */ - private final UDPStatistics parseUDPTable(final String headerLine, final String valuesLine, final UDPStatistics udpstat) { - final UDPStatistics stat = (udpstat != null) ? udpstat : new UDPStatistics(); - final StringTokenizer tok = new StringTokenizer(headerLine); - final StringTokenizer valTok = new StringTokenizer(valuesLine); - String el; - try { - while ((el = tok.nextToken(" \t\n")) != null) { - String valEl = valTok.nextToken(" \t\n"); - if (valEl == null) break; - if (el.equals("InDatagrams")) { // datagrams received - stat.setInDatagrams(valEl, getInstantValue("InDatagrams", stat.getTime(), valEl)); - continue; // advance to the next token - } - if (el.equals("NoPorts")) { // datagrams with no correct ports - stat.setNoPorts(valEl, getInstantValue("NoPorts", stat.getTime(), valEl)); - continue; - } - if (el.equals("InErrors")) { // error datagrams - stat.setInErrors(valEl, getInstantValue("InErrors", stat.getTime(), valEl)); - continue; - } - if (el.equals("OutDatagrams")) { // sent datagrams - stat.setOutDatagrams(valEl, getInstantValue("OutDatagrams", stat.getTime(), valEl)); - } - } - } catch (NoSuchElementException nse) { // done parsing - } - return stat; - } - - /** Method used for parsing the extended statistical values for TCP stack */ - private final TCPExtStatistics parseExtendedTCPTable(final String headerLine, final String valuesLine, final TCPExtStatistics tcpstat) { - final TCPExtStatistics stat = (tcpstat != null) ? tcpstat : new TCPExtStatistics(); - final StringTokenizer tok = new StringTokenizer(headerLine); - final StringTokenizer valTok = new StringTokenizer(valuesLine); - String el; - try { - while ((el = tok.nextToken(" \t\n")) != null) { - String valEl = valTok.nextToken(" \t\n"); - if (valEl == null) break; - if (el.equals("SyncookiesSent")) { // syncookies sent - stat.setSyncookiesSent(valEl, getInstantValue("SyncookiesSent", stat.getTime(), valEl)); - continue; // advance to the next token - } - if (el.equals("SyncookiesRecv")) { // syncookies received - stat.setSyncookiesRecv(valEl, getInstantValue("SyncookiesRecv", stat.getTime(), valEl)); - continue; - } - if (el.equals("SyncookiesFailed")) { // syncookies failed - stat.setSyncookiesFailed(valEl, getInstantValue("SyncookiesFailed", stat.getTime(), valEl)); - continue; - } - if (el.equals("EmbryonicRsts")) { // embryonic resets - stat.setEmbryonicRsts(valEl, getInstantValue("EmbryonicRsts", stat.getTime(), valEl)); - continue; - } - if (el.equals("PruneCalled")) { // packets prunned because of buffer overflow - stat.setPruneCalled(valEl, getInstantValue("PruneCalled", stat.getTime(), valEl)); - continue; - } - if (el.equals("RcvPruned")) { // packets prunned from received queue - stat.setRcvPruned(valEl, getInstantValue("RcvPruned", stat.getTime(), valEl)); - continue; - } - if (el.equals("OfoPruned")) { // packets prunned from out-of-order queue - overfow - stat.setOfoPruned(valEl, getInstantValue("OfoPruned", stat.getTime(), valEl)); - continue; - } - if (el.equals("TW")) { - stat.setTW(valEl, getInstantValue("TW", stat.getTime(), valEl)); - continue; - } - if (el.equals("TWRecycled")) { - stat.setTWRecycled(valEl, getInstantValue("TWRecycled", stat.getTime(), valEl)); - continue; - } - if (el.equals("TWKilled")) { - stat.setTWKilled(valEl, getInstantValue("TWKilled", stat.getTime(), valEl)); - continue; - } - if (el.equals("PAWSPassive")) { - stat.setPAWSPassive(valEl, getInstantValue("PAWSPassive", stat.getTime(), valEl)); - continue; - } - if (el.equals("PAWSActive")) { - stat.setPAWSActive(valEl, getInstantValue("PAWSActive", stat.getTime(), valEl)); - continue; - } - if (el.equals("PAWSEstab")) { - stat.setPAWSEstab(valEl, getInstantValue("PAWEstab", stat.getTime(), valEl)); - continue; - } - if (el.equals("DelayedACKs")) { - stat.setDelayedACKs(valEl, getInstantValue("DelayedACKs", stat.getTime(), valEl)); - continue; - } - if (el.equals("DelayedACKLocked")) { - stat.setDelayedACKLocked(valEl, getInstantValue("DelayedACKLocked", stat.getTime(), valEl)); - continue; - } - if (el.equals("DelayedACKLost")) { - stat.setDelayedACKLost(valEl, getInstantValue("DelayedACKLost", stat.getTime(), valEl)); - continue; - } - if (el.equals("ListenOverflows")) { - stat.setListenOverflows(valEl, getInstantValue("ListenOverflows", stat.getTime(), valEl)); - continue; - } - if (el.equals("ListenDrops")) { - stat.setListenDrops(valEl, getInstantValue("ListenDrops", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPPrequeued")) { - stat.setTCPPrequeued(valEl, getInstantValue("TCPPrequeued", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDirectCopyFromBacklog")) { - stat.setTCPDirectCopyFromBacklog(valEl, getInstantValue("TCPDirectCopyFromBacklog", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDirectCopyFromPrequeue")) { - stat.setTCPDirectCopyFromPrequeue(valEl, getInstantValue("TCPDirectCopyFromPrequeue", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPPrequeueDropped")) { - stat.setTCPPrequeueDropped(valEl, getInstantValue("TCPPrequeueDropped", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPHPHits")) { - stat.setTCPHPHits(valEl, getInstantValue("TCPHPHits", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPHPHitsToUser")) { - stat.setTCPHPHitsToUser(valEl, getInstantValue("TCPHPHitsToUser", stat.getTime(), valEl)); - continue; - } - if (el.equals("SockMallocOOM")) { - stat.setSockMallocOOM(valEl, getInstantValue("SockMallocOOM", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPPureAcks")) { - stat.setTCPPureAcks(valEl, getInstantValue("TCPPureAcks", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPHPAcks")) { - stat.setTCPHPAcks(valEl, getInstantValue("TCPHPAcks", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPRenoRecovery")) { - stat.setTCPRenoRecovery(valEl, getInstantValue("TCPRenoRecovery", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSackRecovery")) { - stat.setTCPSackRecovery(valEl, getInstantValue("TCPSackRecovery", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSACKReneging")) { - stat.setTCPSACKReneging(valEl, getInstantValue("TCPSACKReneging", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPFACKReorder")) { - stat.setTCPFACKReorder(valEl, getInstantValue("TCPFACKReorder", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSACKReorder")) { - stat.setTCPSACKReorder(valEl, getInstantValue("TCPSACKReorder", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPRenoReorder")) { - stat.setTCPRenoReorder(valEl, getInstantValue("TCPRenoReorder", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPTSReorder")) { - stat.setTCPTSReorder(valEl, getInstantValue("TCPTSReorder", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPFullUndo")) { - stat.setTCPFullUndo(valEl, getInstantValue("TCPFullUndo", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPPartialUndo")) { - stat.setTCPPartialUndo(valEl, getInstantValue("TCPPartialUndo", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDSACKUndo")) { - stat.setTCPDSACKUndo(valEl, getInstantValue("TCPDSACKUndo", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPLossUndo")) { - stat.setTCPLossUndo(valEl, getInstantValue("TCPLossUndo", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPLoss")) { - stat.setTCPLoss(valEl, getInstantValue("TCPLoss", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPLostRetransmit")) { - stat.setTCPLostRetransmit(valEl, getInstantValue("TCPLostRetransmit", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPRenoFailures")) { - stat.setTCPRenoFailures(valEl, getInstantValue("TCPRenoFailures", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSackFailures")) { - stat.setTCPSackFailures(valEl, getInstantValue("TCPSackFailures", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPLossFailures")) { - stat.setTCPLossFailures(valEl, getInstantValue("TCPLossFailures", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPFastRetrans")) { - stat.setTCPFastRetrans(valEl, getInstantValue("TCPFastRetrans", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPForwardRetrans")) { - stat.setTCPForwardRetrans(valEl, getInstantValue("TCPForwardRetrans", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSlowStartRetrans")) { - stat.setTCPSlowStartRetrans(valEl, getInstantValue("TCPSlowStartRetrans", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPTimeouts")) { - stat.setTCPTimeouts(valEl, getInstantValue("TCPTimeouts", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPRenoRecoveryFail")) { - stat.setTCPRenoRecoveryFail(valEl, getInstantValue("TCPRenoRecoveryFail", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSackRecoveryFail")) { - stat.setTCPSackRecoveryFail(valEl, getInstantValue("TCPSackRecoveryFail", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPSchedulerFailed")) { - stat.setTCPSchedulerFailed(valEl, getInstantValue("TCPSchedulerFailed", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPRcvCollapsed")) { - stat.setTCPRcvCollapsed(valEl, getInstantValue("TCPRcvCollapsed", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDSACKOldSent")) { - stat.setTCPDSACKOldSent(valEl, getInstantValue("TCPDSACKOldSent", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDSACKOfoSent")) { - stat.setTCPDSACKOfoSent(valEl, getInstantValue("TCPDSACKOfoSent", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDSACKRecv")) { - stat.setTCPDSACKRecv(valEl, getInstantValue("TCPDSACKRecv", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPDSACKOfoRecv")) { - stat.setTCPDSACKOfoRecv(valEl, getInstantValue("TCPDSACKOfoRecv", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortOnSyn")) { - stat.setTCPAbortOnSyn(valEl, getInstantValue("TCPAbortOnSyn", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortOnData")) { - stat.setTCPAbortOnData(valEl, getInstantValue("TCPAbortOnData", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortOnClose")) { - stat.setTCPAbortOnClose(valEl, getInstantValue("TCPAbortOnClose", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortOnMemory")) { - stat.setTCPAbortOnMemory(valEl, getInstantValue("TCPAbortOnMemory", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortOnTimeout")) { - stat.setTCPAbortOnTimeout(valEl, getInstantValue("TCPAbortOnTimeout", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortOnLinger")) { - stat.setTCPAbortOnLinger(valEl, getInstantValue("TCPAbortOnLinger", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPAbortFailed")) { - stat.setTCPAbortFailed(valEl, getInstantValue("TCPAbortFailed", stat.getTime(), valEl)); - continue; - } - if (el.equals("TCPMemoryPressures")) { - stat.setTCPMemoryPressures(valEl, getInstantValue("TCPMemoryPressures", stat.getTime(), valEl)); - continue; - } - } - } catch (NoSuchElementException nse) { // done parsing - } - return stat; - } - - /** - * The constructor - * @param properties We need the properties of the module in order to interogate for paths - * @param out The stream to write output to - */ - public StatisticsHandler(final PrintStream out, final Logger logger) { - this.out = out; - this.logger = logger; - } - - public void getStatistics() { - - ipStatistics = null; - tcpStatistics = null; - udpStatistics = null; - tcpExtStatistics = null; - - // first parse /proc/net/snmp - try { - BufferedReader br = new BufferedReader(new FileReader("/proc/net/snmp")); - while (true) { - // we need to read two lines at a time, the header and the actual values - final String header = br.readLine(); - if (header == null) // end of stream - break; - final String values = br.readLine(); - if (values == null) // end of stream - break; - if (header.startsWith("Ip: ") && values.startsWith("Ip: ")) { - ipStatistics = parseIPTable(header.substring(4), values.substring(4), null); - if (logger.isLoggable(Level.FINEST)) - logger.log(Level.FINE, ipStatistics.toString()); - continue; - } - if (header.startsWith("Tcp: ") && values.startsWith("Tcp: ")) { - tcpStatistics = parseTCPTable(header.substring(5), values.substring(5), null); - if (logger.isLoggable(Level.FINEST)) - logger.log(Level.FINE, tcpStatistics.toString()); - continue; - } - if (header.startsWith("Udp: ") && values.startsWith("Udp: ")) { - udpStatistics = parseUDPTable(header.substring(5), values.substring(5), null); - if (logger.isLoggable(Level.FINEST)) - logger.log(Level.FINE, udpStatistics.toString()); - continue; - } - } - br.close(); - } catch (FileNotFoundException fne) { - if (out != null) - out.println("File /proc/net/snmp not found."); - else - logger.info("File /proc/net/snmp not found."); - } catch (IOException ioe) { - if (out != null) out.println(ioe.getLocalizedMessage()); - else logger.warning(ioe.getLocalizedMessage()); - } - - // second parse /proc/net/netstat - try { - BufferedReader br = new BufferedReader(new FileReader("/proc/net/netstat")); - while (true) { - // we need to read two lines at a time, the header and the actual values - final String header = br.readLine(); - if (header == null) // end of stream - break; - final String values = br.readLine(); - if (values == null) // end of stream - break; - if (header.startsWith("TcpExt: ") && values.startsWith("TcpExt: ")) { - tcpExtStatistics = parseExtendedTCPTable(header.substring(8), values.substring(8), null); - if (logger.isLoggable(Level.FINEST)) - logger.log(Level.FINE, tcpExtStatistics.toString()); - continue; - } - } - br.close(); - } catch (FileNotFoundException fne) { - if (out != null) - out.println("File /proc/net/netstat not found."); - else - logger.info("File /proc/net/netstat not found."); - } catch (IOException ioe) { - if (out != null) out.println(ioe.getLocalizedMessage()); - else logger.warning(ioe.getLocalizedMessage()); - } - } - - public final IPStatistics getIPStatistics() { - return ipStatistics; - } - - public final TCPStatistics getTCPStatistics() { - return tcpStatistics; - } - - public final UDPStatistics getUDPStatistics() { - return udpStatistics; - } - - public final TCPExtStatistics getTCPExtStatistics() { - return tcpExtStatistics; - } - - final static NumberFormat nf = NumberFormat.getInstance(); - - static { - nf.setMaximumFractionDigits(4); - nf.setMinimumFractionDigits(4); - } - - private final String prepareString(String str) { - - // first try to make it double - try { - double d = Double.parseDouble(str); - if (!Double.isInfinite(d) && !Double.isNaN(d)) { - String n = nf.format(d); - n = n.replaceAll(",", ""); - return n; - } - } catch (Throwable t) { } - - if (!str.contains(".")) { - return str+".0000"; - } - int nr = str.lastIndexOf('.')+1; - nr = str.length() - nr; - for (int i=nr; i<4; i++) - str += "0"; - return str; - } - - - public String addWithOverflowCheck(String newVal, String oldVal) - throws NumberFormatException { - - if (newVal == null) - return oldVal; - if (oldVal == null) - return newVal; - - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - return newv.add(oldv).toString(); - } + } + + /** + * Auxiliary method + */ + private final boolean getBooleanOf(final String val) { + return getValueAsInt(val) == 2 ? true : false; + } + + /** + * Method used for parsing the values for IP + */ + private final IPStatistics parseIPTable(final String headerLine, final String valuesLine, final IPStatistics ipstat) { + + final IPStatistics stat = (ipstat != null) ? ipstat : new IPStatistics(); + final StringTokenizer tok = new StringTokenizer(headerLine); + final StringTokenizer valTok = new StringTokenizer(valuesLine); + String el; + try { + while ((el = tok.nextToken(" \t\n")) != null) { + String valEl = valTok.nextToken(" \t\n"); + if (valEl == null) break; + if (el.equals("Forwarding")) { // ip forwarding + stat.setForwarding(getBooleanOf(valEl)); + continue; // advance to the next token + } + if (el.equals("DefaultTTL")) { // default ttl value + stat.setDefaultTTL(getValueAsLong(valEl)); + continue; + } + if (el.equals("InReceives")) { // in received packets + stat.setInReceived(valEl, getInstantValue("InReceives", stat.getTime(), valEl)); + continue; + } + if (el.equals("InHdrErrors")) { // in packets with header errors + stat.setInHdrErrors(valEl, getInstantValue("InHdrErrors", stat.getTime(), valEl)); + continue; + } + if (el.equals("InAddrErrors")) { // in packets with incorrect address field + stat.setInAddrErrors(valEl, getInstantValue("InAddrErrors", stat.getTime(), valEl)); + continue; + } + if (el.equals("ForwDatagrams")) { // number of forwarded datagrams + stat.setForwDatagrams(valEl, getInstantValue("ForwDatagrams", stat.getTime(), valEl)); + continue; + } + if (el.equals("InUnknownProtos")) { // number of packets with uknown protocol + stat.setInUnknownProtos(valEl, getInstantValue("InUnknownProtos", stat.getTime(), valEl)); + continue; + } + if (el.equals("InDiscards")) { // number of discarded packets + stat.setInDiscards(valEl, getInstantValue("InDiscards", stat.getTime(), valEl)); + continue; + } + if (el.equals("InDelivers")) { // number of delivered packets + stat.setInDelivers(valEl, getInstantValue("InDelivers", stat.getTime(), valEl)); + continue; + } + if (el.equals("OutRequests")) { // number of requests sent out + stat.setOutRequests(valEl, getInstantValue("OutRequest", stat.getTime(), valEl)); + continue; + } + if (el.equals("OutDiscards")) { // number of outgoing dropped packets + stat.setOutDiscards(valEl, getInstantValue("OutDiscards", stat.getTime(), valEl)); + continue; + } + if (el.equals("OutNoRoutes")) { // number of dropped because of no route found + stat.setOutNoRoutes(valEl, getInstantValue("OutNoRoutes", stat.getTime(), valEl)); + continue; + } + if (el.equals("ReasmTimeout")) { // number of fragments dropped after timeout + stat.setReasmTimeout(valEl, getInstantValue("ReasmTimeout", stat.getTime(), valEl)); + continue; + } + if (el.equals("ReasmReqds")) { // reassemblies required + stat.setReasmReqds(valEl, getInstantValue("ReasmReqds", stat.getTime(), valEl)); + continue; + } + if (el.equals("ReasmOKs")) { // reassembled ok + stat.setReasmOKs(valEl, getInstantValue("ReasmsOKs", stat.getTime(), valEl)); + continue; + } + if (el.equals("ReasmFails")) { // failed reassembled + stat.setReasmFails(valEl, getInstantValue("ReasmFails", stat.getTime(), valEl)); + continue; + } + if (el.equals("FragOKs")) { // fragments received ok + stat.setFragOKs(valEl, getInstantValue("FragOKs", stat.getTime(), valEl)); + continue; + } + if (el.equals("FragFails")) { // fragments failed + stat.setFragFails(valEl, getInstantValue("FragFails", stat.getTime(), valEl)); + continue; + } + if (el.equals("FragCreates")) { // created fragments + stat.setFragCreates(valEl, getInstantValue("FragCreates", stat.getTime(), valEl)); + } + } + } catch (NoSuchElementException nse) { // done parsing + } + return stat; + } + + /** + * Method used for parsing the statictical values for TCP stack + */ + private final TCPStatistics parseTCPTable(final String headerLine, final String valuesLine, final TCPStatistics tcpstat) { + final TCPStatistics stat = (tcpstat != null) ? tcpstat : new TCPStatistics(); + final StringTokenizer tok = new StringTokenizer(headerLine); + final StringTokenizer valTok = new StringTokenizer(valuesLine); + String el; + try { + while ((el = tok.nextToken(" \t\n")) != null) { + String valEl = valTok.nextToken(" \t\n"); + if (valEl == null) break; + if (el.equals("RtoMin")) { // minimum retransmission time + stat.setRToMin(getValueAsLong(valEl)); + continue; // advance to the next token + } + if (el.equals("RtoMax")) { // maximum retransmission time + stat.setRToMax(getValueAsLong(valEl)); + continue; + } + if (el.equals("ActiveOpens")) { // active connections openned + stat.setActiveOpens(valEl, getInstantValue("ActiveOpens", stat.getTime(), valEl)); + continue; + } + if (el.equals("PassiveOpens")) { // passive connections openned + stat.setPassiveOpens(valEl, getInstantValue("PassiveOpens", stat.getTime(), valEl)); + continue; + } + if (el.equals("AttemptFails")) { // failed connection attempts + stat.setAttemptFails(valEl, getInstantValue("AttemptFails", stat.getTime(), valEl)); + continue; + } + if (el.equals("EstabResets")) { // connection resets received + stat.setEstabResets(valEl, getInstantValue("EstabResets", stat.getTime(), valEl)); + continue; + } + if (el.equals("CurrEstab")) { // connections currently established + stat.setCurrEstab(getValueAsLong(valEl)); + continue; + } + if (el.equals("InSegs")) { // received segments + stat.setInSegs(valEl, getInstantValue("InSegs", stat.getTime(), valEl)); + continue; + } + if (el.equals("OutSegs")) { // segments sent out + stat.setOutSegs(valEl, getInstantValue("OutSegs", stat.getTime(), valEl)); + continue; + } + if (el.equals("RetransSegs")) { // retransmitted segments + stat.setRetransSegs(valEl, getInstantValue("RetransSegs", stat.getTime(), valEl)); + continue; + } + if (el.equals("InErrs")) { // bad segments receied + stat.setInErrs(valEl, getInstantValue("InErrs", stat.getTime(), valEl)); + continue; + } + if (el.equals("OutRsts")) { // resets sent + stat.setOutRsts(valEl, getInstantValue("OutRsts", stat.getTime(), valEl)); + } + } + } catch (NoSuchElementException nse) { // done parsing + } + return stat; + } + + /** + * Method used for parsing the statictical values for UDP stack + */ + private final UDPStatistics parseUDPTable(final String headerLine, final String valuesLine, final UDPStatistics udpstat) { + final UDPStatistics stat = (udpstat != null) ? udpstat : new UDPStatistics(); + final StringTokenizer tok = new StringTokenizer(headerLine); + final StringTokenizer valTok = new StringTokenizer(valuesLine); + String el; + try { + while ((el = tok.nextToken(" \t\n")) != null) { + String valEl = valTok.nextToken(" \t\n"); + if (valEl == null) break; + if (el.equals("InDatagrams")) { // datagrams received + stat.setInDatagrams(valEl, getInstantValue("InDatagrams", stat.getTime(), valEl)); + continue; // advance to the next token + } + if (el.equals("NoPorts")) { // datagrams with no correct ports + stat.setNoPorts(valEl, getInstantValue("NoPorts", stat.getTime(), valEl)); + continue; + } + if (el.equals("InErrors")) { // error datagrams + stat.setInErrors(valEl, getInstantValue("InErrors", stat.getTime(), valEl)); + continue; + } + if (el.equals("OutDatagrams")) { // sent datagrams + stat.setOutDatagrams(valEl, getInstantValue("OutDatagrams", stat.getTime(), valEl)); + } + } + } catch (NoSuchElementException nse) { // done parsing + } + return stat; + } + + /** + * Method used for parsing the extended statistical values for TCP stack + */ + private final TCPExtStatistics parseExtendedTCPTable(final String headerLine, final String valuesLine, final TCPExtStatistics tcpstat) { + final TCPExtStatistics stat = (tcpstat != null) ? tcpstat : new TCPExtStatistics(); + final StringTokenizer tok = new StringTokenizer(headerLine); + final StringTokenizer valTok = new StringTokenizer(valuesLine); + String el; + try { + while ((el = tok.nextToken(" \t\n")) != null) { + String valEl = valTok.nextToken(" \t\n"); + if (valEl == null) break; + if (el.equals("SyncookiesSent")) { // syncookies sent + stat.setSyncookiesSent(valEl, getInstantValue("SyncookiesSent", stat.getTime(), valEl)); + continue; // advance to the next token + } + if (el.equals("SyncookiesRecv")) { // syncookies received + stat.setSyncookiesRecv(valEl, getInstantValue("SyncookiesRecv", stat.getTime(), valEl)); + continue; + } + if (el.equals("SyncookiesFailed")) { // syncookies failed + stat.setSyncookiesFailed(valEl, getInstantValue("SyncookiesFailed", stat.getTime(), valEl)); + continue; + } + if (el.equals("EmbryonicRsts")) { // embryonic resets + stat.setEmbryonicRsts(valEl, getInstantValue("EmbryonicRsts", stat.getTime(), valEl)); + continue; + } + if (el.equals("PruneCalled")) { // packets prunned because of buffer overflow + stat.setPruneCalled(valEl, getInstantValue("PruneCalled", stat.getTime(), valEl)); + continue; + } + if (el.equals("RcvPruned")) { // packets prunned from received queue + stat.setRcvPruned(valEl, getInstantValue("RcvPruned", stat.getTime(), valEl)); + continue; + } + if (el.equals("OfoPruned")) { // packets prunned from out-of-order queue - overfow + stat.setOfoPruned(valEl, getInstantValue("OfoPruned", stat.getTime(), valEl)); + continue; + } + if (el.equals("TW")) { + stat.setTW(valEl, getInstantValue("TW", stat.getTime(), valEl)); + continue; + } + if (el.equals("TWRecycled")) { + stat.setTWRecycled(valEl, getInstantValue("TWRecycled", stat.getTime(), valEl)); + continue; + } + if (el.equals("TWKilled")) { + stat.setTWKilled(valEl, getInstantValue("TWKilled", stat.getTime(), valEl)); + continue; + } + if (el.equals("PAWSPassive")) { + stat.setPAWSPassive(valEl, getInstantValue("PAWSPassive", stat.getTime(), valEl)); + continue; + } + if (el.equals("PAWSActive")) { + stat.setPAWSActive(valEl, getInstantValue("PAWSActive", stat.getTime(), valEl)); + continue; + } + if (el.equals("PAWSEstab")) { + stat.setPAWSEstab(valEl, getInstantValue("PAWEstab", stat.getTime(), valEl)); + continue; + } + if (el.equals("DelayedACKs")) { + stat.setDelayedACKs(valEl, getInstantValue("DelayedACKs", stat.getTime(), valEl)); + continue; + } + if (el.equals("DelayedACKLocked")) { + stat.setDelayedACKLocked(valEl, getInstantValue("DelayedACKLocked", stat.getTime(), valEl)); + continue; + } + if (el.equals("DelayedACKLost")) { + stat.setDelayedACKLost(valEl, getInstantValue("DelayedACKLost", stat.getTime(), valEl)); + continue; + } + if (el.equals("ListenOverflows")) { + stat.setListenOverflows(valEl, getInstantValue("ListenOverflows", stat.getTime(), valEl)); + continue; + } + if (el.equals("ListenDrops")) { + stat.setListenDrops(valEl, getInstantValue("ListenDrops", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPPrequeued")) { + stat.setTCPPrequeued(valEl, getInstantValue("TCPPrequeued", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDirectCopyFromBacklog")) { + stat.setTCPDirectCopyFromBacklog(valEl, getInstantValue("TCPDirectCopyFromBacklog", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDirectCopyFromPrequeue")) { + stat.setTCPDirectCopyFromPrequeue(valEl, getInstantValue("TCPDirectCopyFromPrequeue", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPPrequeueDropped")) { + stat.setTCPPrequeueDropped(valEl, getInstantValue("TCPPrequeueDropped", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPHPHits")) { + stat.setTCPHPHits(valEl, getInstantValue("TCPHPHits", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPHPHitsToUser")) { + stat.setTCPHPHitsToUser(valEl, getInstantValue("TCPHPHitsToUser", stat.getTime(), valEl)); + continue; + } + if (el.equals("SockMallocOOM")) { + stat.setSockMallocOOM(valEl, getInstantValue("SockMallocOOM", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPPureAcks")) { + stat.setTCPPureAcks(valEl, getInstantValue("TCPPureAcks", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPHPAcks")) { + stat.setTCPHPAcks(valEl, getInstantValue("TCPHPAcks", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPRenoRecovery")) { + stat.setTCPRenoRecovery(valEl, getInstantValue("TCPRenoRecovery", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSackRecovery")) { + stat.setTCPSackRecovery(valEl, getInstantValue("TCPSackRecovery", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSACKReneging")) { + stat.setTCPSACKReneging(valEl, getInstantValue("TCPSACKReneging", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPFACKReorder")) { + stat.setTCPFACKReorder(valEl, getInstantValue("TCPFACKReorder", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSACKReorder")) { + stat.setTCPSACKReorder(valEl, getInstantValue("TCPSACKReorder", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPRenoReorder")) { + stat.setTCPRenoReorder(valEl, getInstantValue("TCPRenoReorder", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPTSReorder")) { + stat.setTCPTSReorder(valEl, getInstantValue("TCPTSReorder", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPFullUndo")) { + stat.setTCPFullUndo(valEl, getInstantValue("TCPFullUndo", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPPartialUndo")) { + stat.setTCPPartialUndo(valEl, getInstantValue("TCPPartialUndo", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDSACKUndo")) { + stat.setTCPDSACKUndo(valEl, getInstantValue("TCPDSACKUndo", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPLossUndo")) { + stat.setTCPLossUndo(valEl, getInstantValue("TCPLossUndo", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPLoss")) { + stat.setTCPLoss(valEl, getInstantValue("TCPLoss", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPLostRetransmit")) { + stat.setTCPLostRetransmit(valEl, getInstantValue("TCPLostRetransmit", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPRenoFailures")) { + stat.setTCPRenoFailures(valEl, getInstantValue("TCPRenoFailures", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSackFailures")) { + stat.setTCPSackFailures(valEl, getInstantValue("TCPSackFailures", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPLossFailures")) { + stat.setTCPLossFailures(valEl, getInstantValue("TCPLossFailures", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPFastRetrans")) { + stat.setTCPFastRetrans(valEl, getInstantValue("TCPFastRetrans", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPForwardRetrans")) { + stat.setTCPForwardRetrans(valEl, getInstantValue("TCPForwardRetrans", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSlowStartRetrans")) { + stat.setTCPSlowStartRetrans(valEl, getInstantValue("TCPSlowStartRetrans", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPTimeouts")) { + stat.setTCPTimeouts(valEl, getInstantValue("TCPTimeouts", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPRenoRecoveryFail")) { + stat.setTCPRenoRecoveryFail(valEl, getInstantValue("TCPRenoRecoveryFail", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSackRecoveryFail")) { + stat.setTCPSackRecoveryFail(valEl, getInstantValue("TCPSackRecoveryFail", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPSchedulerFailed")) { + stat.setTCPSchedulerFailed(valEl, getInstantValue("TCPSchedulerFailed", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPRcvCollapsed")) { + stat.setTCPRcvCollapsed(valEl, getInstantValue("TCPRcvCollapsed", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDSACKOldSent")) { + stat.setTCPDSACKOldSent(valEl, getInstantValue("TCPDSACKOldSent", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDSACKOfoSent")) { + stat.setTCPDSACKOfoSent(valEl, getInstantValue("TCPDSACKOfoSent", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDSACKRecv")) { + stat.setTCPDSACKRecv(valEl, getInstantValue("TCPDSACKRecv", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPDSACKOfoRecv")) { + stat.setTCPDSACKOfoRecv(valEl, getInstantValue("TCPDSACKOfoRecv", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortOnSyn")) { + stat.setTCPAbortOnSyn(valEl, getInstantValue("TCPAbortOnSyn", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortOnData")) { + stat.setTCPAbortOnData(valEl, getInstantValue("TCPAbortOnData", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortOnClose")) { + stat.setTCPAbortOnClose(valEl, getInstantValue("TCPAbortOnClose", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortOnMemory")) { + stat.setTCPAbortOnMemory(valEl, getInstantValue("TCPAbortOnMemory", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortOnTimeout")) { + stat.setTCPAbortOnTimeout(valEl, getInstantValue("TCPAbortOnTimeout", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortOnLinger")) { + stat.setTCPAbortOnLinger(valEl, getInstantValue("TCPAbortOnLinger", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPAbortFailed")) { + stat.setTCPAbortFailed(valEl, getInstantValue("TCPAbortFailed", stat.getTime(), valEl)); + continue; + } + if (el.equals("TCPMemoryPressures")) { + stat.setTCPMemoryPressures(valEl, getInstantValue("TCPMemoryPressures", stat.getTime(), valEl)); + continue; + } + } + } catch (NoSuchElementException nse) { // done parsing + } + return stat; + } + + public void getStatistics() { + + ipStatistics = null; + tcpStatistics = null; + udpStatistics = null; + tcpExtStatistics = null; + + // first parse /proc/net/snmp + try { + BufferedReader br = new BufferedReader(new FileReader("/proc/net/snmp")); + while (true) { + // we need to read two lines at a time, the header and the actual values + final String header = br.readLine(); + if (header == null) // end of stream + break; + final String values = br.readLine(); + if (values == null) // end of stream + break; + if (header.startsWith("Ip: ") && values.startsWith("Ip: ")) { + ipStatistics = parseIPTable(header.substring(4), values.substring(4), null); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINE, ipStatistics.toString()); + continue; + } + if (header.startsWith("Tcp: ") && values.startsWith("Tcp: ")) { + tcpStatistics = parseTCPTable(header.substring(5), values.substring(5), null); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINE, tcpStatistics.toString()); + continue; + } + if (header.startsWith("Udp: ") && values.startsWith("Udp: ")) { + udpStatistics = parseUDPTable(header.substring(5), values.substring(5), null); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINE, udpStatistics.toString()); + continue; + } + } + br.close(); + } catch (FileNotFoundException fne) { + if (out != null) + out.println("File /proc/net/snmp not found."); + else + logger.info("File /proc/net/snmp not found."); + } catch (IOException ioe) { + if (out != null) out.println(ioe.getLocalizedMessage()); + else logger.warning(ioe.getLocalizedMessage()); + } + + // second parse /proc/net/netstat + try { + BufferedReader br = new BufferedReader(new FileReader("/proc/net/netstat")); + while (true) { + // we need to read two lines at a time, the header and the actual values + final String header = br.readLine(); + if (header == null) // end of stream + break; + final String values = br.readLine(); + if (values == null) // end of stream + break; + if (header.startsWith("TcpExt: ") && values.startsWith("TcpExt: ")) { + tcpExtStatistics = parseExtendedTCPTable(header.substring(8), values.substring(8), null); + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINE, tcpExtStatistics.toString()); + continue; + } + } + br.close(); + } catch (FileNotFoundException fne) { + if (out != null) + out.println("File /proc/net/netstat not found."); + else + logger.info("File /proc/net/netstat not found."); + } catch (IOException ioe) { + if (out != null) out.println(ioe.getLocalizedMessage()); + else logger.warning(ioe.getLocalizedMessage()); + } + } + + public final IPStatistics getIPStatistics() { + return ipStatistics; + } + + public final TCPStatistics getTCPStatistics() { + return tcpStatistics; + } + + public final UDPStatistics getUDPStatistics() { + return udpStatistics; + } + + public final TCPExtStatistics getTCPExtStatistics() { + return tcpExtStatistics; + } + + private final String prepareString(String str) { + + // first try to make it double + try { + double d = Double.parseDouble(str); + if (!Double.isInfinite(d) && !Double.isNaN(d)) { + String n = nf.format(d); + n = n.replaceAll(",", ""); + return n; + } + } catch (Throwable t) { + } + + if (!str.contains(".")) { + return str + ".0000"; + } + int nr = str.lastIndexOf('.') + 1; + nr = str.length() - nr; + for (int i = nr; i < 4; i++) + str += "0"; + return str; + } + + public String addWithOverflowCheck(String newVal, String oldVal) + throws NumberFormatException { + + if (newVal == null) + return oldVal; + if (oldVal == null) + return newVal; + + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + return newv.add(oldv).toString(); + } // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return addWithOverflowCheck(newVal, oldVal); - } + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return addWithOverflowCheck(newVal, oldVal); + } // so it's still 32 bits arch - return "" + (newv + oldv); - } - - public String divideWithOverflowCheck(String newVal, String oldVal) - throws NumberFormatException { - - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - return newv.divide(oldv, BigDecimal.ROUND_FLOOR).toString(); - } + return "" + (newv + oldv); + } + + public String divideWithOverflowCheck(String newVal, String oldVal) + throws NumberFormatException { + + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + return newv.divide(oldv, BigDecimal.ROUND_FLOOR).toString(); + } // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return divideWithOverflowCheck(newVal, oldVal); - } + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return divideWithOverflowCheck(newVal, oldVal); + } // so it's still 32 bits arch - return "" + (newv / oldv); - } - - public String mulWithOverflowCheck(String newVal, String oldVal) - throws NumberFormatException { - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - return newv.multiply(oldv).toString(); - } + return "" + (newv / oldv); + } + + public String mulWithOverflowCheck(String newVal, String oldVal) + throws NumberFormatException { + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + return newv.multiply(oldv).toString(); + } // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return mulWithOverflowCheck(newVal, oldVal); - } + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return mulWithOverflowCheck(newVal, oldVal); + } // so it's still 32 bits arch - return "" + (newv * oldv); - } - - public String diffWithOverflowCheck(String newVal, String oldVal) - throws NumberFormatException { - if (is64BitArch) { - String str = prepareString(newVal); - BigDecimal newv = null; - try { - newv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - str = prepareString(oldVal); - BigDecimal oldv = null; - try { - oldv = new BigDecimal(str); - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception "+t+" for "+str); - } - if (newv.compareTo(oldv) >= 0) - return newv.subtract(oldv).toString(); - BigInteger overflow = new BigInteger("1").shiftLeft(64); - BigDecimal d = new BigDecimal(overflow.toString()); - return newv.add(d).subtract(oldv).toString(); - } + return "" + (newv * oldv); + } + + public String diffWithOverflowCheck(String newVal, String oldVal) + throws NumberFormatException { + if (is64BitArch) { + String str = prepareString(newVal); + BigDecimal newv = null; + try { + newv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + str = prepareString(oldVal); + BigDecimal oldv = null; + try { + oldv = new BigDecimal(str); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception " + t + " for " + str); + } + if (newv.compareTo(oldv) >= 0) + return newv.subtract(oldv).toString(); + BigInteger overflow = new BigInteger("1").shiftLeft(64); + BigDecimal d = new BigDecimal(overflow.toString()); + return newv.add(d).subtract(oldv).toString(); + } // otherwise we still assume 32 bits arch - double toCompare = 1L << 32; - double newv = Double.parseDouble(newVal); - double oldv = Double.parseDouble(oldVal); - if (newv >= toCompare || oldv >= toCompare) { - is64BitArch = true; - return diffWithOverflowCheck(newVal, oldVal); - } + double toCompare = 1L << 32; + double newv = Double.parseDouble(newVal); + double oldv = Double.parseDouble(oldVal); + if (newv >= toCompare || oldv >= toCompare) { + is64BitArch = true; + return diffWithOverflowCheck(newVal, oldVal); + } // so it's still 32 bits arch - if (newv >= oldv) { - return "" + (newv - oldv); - } - long vmax = 1L << 32; // 32 bits - return "" + (newv - oldv + vmax); - } - - public static void main(String args[]) { - - StatisticsHandler h = new StatisticsHandler(System.out, null); - NumberFormat nf = NumberFormat.getInstance(); - nf.setMaximumFractionDigits(2); - nf.setMinimumFractionDigits(2); - - double d = h.getInstantValue("test", 1000, "105395052"); - d = h.getInstantValue("test", 6000, "105395986"); - System.out.println(nf.format(d)); - } - - public void clear() { - ipStatistics = null; - tcpStatistics = null; - udpStatistics = null; - tcpExtStatistics = null; - } + if (newv >= oldv) { + return "" + (newv - oldv); + } + long vmax = 1L << 32; // 32 bits + return "" + (newv - oldv + vmax); + } + + public void clear() { + ipStatistics = null; + tcpStatistics = null; + udpStatistics = null; + tcpExtStatistics = null; + } } // end of class StatisticsHandler diff --git a/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPExtStatistics.java b/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPExtStatistics.java index caee8ac..604f76e 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPExtStatistics.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPExtStatistics.java @@ -7,1183 +7,1236 @@ /** * Extended statistics regarding the tcp protocol suite + * * @author Ciprian Dobre */ public class TCPExtStatistics extends Statistics { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - /** SYN cookies sent */ - protected String syncookiesSent; - protected double syncookiesSentI; - - /** SYN cookies received */ - protected String syncookiesRecv; - protected double syncookiesRecvI; - - /** SYN cookies failed */ - protected String syncookiesFailed; - protected double syncookiesFailedI; - - /** Packets pruned from receive queue because of buffer overrun */ - protected String pruneCalled; - protected double pruneCalledI; - - /** resets received because of embryonic sockets */ - protected String embryonicRsts; - protected double embryonicRstsI; - - /** Packets prunned from receive queue */ - protected String rcvPruned; - protected double rcvPrunedI; - - /** Packets prunned from out-of-order queue because of overflow */ - protected String ofoPruned; - protected double ofoPrunedI; - - /** tcp sockets finished time wait in fast timer */ - protected String tW; - protected double tWI; - - /** sockets recicled by time stamp */ - protected String tWRecycled; - protected double tWRecycledI; - - /** TCP sockets finished time wait in slow timer */ - protected String tWKilled; - protected double tWKilledI; - - /** passive connections rejected because of timestamp */ - protected String pAWSPassive; - protected double pAWSPassiveI; - - /** active connection rejected because of timestamp */ - protected String pAWSActive; - protected double pAWSActiveI; - - /** packets rejected in established connection because of timestamp */ - protected String pAWSEstab; - protected double pAWSEstabI; - - /** delayed acks sent */ - protected String delayedACKs; - protected double delayedACKsI; - - /** delayed acks further delayed because of socket lock */ - protected String delayedACKLocked; - protected double delayedACKLockedI; - - /** Quick ack mode activated times */ - protected String delayedACKLost; - protected double delayedACKLostI; - - /** how many times the listening queue of a socket overflowed */ - protected String listenOverflows; - protected double listenOverflowsI; - - /** SYNs to LISTEN sockets ignored */ - protected String listenDrops; - protected double listenDropsI; - - /** packets directly queued to recvmsg - urgwent packets */ - protected String tCPPrequeued; - protected double tCPPrequeuedI; - - /** packets directly received from backlog */ - protected String tCPDirectCopyFromBacklog; - protected double tCPDirectCopyFromBacklogI; - - /** packets directly received from prequeue */ - protected String tCPDirectCopyFromPrequeue; - protected double tCPDirectCopyFromPrequeueI; - - /** packets dropped from prequeue */ - protected String tCPPrequeueDropped; - protected double tCPPrequeueDroppedI; - - /** packet headers predicted */ - protected String tCPHPHits; - protected double tCPHPHitsI; - - /** packet headers predicted and directly queued to user */ - protected String tCPHPHitsToUser; - protected double tCPHPHitsToUserI; - - /** how many times oom was encoutered when sending */ - protected String sockMallocOOM; - protected double sockMallocOOMI; - - /** Pure ack recvd */ - protected String tCPPureAcks; - protected double tCPPureAcksI; - - protected String tCPHPAcks; - protected double tCPHPAcksI; - - protected String tCPRenoRecovery; - protected double tCPRenoRecoveryI; - - protected String tCPSackRecovery; - protected double tCPSackRecoveryI; - - protected String tCPSACKReneging; - protected double tCPSACKRenegingI; - - protected String tCPFACKReorder; - protected double tCPFACKReorderI; - - protected String tCPSACKReorder; - protected double tCPSACKReorderI; - - protected String tCPRenoReorder; - protected double tCPRenoReorderI; - - protected String tCPtSReorder; - protected double tCPtSReorderI; - - protected String tCPFullUndo; - protected double tCPFullUndoI; - - protected String tCPPartialUndo; - protected double tCPPartialUndoI; - - protected String tCPDSACKUndo; - protected double tCPDSACKUndoI; - - protected String tCPLossUndo; - protected double tCPLossUndoI; - - protected String tCPLoss; - protected double tCPLossI; - - protected String tCPLostRetransmit; - protected double tCPLostRetransmitI; - - protected String tCPRenoFailures; - protected double tCPRenoFailuresI; - - protected String tCPSackFailures; - protected double tCPSackFailuresI; - - protected String tCPLossFailures; - protected double tCPLossFailuresI; - - protected String tCPFastRetrans; - protected double tCPFastRetransI; - - protected String tCPForwardRetrans; - protected double tCPForwardRetransI; - - protected String tCPSlowStartRetrans; - protected double tCPSlowStartRetransI; - - protected String tCPtimeouts; - protected double tCPtimeoutsI; - - protected String tCPRenoRecoveryFail; - protected double tCPRenoRecoveryFailI; - - protected String tCPSackRecoveryFail; - protected double tCPSackRecoveryFailI; - - protected String tCPSchedulerFailed; - protected double tCPSchedulerFailedI; - - protected String tCPRcvCollapsed; - protected double tCPRcvCollapsedI; - - protected String tCPDSACKOldSent; - protected double tCPDSACKOldSentI; - - protected String tCPDSACKOfoSent; - protected double tCPDSACKOfoSentI; - - protected String tCPDSACKRecv; - protected double tCPDSACKRecvI; - - protected String tCPDSACKOfoRecv; - protected double tCPDSACKOfoRecvI; - - protected String tCPAbortOnSyn; - protected double tCPAbortOnSynI; - - protected String tCPAbortOnData; - protected double tCPAbortOnDataI; - - protected String tCPAbortOnClose; - protected double tCPAbortOnCloseI; - - protected String tCPAbortOnMemory; - protected double tCPAbortOnMemoryI; - - protected String tCPAbortOntimeout; - protected double tCPAbortOntimeoutI; - - protected String tCPAbortOnLinger; - protected double tCPAbortOnLingerI; - - protected String tCPAbortFailed; - protected double tCPAbortFailedI; - - protected String tCPMemoryPressures; - protected double tCPMemoryPressuresI; - - public TCPExtStatistics() { - super(); - } - - public final void setSyncookiesSent(final String syncookiesSent, final double syncookiesSentI) { - this.syncookiesSent = syncookiesSent; - this.syncookiesSentI = syncookiesSentI; - } - - public final String getSyncookiesSent() { - return syncookiesSent; - } - - public final double getSyncookiesSentI() { - return syncookiesSentI; - } - - public final String getSyncookiesSentAsString() { - return syncookiesSent+" SYN cookies sent"; - } - - public final void setSyncookiesRecv(final String syncookiesRecv, final double syncookiesRecvI) { - this.syncookiesRecv = syncookiesRecv; - this.syncookiesRecvI = syncookiesRecvI; - } - - public final String getSyncookiesRecv() { - return syncookiesRecv; - } - - public final double getSyncookiesRecvI() { - return syncookiesRecvI; - } - - public final String getSyncookiesRecvAsString() { - return syncookiesRecv+" SYN cookies received"; - } - - public final void setSyncookiesFailed(final String syncookiesFailed, final double syncookiesFailedI) { - this.syncookiesFailed = syncookiesFailed; - this.syncookiesFailedI = syncookiesFailedI; - } - - public final String getSyncookiesFailed() { - return syncookiesFailed; - } - - public final double getSyncookiesFailedI() { - return syncookiesFailedI; - } - - public final String getSyncookiesFailedAsString() { - return syncookiesFailed+" invalid SYN cookies received"; - } - - public final void setEmbryonicRsts(final String embryonicRsts, final double embryonicRstsI) { - this.embryonicRsts = embryonicRsts; - this.embryonicRstsI = embryonicRstsI; - } - - public final String getEmbryonicRsts() { - return embryonicRsts; - } - - public final double getEmbryonicRstsI() { - return embryonicRstsI; - } - - public final String getEmbryonicRstsAsString() { - return embryonicRsts+" resets received for embryonic SYN_RECV sockets"; - } - - public final void setPruneCalled(final String pruneCalled, final double pruneCalledI) { - this.pruneCalled = pruneCalled; - this.pruneCalledI = pruneCalledI; - } - - public final String getPruneCalled() { - return pruneCalled; - } - - public final double getPruneCalledI() { - return pruneCalledI; - } - - public final String getPruneCalledAsString() { - return pruneCalled+" packets pruned from receive queue because of socket buffer overrun"; - } - - public final void setRcvPruned(final String rcvPruned, final double rcvPrunedI) { - this.rcvPruned = rcvPruned; - this.rcvPrunedI = rcvPrunedI; - } - - public final String getRcvPruned() { - return rcvPruned; - } - - public final double getRcvPrunedI() { - return rcvPrunedI; - } - - public final String getRcvPrunedAsString() { - return rcvPruned+" packets pruned from receive queue"; - } - - public final void setOfoPruned(final String ofoPruned, final double ofoPrunedI) { - this.ofoPruned = ofoPruned; - this.ofoPrunedI = ofoPrunedI; - } - - public final String getOfoPruned() { - return ofoPruned; - } - - public final double getOfoPrunedI() { - return ofoPrunedI; - } - - public final String getOfoPrunedAsString() { - return ofoPruned+" packets dropped from out-of-order queue because of socket buffer overrun"; - } - - public final void setTW(final String tW, final double tWI) { - this.tW = tW; - this.tWI = tWI; - } - - public final String getTW() { - return tW; - } - - public final double getTWI() { - return tWI; - } - - public final String getTWAsString() { - return tW+" TCP sockets finished time wait in fast timer"; - } - - public final void setTWRecycled(final String tWRecycled, final double tWRecycledI) { - this.tWRecycled = tWRecycled; - this.tWRecycledI = tWRecycledI; - } - - public final String getTWRecycled() { - return tWRecycled; - } - - public final double getTWRecycledI() { - return tWRecycledI; - } - - public final String getTWRecycledAsString() { - return tWRecycled+" time wait sockets recycled by time stamp"; - } - - public final void setTWKilled(final String tWKilled, final double tWKilledI) { - this.tWKilled = tWKilled; - this.tWKilledI = tWKilledI; - } - - public final String getTWKilled() { - return tWKilled; - } - - public final double getTWKilledI() { - return tWKilledI; - } - - public final String getTWKilledAsString() { - return tWKilled+" TCP sockets finished time wait in slow timer"; - } - - public final void setPAWSPassive(final String pAWSPassive, final double pAWSPassiveI) { - this.pAWSPassive = pAWSPassive; - this.pAWSPassiveI = pAWSPassiveI; - } - - public final String getPAWSPassive() { - return pAWSPassive; - } - - public final double getPAWSPassiveI() { - return pAWSPassiveI; - } - - public final String getPAWSPassiveAsString() { - return pAWSPassive+" passive connections rejected because of time stamp"; - } - - public final void setPAWSActive(final String pAWSActive, final double pAWSActiveI) { - this.pAWSActive =pAWSActive; - this.pAWSActiveI =pAWSActiveI; - } - - public final String getPAWSActive() { - return pAWSActive; - } - - public final double getPAWSActiveI() { - return pAWSActiveI; - } - - public final String getPAWSActiveAsString() { - return pAWSActive+" active connections rejected because of time stamp"; - } - - public final void setPAWSEstab(final String pAWSEstab, final double pAWSEstabI) { - this.pAWSEstab = pAWSEstab; - this.pAWSEstabI = pAWSEstabI; - } - - public final String getPAWSEstab() { - return pAWSEstab; - } - - public final double getPAWSEstabI() { - return pAWSEstabI; - } - - public final String getPAWSEstabAsString() { - return pAWSEstab+" packets rejects in established connections because of timestamp"; - } - - public final void setDelayedACKs(final String delayedACKs, final double delayedACKsI) { - this.delayedACKs = delayedACKs; - this.delayedACKsI = delayedACKsI; - } - - public final String getDelayedACKs() { - return delayedACKs; - } - - public final double getDelayedACKsI() { - return delayedACKsI; - } - - public final String getDelayedACKsAsString() { - return delayedACKs+" delayed acks sent"; - } - - public final void setDelayedACKLocked(final String delayedACKLocked, final double delayedACKLockedI) { - this.delayedACKLocked = delayedACKLocked; - this.delayedACKLockedI = delayedACKLockedI; - } - - public final String getDelayedACKLocked() { - return delayedACKLocked; - } - - public final double getDelayedACKLockedI() { - return delayedACKLockedI; - } - - public final String getDelayedACKLockedAsString() { - return delayedACKLocked+" delayed acks further delayed because of locked socket"; - } - - public final void setDelayedACKLost(final String delayedACKLost, final double delayedACKLostI) { - this.delayedACKLost = delayedACKLost; - this.delayedACKLostI = delayedACKLostI; - } - - public final String getDelayedACKLost() { - return delayedACKLost; - } - - public final double getDelayedACKLostI() { - return delayedACKLostI; - } - - public final String getDelayedACKLostAsString() { - return "Quick ack mode was activated "+delayedACKLost+" times"; - } - - public final void setListenOverflows(final String listenOverflows, final double listenOverflowsI) { - this.listenOverflows = listenOverflows; - this.listenOverflowsI = listenOverflowsI; - } - - public final String getListenOverflows() { - return listenOverflows; - } - - public final double getListenOverflowsI() { - return listenOverflowsI; - } - - public final String getListenOverflowsAsString() { - return listenOverflows+" times the listen queue of a socket overflowed"; - } - - public final void setListenDrops(final String listenDrops, final double listenDropsI) { - this.listenDrops = listenDrops; - this.listenDropsI = listenDropsI; - } - - public final String getListenDrops() { - return listenDrops; - } - - public final double getListenDropsI() { - return listenDropsI; - } - - public final String getListenDropsAsString() { - return listenDrops+" SYNs to LISTEN sockets ignored"; - } - - public final void setTCPPrequeued(final String tCPPrequeued, final double tCPPrequeuedI) { - this.tCPPrequeued = tCPPrequeued; - this.tCPPrequeuedI = tCPPrequeuedI; - } - - public final String getTCPPrequeued() { - return tCPPrequeued; - } - - public final double getTCPPrequeuedI() { - return tCPPrequeuedI; - } - - public final String getTCPPrequeuedAsString() { - return tCPPrequeued+" packets directly queued to recvmsg prequeue"; - } - - public final void setTCPDirectCopyFromBacklog(final String tCPDirectCopyFromBacklog, final double tCPDirectCopyFromBacklogI) { - this.tCPDirectCopyFromBacklog = tCPDirectCopyFromBacklog; - this.tCPDirectCopyFromBacklogI = tCPDirectCopyFromBacklogI; - } - - public final String getTCPDirectCopyFromBacklog() { - return tCPDirectCopyFromBacklog; - } - - public final double getTCPDirectCopyFromBacklogI() { - return tCPDirectCopyFromBacklogI; - } - - public final String getTCPDirectCopyFromBacklogAsString() { - return tCPDirectCopyFromBacklog+" packets directly received from backlog"; - } - - public final void setTCPDirectCopyFromPrequeue(final String tCPDirectCopyFromPrequeue, final double tCPDirectCopyFromPrequeueI) { - this.tCPDirectCopyFromPrequeue = tCPDirectCopyFromPrequeue; - this.tCPDirectCopyFromPrequeueI = tCPDirectCopyFromPrequeueI; - } - - public final String getTCPDirectCopyFromPrequeue() { - return tCPDirectCopyFromPrequeue; - } - - public final double getTCPDirectCopyFromPrequeueI() { - return tCPDirectCopyFromPrequeueI; - } - - public final String getTCPDirectCopyFromPrequeueAsString() { - return tCPDirectCopyFromPrequeue+" packets directly received from prequeue"; - } - - public final void setTCPPrequeueDropped(final String tCPPrequeueDropped, final double tCPPrequeueDroppedI) { - this.tCPPrequeueDropped = tCPPrequeueDropped; - this.tCPPrequeueDroppedI = tCPPrequeueDroppedI; - } - - public final String getTCPPrequeueDropped() { - return tCPPrequeueDropped; - } - - public final double getTCPPrequeueDroppedI() { - return tCPPrequeueDroppedI; - } - - public final String getTCPPrequeueDroppedAsString() { - return tCPPrequeueDropped+" packets dropped from prequeue"; - } - - public final void setTCPHPHits(final String tCPHPHits, final double tCPHPHitsI) { - this.tCPHPHits = tCPHPHits; - this.tCPHPHitsI = tCPHPHitsI; - } - - public final String getTCPHPHits() { - return tCPHPHits; - } - - public final double getTCPHPHitsI() { - return tCPHPHitsI; - } - - public final String getTCPHPHitsAsString() { - return tCPHPHits+" packets header predicted"; - } - - public final void setTCPHPHitsToUser(final String tCPHPHitsToUser, final double tCPHPHitsToUserI) { - this.tCPHPHitsToUser = tCPHPHitsToUser; - this.tCPHPHitsToUserI = tCPHPHitsToUserI; - } - - public final String getTCPHPHitsToUser() { - return tCPHPHitsToUser; - } - - public final double getTCPHPHitsToUserI() { - return tCPHPHitsToUserI; - } - - public final String getTCPHPHitsToUserAsString() { - return tCPHPHitsToUser+" packets header predicted and directly queued to user"; - } - - public final void setSockMallocOOM(final String sockMallocOOM, final double sockMallocOOMI) { - this.sockMallocOOM = sockMallocOOM; - this.sockMallocOOMI = sockMallocOOMI; - } - - public final String getSockMallocOOM() { - return sockMallocOOM; - } - - public final double getSockMallocOOMI() { - return sockMallocOOMI; - } - - public final String getSockMallocOOMAsString() { - return "Ran "+sockMallocOOM+" times out of system memory during packet sending"; - } - - public final void setTCPPureAcks(final String tCPPureAcks, final double tCPPureAcksI) { - this.tCPPureAcks = tCPPureAcks; - this.tCPPureAcksI = tCPPureAcksI; - } - - public final String getTCPPureAcks() { - return tCPPureAcks; - } - - public final double getTCPPureAcksI() { - return tCPPureAcksI; - } - - public final void setTCPHPAcks(final String tCPHPAcks, final double tCPHPAcksI) { - this.tCPHPAcks = tCPHPAcks; - this.tCPHPAcksI = tCPHPAcksI; - } - - public final String getTCPHPAcks() { - return tCPHPAcks; - } - - public final double getTCPHPAcksI() { - return tCPHPAcksI; - } - - public final void setTCPRenoRecovery(final String tCPRenoRecovery, final double tCPRenoRecoveryI) { - this.tCPRenoRecovery = tCPRenoRecovery; - this.tCPRenoRecoveryI = tCPRenoRecoveryI; - } - - public final String getTCPRenoRecovery() { - return tCPRenoRecovery; - } - - public final double getTCPRenoRecoveryI() { - return tCPRenoRecoveryI; - } - - public final void setTCPSackRecovery(final String tCPSackRecovery, final double tCPSackRecoveryI) { - this.tCPSackRecovery = tCPSackRecovery; - this.tCPSackRecoveryI = tCPSackRecoveryI; - } - - public final String getTCPSackRecovery() { - return tCPSackRecovery; - } - - public final double getTCPSackRecoveryI() { - return tCPSackRecoveryI; - } - - public String toString() { - final StringBuffer buf = new StringBuffer(); - buf.append("Extended TCP Statistics:\n"); - buf.append(getSyncookiesSentAsString()).append("\n"); - buf.append(getSyncookiesRecvAsString()).append("\n"); - buf.append(getSyncookiesFailedAsString()).append("\n"); - buf.append(getEmbryonicRstsAsString()).append("\n"); - buf.append(getPruneCalledAsString()).append("\n"); - buf.append(getRcvPrunedAsString()).append("\n"); - buf.append(getOfoPrunedAsString()).append("\n"); - buf.append(getTWAsString()).append("\n"); - buf.append(getTWRecycledAsString()).append("\n"); - buf.append(getTWKilledAsString()).append("\n"); - buf.append(getPAWSPassiveAsString()).append("\n"); - buf.append(getPAWSActiveAsString()).append("\n"); - buf.append(getPAWSEstabAsString()).append("\n"); - buf.append(getDelayedACKsAsString()).append("\n"); - buf.append(getDelayedACKLockedAsString()).append("\n"); - buf.append(getDelayedACKLostAsString()).append("\n"); - buf.append(getListenOverflowsAsString()).append("\n"); - buf.append(getListenDropsAsString()).append("\n"); - buf.append(getTCPPrequeuedAsString()).append("\n"); - buf.append(getTCPDirectCopyFromBacklogAsString()).append("\n"); - buf.append(getTCPDirectCopyFromPrequeueAsString()).append("\n"); - buf.append(getTCPPrequeueDroppedAsString()).append("\n"); - buf.append(getTCPHPHitsAsString()).append("\n"); - buf.append(getTCPHPHitsToUserAsString()).append("\n"); - buf.append(getSockMallocOOMAsString()).append("\n"); - return buf.toString(); - } - - public void setTCPDSACKUndo(String undo, double undoI) { - tCPDSACKUndo = undo; - tCPDSACKUndoI = undoI; - } - - public String getTCPDSACKUndo() { - return tCPDSACKUndo; - } - - public double getTCPDSACKUndoI() { - return tCPDSACKUndoI; - } - - public void setTCPFACKReorder(String reorder, double reorderI) { - tCPFACKReorder = reorder; - tCPFACKReorderI = reorderI; - } - - public String getTCPFACKReorder() { - return tCPFACKReorder; - } - - public double getTCPFACKReorderI() { - return tCPFACKReorderI; - } - - public void setTCPFullUndo(String fullUndo, double fullUndoI) { - tCPFullUndo = fullUndo; - tCPFullUndoI = fullUndoI; - } - - public String getTCPFullUndo() { - return tCPFullUndo; - } - - public double getTCPFullUndoI() { - return tCPFullUndoI; - } - - public void setTCPLoss(String loss, double lossI) { - tCPLoss = loss; - tCPLossI = lossI; - } - - public String getTCPLoss() { - return tCPLoss; - } - - public double getTCPLossI() { - return tCPLossI; - } - - public void setTCPLossFailures(String lossFailures, double lossFailuresI) { - tCPLossFailures = lossFailures; - tCPLossFailuresI = lossFailuresI; - } - - public String getTCPLossFailures() { - return tCPLossFailures; - } - - public double getTCPLossFailuresI() { - return tCPLossFailuresI; - } - - public void setTCPLossUndo(String lossUndo, double lossUndoI) { - tCPLossUndo = lossUndo; - tCPLossUndoI = lossUndoI; - } - - public String getTCPLossUndo() { - return tCPLossUndo; - } - - public double getTCPLossUndoI() { - return tCPLossUndoI; - } - - public void setTCPLostRetransmit(String lostRetransmit, double lostRetransmitI) { - tCPLostRetransmit = lostRetransmit; - tCPLostRetransmitI = lostRetransmitI; - } - - public String getTCPLostRetransmit() { - return tCPLostRetransmit; - } - - public double getTCPLostRetransmitI() { - return tCPLostRetransmitI; - } - - public void setTCPPartialUndo(String partialUndo, double partialUndoI) { - tCPPartialUndo = partialUndo; - tCPPartialUndoI = partialUndoI; - } - - public String getTCPPartialUndo() { - return tCPPartialUndo; - } - - public double getTCPPartialUndoI() { - return tCPPartialUndoI; - } - - public void setTCPRenoFailures(String renoFailures, double renoFailuresI) { - tCPRenoFailures = renoFailures; - tCPRenoFailuresI = renoFailuresI; - } - - public String getTCPRenoFailures() { - return tCPRenoFailures; - } - - public double getTCPRenoFailuresI() { - return tCPRenoFailuresI; - } - - public void setTCPRenoReorder(String renoReorder, double renoReorderI) { - tCPRenoReorder = renoReorder; - tCPRenoReorderI = renoReorderI; - } - - public String getTCPRenoReorder() { - return tCPRenoReorder; - } - - public double getTCPRenoReorderI() { - return tCPRenoReorderI; - } - - public void setTCPSackFailures(String sackFailures, double sackFailuresI) { - tCPSackFailures = sackFailures; - tCPSackFailuresI = sackFailuresI; - } - - public String getTCPSackFailures() { - return tCPSackFailures; - } - - public double getTCPSackFailuresI() { - return tCPSackFailuresI; - } - - public void setTCPSACKReneging(String reneging, double renegingI) { - tCPSACKReneging = reneging; - tCPSACKRenegingI = renegingI; - } - - public String getTCPSACKReneging() { - return tCPSACKReneging; - } - - public double getTCPSACKRenegingI() { - return tCPSACKRenegingI; - } - - public void setTCPSACKReorder(String reorder, double reorderI) { - tCPSACKReorder = reorder; - tCPSACKReorderI = reorderI; - } - - public String getTCPSACKReorder() { - return tCPSACKReorder; - } - - public double getTCPSACKReorderI() { - return tCPSACKReorderI; - } - - public void setTCPTSReorder(String ptSReorder, double ptSReorderI) { - tCPtSReorder = ptSReorder; - tCPtSReorderI = ptSReorderI; - } - - public String getTCPTSReorder() { - return tCPtSReorder; - } - - public double getTCPTSReorderI() { - return tCPtSReorderI; - } - - public void setTCPAbortFailed(String abortFailed, double abortFailedI) { - tCPAbortFailed = abortFailed; - tCPAbortFailedI = abortFailedI; - } - - public String getTCPAbortFailed() { - return tCPAbortFailed; - } - - public double getTCPAbortFailedI() { - return tCPAbortFailedI; - } - - public void setTCPAbortOnClose(String abortOnClose, double abortOnCloseI) { - tCPAbortOnClose = abortOnClose; - tCPAbortOnCloseI = abortOnCloseI; - } - - public String getTCPAbortOnClose() { - return tCPAbortOnClose; - } - - public double getTCPAbortOnCloseI() { - return tCPAbortOnCloseI; - } - - public void setTCPAbortOnData(String abortOnData, double abortOnDataI) { - tCPAbortOnData = abortOnData; - tCPAbortOnDataI = abortOnDataI; - } - - public String getTCPAbortOnData() { - return tCPAbortOnData; - } - - public double getTCPAbortOnDataI() { - return tCPAbortOnDataI; - } - - public void setTCPAbortOnLinger(String abortOnLinger, double abortOnLingerI) { - tCPAbortOnLinger = abortOnLinger; - tCPAbortOnLingerI = abortOnLingerI; - } - - public String getTCPAbortOnLinger() { - return tCPAbortOnLinger; - } - - public double getTCPAbortOnLingerI() { - return tCPAbortOnLingerI; - } - - public void setTCPAbortOnMemory(String abortOnMemory, double abortOnMemoryI) { - tCPAbortOnMemory = abortOnMemory; - tCPAbortOnMemoryI = abortOnMemoryI; - } - - public String getTCPAbortOnMemory() { - return tCPAbortOnMemory; - } - - public double getTCPAbortOnMemoryI() { - return tCPAbortOnMemoryI; - } - - public void setTCPAbortOnSyn(String abortOnSyn, double abortOnSynI) { - tCPAbortOnSyn = abortOnSyn; - tCPAbortOnSynI = abortOnSynI; - } - - public String getTCPAbortOnSyn() { - return tCPAbortOnSyn; - } - - public double getTCPAbortOnSynI() { - return tCPAbortOnSynI; - } - - public void setTCPAbortOnTimeout(String abortOntimeout, double abortOntimeoutI) { - tCPAbortOntimeout = abortOntimeout; - tCPAbortOntimeoutI = abortOntimeoutI; - } - - public String getTCPAbortOnTimeout() { - return tCPAbortOntimeout; - } - - public double getTCPAbortOnTimeoutI() { - return tCPAbortOntimeoutI; - } - - public void setTCPDSACKOfoRecv(String ofoRecv, double ofoRecvI) { - tCPDSACKOfoRecv = ofoRecv; - tCPDSACKOfoRecvI = ofoRecvI; - } - - public String getTCPDSACKOfoRecv() { - return tCPDSACKOfoRecv; - } - - public double getTCPDSACKOfoRecvI() { - return tCPDSACKOfoRecvI; - } - - public void setTCPDSACKOfoSent(String ofoSent, double ofoSentI) { - tCPDSACKOfoSent = ofoSent; - tCPDSACKOfoSentI = ofoSentI; - } - - public String getTCPDSACKOfoSent() { - return tCPDSACKOfoSent; - } - - public double getTCPDSACKOfoSentI() { - return tCPDSACKOfoSentI; - } - - public void setTCPDSACKOldSent(String oldSent, double oldSentI) { - tCPDSACKOldSent = oldSent; - tCPDSACKOldSentI = oldSentI; - } - - public String getTCPDSACKOldSent() { - return tCPDSACKOldSent; - } - - public double getTCPDSACKOldSentI() { - return tCPDSACKOldSentI; - } - - public void setTCPDSACKRecv(String recv, double recvI) { - tCPDSACKRecv = recv; - tCPDSACKRecvI = recvI; - } - - public String getTCPDSACKRecv() { - return tCPDSACKRecv; - } - - public double getTCPDSACKRecvI() { - return tCPDSACKRecvI; - } - - public void setTCPFastRetrans(String fastRetrans, double fastRetransI) { - tCPFastRetrans = fastRetrans; - tCPFastRetransI = fastRetransI; - } - - public String getTCPFastRetrans() { - return tCPFastRetrans; - } - - public double getTCPFastRetransI() { - return tCPFastRetransI; - } - - public void setTCPForwardRetrans(String forwardRetrans, double forwardRetransI) { - tCPForwardRetrans = forwardRetrans; - tCPForwardRetransI = forwardRetransI; - } - - public String getTCPForwardRetrans() { - return tCPForwardRetrans; - } - - public double getTCPForwardRetransI() { - return tCPForwardRetransI; - } - - public void setTCPMemoryPressures(String memoryPressures, double memoryPressuresI) { - tCPMemoryPressures = memoryPressures; - tCPMemoryPressuresI = memoryPressuresI; - } - - public String getTCPMemoryPressures() { - return tCPMemoryPressures; - } - - public double getTCPMemoryPressuresI() { - return tCPMemoryPressuresI; - } - - public void setTCPRcvCollapsed(String rcvCollapsed, double rcvCollapsedI) { - tCPRcvCollapsed = rcvCollapsed; - tCPRcvCollapsedI = rcvCollapsedI; - } - - public String getTCPRcvCollapsed() { - return tCPRcvCollapsed; - } - - public double getTCPRcvCollapsedI() { - return tCPRcvCollapsedI; - } - - public void setTCPRenoRecoveryFail(String renoRecoveryFail, double renoRecoveryFailI) { - tCPRenoRecoveryFail = renoRecoveryFail; - tCPRenoRecoveryFailI = renoRecoveryFailI; - } - - public String getTCPRenoRecoveryFail() { - return tCPRenoRecoveryFail; - } - - public double getTCPRenoRecoveryFailI() { - return tCPRenoRecoveryFailI; - } - - public void setTCPSackRecoveryFail(String sackRecoveryFail, double sackRecoveryFailI) { - tCPSackRecoveryFail = sackRecoveryFail; - tCPSackRecoveryFailI = sackRecoveryFailI; - } - - public String getTCPSackRecoveryFail() { - return tCPSackRecoveryFail; - } - - public double getTCPSackRecoveryFailI() { - return tCPSackRecoveryFailI; - } - - public void setTCPSchedulerFailed(String schedulerFailed, double schedulerFailedI) { - tCPSchedulerFailed = schedulerFailed; - tCPSchedulerFailedI = schedulerFailedI; - } - - public String getTCPSchedulerFailed() { - return tCPSchedulerFailed; - } - - public double getTCPSchedulerFailedI() { - return tCPSchedulerFailedI; - } - - public void setTCPSlowStartRetrans(String slowStartRetrans, double slowStartRetransI) { - tCPSlowStartRetrans = slowStartRetrans; - tCPSlowStartRetransI = slowStartRetransI; - } - - public String getTCPSlowStartRetrans() { - return tCPSlowStartRetrans; - } - - public double getTCPSlowStartRetransI() { - return tCPSlowStartRetransI; - } - - public void setTCPTimeouts(String ptimeouts, double ptimeoutsI) { - tCPtimeouts = ptimeouts; - tCPtimeoutsI = ptimeoutsI; - } - - public String getTCPTimeouts() { - return tCPtimeouts; - } - - public double getTCPTimeoutsI() { - return tCPtimeoutsI; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + /** + * SYN cookies sent + */ + protected String syncookiesSent; + protected double syncookiesSentI; + + /** + * SYN cookies received + */ + protected String syncookiesRecv; + protected double syncookiesRecvI; + + /** + * SYN cookies failed + */ + protected String syncookiesFailed; + protected double syncookiesFailedI; + + /** + * Packets pruned from receive queue because of buffer overrun + */ + protected String pruneCalled; + protected double pruneCalledI; + + /** + * resets received because of embryonic sockets + */ + protected String embryonicRsts; + protected double embryonicRstsI; + + /** + * Packets prunned from receive queue + */ + protected String rcvPruned; + protected double rcvPrunedI; + + /** + * Packets prunned from out-of-order queue because of overflow + */ + protected String ofoPruned; + protected double ofoPrunedI; + + /** + * tcp sockets finished time wait in fast timer + */ + protected String tW; + protected double tWI; + + /** + * sockets recicled by time stamp + */ + protected String tWRecycled; + protected double tWRecycledI; + + /** + * TCP sockets finished time wait in slow timer + */ + protected String tWKilled; + protected double tWKilledI; + + /** + * passive connections rejected because of timestamp + */ + protected String pAWSPassive; + protected double pAWSPassiveI; + + /** + * active connection rejected because of timestamp + */ + protected String pAWSActive; + protected double pAWSActiveI; + + /** + * packets rejected in established connection because of timestamp + */ + protected String pAWSEstab; + protected double pAWSEstabI; + + /** + * delayed acks sent + */ + protected String delayedACKs; + protected double delayedACKsI; + + /** + * delayed acks further delayed because of socket lock + */ + protected String delayedACKLocked; + protected double delayedACKLockedI; + + /** + * Quick ack mode activated times + */ + protected String delayedACKLost; + protected double delayedACKLostI; + + /** + * how many times the listening queue of a socket overflowed + */ + protected String listenOverflows; + protected double listenOverflowsI; + + /** + * SYNs to LISTEN sockets ignored + */ + protected String listenDrops; + protected double listenDropsI; + + /** + * packets directly queued to recvmsg - urgwent packets + */ + protected String tCPPrequeued; + protected double tCPPrequeuedI; + + /** + * packets directly received from backlog + */ + protected String tCPDirectCopyFromBacklog; + protected double tCPDirectCopyFromBacklogI; + + /** + * packets directly received from prequeue + */ + protected String tCPDirectCopyFromPrequeue; + protected double tCPDirectCopyFromPrequeueI; + + /** + * packets dropped from prequeue + */ + protected String tCPPrequeueDropped; + protected double tCPPrequeueDroppedI; + + /** + * packet headers predicted + */ + protected String tCPHPHits; + protected double tCPHPHitsI; + + /** + * packet headers predicted and directly queued to user + */ + protected String tCPHPHitsToUser; + protected double tCPHPHitsToUserI; + + /** + * how many times oom was encoutered when sending + */ + protected String sockMallocOOM; + protected double sockMallocOOMI; + + /** + * Pure ack recvd + */ + protected String tCPPureAcks; + protected double tCPPureAcksI; + + protected String tCPHPAcks; + protected double tCPHPAcksI; + + protected String tCPRenoRecovery; + protected double tCPRenoRecoveryI; + + protected String tCPSackRecovery; + protected double tCPSackRecoveryI; + + protected String tCPSACKReneging; + protected double tCPSACKRenegingI; + + protected String tCPFACKReorder; + protected double tCPFACKReorderI; + + protected String tCPSACKReorder; + protected double tCPSACKReorderI; + + protected String tCPRenoReorder; + protected double tCPRenoReorderI; + + protected String tCPtSReorder; + protected double tCPtSReorderI; + + protected String tCPFullUndo; + protected double tCPFullUndoI; + + protected String tCPPartialUndo; + protected double tCPPartialUndoI; + + protected String tCPDSACKUndo; + protected double tCPDSACKUndoI; + + protected String tCPLossUndo; + protected double tCPLossUndoI; + + protected String tCPLoss; + protected double tCPLossI; + + protected String tCPLostRetransmit; + protected double tCPLostRetransmitI; + + protected String tCPRenoFailures; + protected double tCPRenoFailuresI; + + protected String tCPSackFailures; + protected double tCPSackFailuresI; + + protected String tCPLossFailures; + protected double tCPLossFailuresI; + + protected String tCPFastRetrans; + protected double tCPFastRetransI; + + protected String tCPForwardRetrans; + protected double tCPForwardRetransI; + + protected String tCPSlowStartRetrans; + protected double tCPSlowStartRetransI; + + protected String tCPtimeouts; + protected double tCPtimeoutsI; + + protected String tCPRenoRecoveryFail; + protected double tCPRenoRecoveryFailI; + + protected String tCPSackRecoveryFail; + protected double tCPSackRecoveryFailI; + + protected String tCPSchedulerFailed; + protected double tCPSchedulerFailedI; + + protected String tCPRcvCollapsed; + protected double tCPRcvCollapsedI; + + protected String tCPDSACKOldSent; + protected double tCPDSACKOldSentI; + + protected String tCPDSACKOfoSent; + protected double tCPDSACKOfoSentI; + + protected String tCPDSACKRecv; + protected double tCPDSACKRecvI; + + protected String tCPDSACKOfoRecv; + protected double tCPDSACKOfoRecvI; + + protected String tCPAbortOnSyn; + protected double tCPAbortOnSynI; + + protected String tCPAbortOnData; + protected double tCPAbortOnDataI; + + protected String tCPAbortOnClose; + protected double tCPAbortOnCloseI; + + protected String tCPAbortOnMemory; + protected double tCPAbortOnMemoryI; + + protected String tCPAbortOntimeout; + protected double tCPAbortOntimeoutI; + + protected String tCPAbortOnLinger; + protected double tCPAbortOnLingerI; + + protected String tCPAbortFailed; + protected double tCPAbortFailedI; + + protected String tCPMemoryPressures; + protected double tCPMemoryPressuresI; + + public TCPExtStatistics() { + super(); + } + + public final void setSyncookiesSent(final String syncookiesSent, final double syncookiesSentI) { + this.syncookiesSent = syncookiesSent; + this.syncookiesSentI = syncookiesSentI; + } + + public final String getSyncookiesSent() { + return syncookiesSent; + } + + public final double getSyncookiesSentI() { + return syncookiesSentI; + } + + public final String getSyncookiesSentAsString() { + return syncookiesSent + " SYN cookies sent"; + } + + public final void setSyncookiesRecv(final String syncookiesRecv, final double syncookiesRecvI) { + this.syncookiesRecv = syncookiesRecv; + this.syncookiesRecvI = syncookiesRecvI; + } + + public final String getSyncookiesRecv() { + return syncookiesRecv; + } + + public final double getSyncookiesRecvI() { + return syncookiesRecvI; + } + + public final String getSyncookiesRecvAsString() { + return syncookiesRecv + " SYN cookies received"; + } + + public final void setSyncookiesFailed(final String syncookiesFailed, final double syncookiesFailedI) { + this.syncookiesFailed = syncookiesFailed; + this.syncookiesFailedI = syncookiesFailedI; + } + + public final String getSyncookiesFailed() { + return syncookiesFailed; + } + + public final double getSyncookiesFailedI() { + return syncookiesFailedI; + } + + public final String getSyncookiesFailedAsString() { + return syncookiesFailed + " invalid SYN cookies received"; + } + + public final void setEmbryonicRsts(final String embryonicRsts, final double embryonicRstsI) { + this.embryonicRsts = embryonicRsts; + this.embryonicRstsI = embryonicRstsI; + } + + public final String getEmbryonicRsts() { + return embryonicRsts; + } + + public final double getEmbryonicRstsI() { + return embryonicRstsI; + } + + public final String getEmbryonicRstsAsString() { + return embryonicRsts + " resets received for embryonic SYN_RECV sockets"; + } + + public final void setPruneCalled(final String pruneCalled, final double pruneCalledI) { + this.pruneCalled = pruneCalled; + this.pruneCalledI = pruneCalledI; + } + + public final String getPruneCalled() { + return pruneCalled; + } + + public final double getPruneCalledI() { + return pruneCalledI; + } + + public final String getPruneCalledAsString() { + return pruneCalled + " packets pruned from receive queue because of socket buffer overrun"; + } + + public final void setRcvPruned(final String rcvPruned, final double rcvPrunedI) { + this.rcvPruned = rcvPruned; + this.rcvPrunedI = rcvPrunedI; + } + + public final String getRcvPruned() { + return rcvPruned; + } + + public final double getRcvPrunedI() { + return rcvPrunedI; + } + + public final String getRcvPrunedAsString() { + return rcvPruned + " packets pruned from receive queue"; + } + + public final void setOfoPruned(final String ofoPruned, final double ofoPrunedI) { + this.ofoPruned = ofoPruned; + this.ofoPrunedI = ofoPrunedI; + } + + public final String getOfoPruned() { + return ofoPruned; + } + + public final double getOfoPrunedI() { + return ofoPrunedI; + } + + public final String getOfoPrunedAsString() { + return ofoPruned + " packets dropped from out-of-order queue because of socket buffer overrun"; + } + + public final void setTW(final String tW, final double tWI) { + this.tW = tW; + this.tWI = tWI; + } + + public final String getTW() { + return tW; + } + + public final double getTWI() { + return tWI; + } + + public final String getTWAsString() { + return tW + " TCP sockets finished time wait in fast timer"; + } + + public final void setTWRecycled(final String tWRecycled, final double tWRecycledI) { + this.tWRecycled = tWRecycled; + this.tWRecycledI = tWRecycledI; + } + + public final String getTWRecycled() { + return tWRecycled; + } + + public final double getTWRecycledI() { + return tWRecycledI; + } + + public final String getTWRecycledAsString() { + return tWRecycled + " time wait sockets recycled by time stamp"; + } + + public final void setTWKilled(final String tWKilled, final double tWKilledI) { + this.tWKilled = tWKilled; + this.tWKilledI = tWKilledI; + } + + public final String getTWKilled() { + return tWKilled; + } + + public final double getTWKilledI() { + return tWKilledI; + } + + public final String getTWKilledAsString() { + return tWKilled + " TCP sockets finished time wait in slow timer"; + } + + public final void setPAWSPassive(final String pAWSPassive, final double pAWSPassiveI) { + this.pAWSPassive = pAWSPassive; + this.pAWSPassiveI = pAWSPassiveI; + } + + public final String getPAWSPassive() { + return pAWSPassive; + } + + public final double getPAWSPassiveI() { + return pAWSPassiveI; + } + + public final String getPAWSPassiveAsString() { + return pAWSPassive + " passive connections rejected because of time stamp"; + } + + public final void setPAWSActive(final String pAWSActive, final double pAWSActiveI) { + this.pAWSActive = pAWSActive; + this.pAWSActiveI = pAWSActiveI; + } + + public final String getPAWSActive() { + return pAWSActive; + } + + public final double getPAWSActiveI() { + return pAWSActiveI; + } + + public final String getPAWSActiveAsString() { + return pAWSActive + " active connections rejected because of time stamp"; + } + + public final void setPAWSEstab(final String pAWSEstab, final double pAWSEstabI) { + this.pAWSEstab = pAWSEstab; + this.pAWSEstabI = pAWSEstabI; + } + + public final String getPAWSEstab() { + return pAWSEstab; + } + + public final double getPAWSEstabI() { + return pAWSEstabI; + } + + public final String getPAWSEstabAsString() { + return pAWSEstab + " packets rejects in established connections because of timestamp"; + } + + public final void setDelayedACKs(final String delayedACKs, final double delayedACKsI) { + this.delayedACKs = delayedACKs; + this.delayedACKsI = delayedACKsI; + } + + public final String getDelayedACKs() { + return delayedACKs; + } + + public final double getDelayedACKsI() { + return delayedACKsI; + } + + public final String getDelayedACKsAsString() { + return delayedACKs + " delayed acks sent"; + } + + public final void setDelayedACKLocked(final String delayedACKLocked, final double delayedACKLockedI) { + this.delayedACKLocked = delayedACKLocked; + this.delayedACKLockedI = delayedACKLockedI; + } + + public final String getDelayedACKLocked() { + return delayedACKLocked; + } + + public final double getDelayedACKLockedI() { + return delayedACKLockedI; + } + + public final String getDelayedACKLockedAsString() { + return delayedACKLocked + " delayed acks further delayed because of locked socket"; + } + + public final void setDelayedACKLost(final String delayedACKLost, final double delayedACKLostI) { + this.delayedACKLost = delayedACKLost; + this.delayedACKLostI = delayedACKLostI; + } + + public final String getDelayedACKLost() { + return delayedACKLost; + } + + public final double getDelayedACKLostI() { + return delayedACKLostI; + } + + public final String getDelayedACKLostAsString() { + return "Quick ack mode was activated " + delayedACKLost + " times"; + } + + public final void setListenOverflows(final String listenOverflows, final double listenOverflowsI) { + this.listenOverflows = listenOverflows; + this.listenOverflowsI = listenOverflowsI; + } + + public final String getListenOverflows() { + return listenOverflows; + } + + public final double getListenOverflowsI() { + return listenOverflowsI; + } + + public final String getListenOverflowsAsString() { + return listenOverflows + " times the listen queue of a socket overflowed"; + } + + public final void setListenDrops(final String listenDrops, final double listenDropsI) { + this.listenDrops = listenDrops; + this.listenDropsI = listenDropsI; + } + + public final String getListenDrops() { + return listenDrops; + } + + public final double getListenDropsI() { + return listenDropsI; + } + + public final String getListenDropsAsString() { + return listenDrops + " SYNs to LISTEN sockets ignored"; + } + + public final void setTCPPrequeued(final String tCPPrequeued, final double tCPPrequeuedI) { + this.tCPPrequeued = tCPPrequeued; + this.tCPPrequeuedI = tCPPrequeuedI; + } + + public final String getTCPPrequeued() { + return tCPPrequeued; + } + + public final double getTCPPrequeuedI() { + return tCPPrequeuedI; + } + + public final String getTCPPrequeuedAsString() { + return tCPPrequeued + " packets directly queued to recvmsg prequeue"; + } + + public final void setTCPDirectCopyFromBacklog(final String tCPDirectCopyFromBacklog, final double tCPDirectCopyFromBacklogI) { + this.tCPDirectCopyFromBacklog = tCPDirectCopyFromBacklog; + this.tCPDirectCopyFromBacklogI = tCPDirectCopyFromBacklogI; + } + + public final String getTCPDirectCopyFromBacklog() { + return tCPDirectCopyFromBacklog; + } + + public final double getTCPDirectCopyFromBacklogI() { + return tCPDirectCopyFromBacklogI; + } + + public final String getTCPDirectCopyFromBacklogAsString() { + return tCPDirectCopyFromBacklog + " packets directly received from backlog"; + } + + public final void setTCPDirectCopyFromPrequeue(final String tCPDirectCopyFromPrequeue, final double tCPDirectCopyFromPrequeueI) { + this.tCPDirectCopyFromPrequeue = tCPDirectCopyFromPrequeue; + this.tCPDirectCopyFromPrequeueI = tCPDirectCopyFromPrequeueI; + } + + public final String getTCPDirectCopyFromPrequeue() { + return tCPDirectCopyFromPrequeue; + } + + public final double getTCPDirectCopyFromPrequeueI() { + return tCPDirectCopyFromPrequeueI; + } + + public final String getTCPDirectCopyFromPrequeueAsString() { + return tCPDirectCopyFromPrequeue + " packets directly received from prequeue"; + } + + public final void setTCPPrequeueDropped(final String tCPPrequeueDropped, final double tCPPrequeueDroppedI) { + this.tCPPrequeueDropped = tCPPrequeueDropped; + this.tCPPrequeueDroppedI = tCPPrequeueDroppedI; + } + + public final String getTCPPrequeueDropped() { + return tCPPrequeueDropped; + } + + public final double getTCPPrequeueDroppedI() { + return tCPPrequeueDroppedI; + } + + public final String getTCPPrequeueDroppedAsString() { + return tCPPrequeueDropped + " packets dropped from prequeue"; + } + + public final void setTCPHPHits(final String tCPHPHits, final double tCPHPHitsI) { + this.tCPHPHits = tCPHPHits; + this.tCPHPHitsI = tCPHPHitsI; + } + + public final String getTCPHPHits() { + return tCPHPHits; + } + + public final double getTCPHPHitsI() { + return tCPHPHitsI; + } + + public final String getTCPHPHitsAsString() { + return tCPHPHits + " packets header predicted"; + } + + public final void setTCPHPHitsToUser(final String tCPHPHitsToUser, final double tCPHPHitsToUserI) { + this.tCPHPHitsToUser = tCPHPHitsToUser; + this.tCPHPHitsToUserI = tCPHPHitsToUserI; + } + + public final String getTCPHPHitsToUser() { + return tCPHPHitsToUser; + } + + public final double getTCPHPHitsToUserI() { + return tCPHPHitsToUserI; + } + + public final String getTCPHPHitsToUserAsString() { + return tCPHPHitsToUser + " packets header predicted and directly queued to user"; + } + + public final void setSockMallocOOM(final String sockMallocOOM, final double sockMallocOOMI) { + this.sockMallocOOM = sockMallocOOM; + this.sockMallocOOMI = sockMallocOOMI; + } + + public final String getSockMallocOOM() { + return sockMallocOOM; + } + + public final double getSockMallocOOMI() { + return sockMallocOOMI; + } + + public final String getSockMallocOOMAsString() { + return "Ran " + sockMallocOOM + " times out of system memory during packet sending"; + } + + public final void setTCPPureAcks(final String tCPPureAcks, final double tCPPureAcksI) { + this.tCPPureAcks = tCPPureAcks; + this.tCPPureAcksI = tCPPureAcksI; + } + + public final String getTCPPureAcks() { + return tCPPureAcks; + } + + public final double getTCPPureAcksI() { + return tCPPureAcksI; + } + + public final void setTCPHPAcks(final String tCPHPAcks, final double tCPHPAcksI) { + this.tCPHPAcks = tCPHPAcks; + this.tCPHPAcksI = tCPHPAcksI; + } + + public final String getTCPHPAcks() { + return tCPHPAcks; + } + + public final double getTCPHPAcksI() { + return tCPHPAcksI; + } + + public final void setTCPRenoRecovery(final String tCPRenoRecovery, final double tCPRenoRecoveryI) { + this.tCPRenoRecovery = tCPRenoRecovery; + this.tCPRenoRecoveryI = tCPRenoRecoveryI; + } + + public final String getTCPRenoRecovery() { + return tCPRenoRecovery; + } + + public final double getTCPRenoRecoveryI() { + return tCPRenoRecoveryI; + } + + public final void setTCPSackRecovery(final String tCPSackRecovery, final double tCPSackRecoveryI) { + this.tCPSackRecovery = tCPSackRecovery; + this.tCPSackRecoveryI = tCPSackRecoveryI; + } + + public final String getTCPSackRecovery() { + return tCPSackRecovery; + } + + public final double getTCPSackRecoveryI() { + return tCPSackRecoveryI; + } + + public String toString() { + final StringBuffer buf = new StringBuffer(); + buf.append("Extended TCP Statistics:\n"); + buf.append(getSyncookiesSentAsString()).append("\n"); + buf.append(getSyncookiesRecvAsString()).append("\n"); + buf.append(getSyncookiesFailedAsString()).append("\n"); + buf.append(getEmbryonicRstsAsString()).append("\n"); + buf.append(getPruneCalledAsString()).append("\n"); + buf.append(getRcvPrunedAsString()).append("\n"); + buf.append(getOfoPrunedAsString()).append("\n"); + buf.append(getTWAsString()).append("\n"); + buf.append(getTWRecycledAsString()).append("\n"); + buf.append(getTWKilledAsString()).append("\n"); + buf.append(getPAWSPassiveAsString()).append("\n"); + buf.append(getPAWSActiveAsString()).append("\n"); + buf.append(getPAWSEstabAsString()).append("\n"); + buf.append(getDelayedACKsAsString()).append("\n"); + buf.append(getDelayedACKLockedAsString()).append("\n"); + buf.append(getDelayedACKLostAsString()).append("\n"); + buf.append(getListenOverflowsAsString()).append("\n"); + buf.append(getListenDropsAsString()).append("\n"); + buf.append(getTCPPrequeuedAsString()).append("\n"); + buf.append(getTCPDirectCopyFromBacklogAsString()).append("\n"); + buf.append(getTCPDirectCopyFromPrequeueAsString()).append("\n"); + buf.append(getTCPPrequeueDroppedAsString()).append("\n"); + buf.append(getTCPHPHitsAsString()).append("\n"); + buf.append(getTCPHPHitsToUserAsString()).append("\n"); + buf.append(getSockMallocOOMAsString()).append("\n"); + return buf.toString(); + } + + public void setTCPDSACKUndo(String undo, double undoI) { + tCPDSACKUndo = undo; + tCPDSACKUndoI = undoI; + } + + public String getTCPDSACKUndo() { + return tCPDSACKUndo; + } + + public double getTCPDSACKUndoI() { + return tCPDSACKUndoI; + } + + public void setTCPFACKReorder(String reorder, double reorderI) { + tCPFACKReorder = reorder; + tCPFACKReorderI = reorderI; + } + + public String getTCPFACKReorder() { + return tCPFACKReorder; + } + + public double getTCPFACKReorderI() { + return tCPFACKReorderI; + } + + public void setTCPFullUndo(String fullUndo, double fullUndoI) { + tCPFullUndo = fullUndo; + tCPFullUndoI = fullUndoI; + } + + public String getTCPFullUndo() { + return tCPFullUndo; + } + + public double getTCPFullUndoI() { + return tCPFullUndoI; + } + + public void setTCPLoss(String loss, double lossI) { + tCPLoss = loss; + tCPLossI = lossI; + } + + public String getTCPLoss() { + return tCPLoss; + } + + public double getTCPLossI() { + return tCPLossI; + } + + public void setTCPLossFailures(String lossFailures, double lossFailuresI) { + tCPLossFailures = lossFailures; + tCPLossFailuresI = lossFailuresI; + } + + public String getTCPLossFailures() { + return tCPLossFailures; + } + + public double getTCPLossFailuresI() { + return tCPLossFailuresI; + } + + public void setTCPLossUndo(String lossUndo, double lossUndoI) { + tCPLossUndo = lossUndo; + tCPLossUndoI = lossUndoI; + } + + public String getTCPLossUndo() { + return tCPLossUndo; + } + + public double getTCPLossUndoI() { + return tCPLossUndoI; + } + + public void setTCPLostRetransmit(String lostRetransmit, double lostRetransmitI) { + tCPLostRetransmit = lostRetransmit; + tCPLostRetransmitI = lostRetransmitI; + } + + public String getTCPLostRetransmit() { + return tCPLostRetransmit; + } + + public double getTCPLostRetransmitI() { + return tCPLostRetransmitI; + } + + public void setTCPPartialUndo(String partialUndo, double partialUndoI) { + tCPPartialUndo = partialUndo; + tCPPartialUndoI = partialUndoI; + } + + public String getTCPPartialUndo() { + return tCPPartialUndo; + } + + public double getTCPPartialUndoI() { + return tCPPartialUndoI; + } + + public void setTCPRenoFailures(String renoFailures, double renoFailuresI) { + tCPRenoFailures = renoFailures; + tCPRenoFailuresI = renoFailuresI; + } + + public String getTCPRenoFailures() { + return tCPRenoFailures; + } + + public double getTCPRenoFailuresI() { + return tCPRenoFailuresI; + } + + public void setTCPRenoReorder(String renoReorder, double renoReorderI) { + tCPRenoReorder = renoReorder; + tCPRenoReorderI = renoReorderI; + } + + public String getTCPRenoReorder() { + return tCPRenoReorder; + } + + public double getTCPRenoReorderI() { + return tCPRenoReorderI; + } + + public void setTCPSackFailures(String sackFailures, double sackFailuresI) { + tCPSackFailures = sackFailures; + tCPSackFailuresI = sackFailuresI; + } + + public String getTCPSackFailures() { + return tCPSackFailures; + } + + public double getTCPSackFailuresI() { + return tCPSackFailuresI; + } + + public void setTCPSACKReneging(String reneging, double renegingI) { + tCPSACKReneging = reneging; + tCPSACKRenegingI = renegingI; + } + + public String getTCPSACKReneging() { + return tCPSACKReneging; + } + + public double getTCPSACKRenegingI() { + return tCPSACKRenegingI; + } + + public void setTCPSACKReorder(String reorder, double reorderI) { + tCPSACKReorder = reorder; + tCPSACKReorderI = reorderI; + } + + public String getTCPSACKReorder() { + return tCPSACKReorder; + } + + public double getTCPSACKReorderI() { + return tCPSACKReorderI; + } + + public void setTCPTSReorder(String ptSReorder, double ptSReorderI) { + tCPtSReorder = ptSReorder; + tCPtSReorderI = ptSReorderI; + } + + public String getTCPTSReorder() { + return tCPtSReorder; + } + + public double getTCPTSReorderI() { + return tCPtSReorderI; + } + + public void setTCPAbortFailed(String abortFailed, double abortFailedI) { + tCPAbortFailed = abortFailed; + tCPAbortFailedI = abortFailedI; + } + + public String getTCPAbortFailed() { + return tCPAbortFailed; + } + + public double getTCPAbortFailedI() { + return tCPAbortFailedI; + } + + public void setTCPAbortOnClose(String abortOnClose, double abortOnCloseI) { + tCPAbortOnClose = abortOnClose; + tCPAbortOnCloseI = abortOnCloseI; + } + + public String getTCPAbortOnClose() { + return tCPAbortOnClose; + } + + public double getTCPAbortOnCloseI() { + return tCPAbortOnCloseI; + } + + public void setTCPAbortOnData(String abortOnData, double abortOnDataI) { + tCPAbortOnData = abortOnData; + tCPAbortOnDataI = abortOnDataI; + } + + public String getTCPAbortOnData() { + return tCPAbortOnData; + } + + public double getTCPAbortOnDataI() { + return tCPAbortOnDataI; + } + + public void setTCPAbortOnLinger(String abortOnLinger, double abortOnLingerI) { + tCPAbortOnLinger = abortOnLinger; + tCPAbortOnLingerI = abortOnLingerI; + } + + public String getTCPAbortOnLinger() { + return tCPAbortOnLinger; + } + + public double getTCPAbortOnLingerI() { + return tCPAbortOnLingerI; + } + + public void setTCPAbortOnMemory(String abortOnMemory, double abortOnMemoryI) { + tCPAbortOnMemory = abortOnMemory; + tCPAbortOnMemoryI = abortOnMemoryI; + } + + public String getTCPAbortOnMemory() { + return tCPAbortOnMemory; + } + + public double getTCPAbortOnMemoryI() { + return tCPAbortOnMemoryI; + } + + public void setTCPAbortOnSyn(String abortOnSyn, double abortOnSynI) { + tCPAbortOnSyn = abortOnSyn; + tCPAbortOnSynI = abortOnSynI; + } + + public String getTCPAbortOnSyn() { + return tCPAbortOnSyn; + } + + public double getTCPAbortOnSynI() { + return tCPAbortOnSynI; + } + + public void setTCPAbortOnTimeout(String abortOntimeout, double abortOntimeoutI) { + tCPAbortOntimeout = abortOntimeout; + tCPAbortOntimeoutI = abortOntimeoutI; + } + + public String getTCPAbortOnTimeout() { + return tCPAbortOntimeout; + } + + public double getTCPAbortOnTimeoutI() { + return tCPAbortOntimeoutI; + } + + public void setTCPDSACKOfoRecv(String ofoRecv, double ofoRecvI) { + tCPDSACKOfoRecv = ofoRecv; + tCPDSACKOfoRecvI = ofoRecvI; + } + + public String getTCPDSACKOfoRecv() { + return tCPDSACKOfoRecv; + } + + public double getTCPDSACKOfoRecvI() { + return tCPDSACKOfoRecvI; + } + + public void setTCPDSACKOfoSent(String ofoSent, double ofoSentI) { + tCPDSACKOfoSent = ofoSent; + tCPDSACKOfoSentI = ofoSentI; + } + + public String getTCPDSACKOfoSent() { + return tCPDSACKOfoSent; + } + + public double getTCPDSACKOfoSentI() { + return tCPDSACKOfoSentI; + } + + public void setTCPDSACKOldSent(String oldSent, double oldSentI) { + tCPDSACKOldSent = oldSent; + tCPDSACKOldSentI = oldSentI; + } + + public String getTCPDSACKOldSent() { + return tCPDSACKOldSent; + } + + public double getTCPDSACKOldSentI() { + return tCPDSACKOldSentI; + } + + public void setTCPDSACKRecv(String recv, double recvI) { + tCPDSACKRecv = recv; + tCPDSACKRecvI = recvI; + } + + public String getTCPDSACKRecv() { + return tCPDSACKRecv; + } + + public double getTCPDSACKRecvI() { + return tCPDSACKRecvI; + } + + public void setTCPFastRetrans(String fastRetrans, double fastRetransI) { + tCPFastRetrans = fastRetrans; + tCPFastRetransI = fastRetransI; + } + + public String getTCPFastRetrans() { + return tCPFastRetrans; + } + + public double getTCPFastRetransI() { + return tCPFastRetransI; + } + + public void setTCPForwardRetrans(String forwardRetrans, double forwardRetransI) { + tCPForwardRetrans = forwardRetrans; + tCPForwardRetransI = forwardRetransI; + } + + public String getTCPForwardRetrans() { + return tCPForwardRetrans; + } + + public double getTCPForwardRetransI() { + return tCPForwardRetransI; + } + + public void setTCPMemoryPressures(String memoryPressures, double memoryPressuresI) { + tCPMemoryPressures = memoryPressures; + tCPMemoryPressuresI = memoryPressuresI; + } + + public String getTCPMemoryPressures() { + return tCPMemoryPressures; + } + + public double getTCPMemoryPressuresI() { + return tCPMemoryPressuresI; + } + + public void setTCPRcvCollapsed(String rcvCollapsed, double rcvCollapsedI) { + tCPRcvCollapsed = rcvCollapsed; + tCPRcvCollapsedI = rcvCollapsedI; + } + + public String getTCPRcvCollapsed() { + return tCPRcvCollapsed; + } + + public double getTCPRcvCollapsedI() { + return tCPRcvCollapsedI; + } + + public void setTCPRenoRecoveryFail(String renoRecoveryFail, double renoRecoveryFailI) { + tCPRenoRecoveryFail = renoRecoveryFail; + tCPRenoRecoveryFailI = renoRecoveryFailI; + } + + public String getTCPRenoRecoveryFail() { + return tCPRenoRecoveryFail; + } + + public double getTCPRenoRecoveryFailI() { + return tCPRenoRecoveryFailI; + } + + public void setTCPSackRecoveryFail(String sackRecoveryFail, double sackRecoveryFailI) { + tCPSackRecoveryFail = sackRecoveryFail; + tCPSackRecoveryFailI = sackRecoveryFailI; + } + + public String getTCPSackRecoveryFail() { + return tCPSackRecoveryFail; + } + + public double getTCPSackRecoveryFailI() { + return tCPSackRecoveryFailI; + } + + public void setTCPSchedulerFailed(String schedulerFailed, double schedulerFailedI) { + tCPSchedulerFailed = schedulerFailed; + tCPSchedulerFailedI = schedulerFailedI; + } + + public String getTCPSchedulerFailed() { + return tCPSchedulerFailed; + } + + public double getTCPSchedulerFailedI() { + return tCPSchedulerFailedI; + } + + public void setTCPSlowStartRetrans(String slowStartRetrans, double slowStartRetransI) { + tCPSlowStartRetrans = slowStartRetrans; + tCPSlowStartRetransI = slowStartRetransI; + } + + public String getTCPSlowStartRetrans() { + return tCPSlowStartRetrans; + } + + public double getTCPSlowStartRetransI() { + return tCPSlowStartRetransI; + } + + public void setTCPTimeouts(String ptimeouts, double ptimeoutsI) { + tCPtimeouts = ptimeouts; + tCPtimeoutsI = ptimeoutsI; + } + + public String getTCPTimeouts() { + return tCPtimeouts; + } + + public double getTCPTimeoutsI() { + return tCPtimeoutsI; + } } // end of class TCPExtStatistics diff --git a/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPStatistics.java b/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPStatistics.java index f38ccb9..551a2e1 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPStatistics.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/statistics/TCPStatistics.java @@ -7,271 +7,296 @@ /** * Statistics regarding the tcp protocol suite + * * @author Ciprian Dobre */ public class TCPStatistics extends Statistics { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - /** The minimum restransmission timeout value */ - protected long rToMin = 0; - - /** The maximum retransmission timeout value */ - protected long rToMax = 0; - - /** Active connection openned */ - protected String activeOpens = "0"; - protected double activeOpensI = 0D; - - /** Passive connection openned */ - protected String passiveOpens = "0"; - protected double passiveOpensI = 0D; - - /** Failed connection attempts */ - protected String attemptFails = "0"; - protected double attemptFailsI = 0D; - - /** Connection resets received */ - protected String estabResets = "0"; - protected double estabResetsI = 0D; - - /** Connections established */ - protected long currEstab = 0; - - /** Segments received */ - protected String inSegs = "0"; - protected double inSegsI = 0; - - /** Segments sent */ - protected String outSegs = "0"; - protected double outSegsI = 0D; - - /** Segments retransmitted */ - protected String retransSegs = "0"; - protected double retransSegsI = 0D; - - /** Bad segments received */ - protected String inErrs = "0"; - protected double inErrsI = 0D; - - /** Resets sent */ - protected String outRsts = "0"; - protected double outRstsI = 0D; - - public TCPStatistics() { - super(); - } - - public final void setRToMin(final long rToMin) { - this.rToMin = rToMin; - } - - public final long getRToMin() { - return rToMin; - } - - public final String getRToMinAsString() { - return "The minimum retransmission timeout value is "+rToMin; - } - - public final void setRToMax(final long rToMax) { - this.rToMax = rToMax; - } - - public final long getRToMax() { - return rToMax; - } - - public final String getRToMaxAsString() { - return "The maximum retransmission timeout value is "+rToMax; - } - - public final void setActiveOpens(final String activeOpens, final double activeOpensI) { - this.activeOpens = activeOpens; - this.activeOpensI = activeOpensI; - } - - public final String getActiveOpens() { - return activeOpens; - } - - public final double getActiveOpensI() { - return activeOpensI; - } - - public final String getActiveOpensAsString() { - return activeOpens+" active connections openings"; - } - - public final void setPassiveOpens(final String passiveOpens, final double passiveOpensI) { - this.passiveOpens = passiveOpens; - this.passiveOpensI = passiveOpensI; - } - - public final String getPassiveOpens() { - return passiveOpens; - } - - public final double getPassiveOpensI() { - return passiveOpensI; - } - - public final String getPassiveOpensAsString() { - return passiveOpens+" passive connection openings"; - } - - public final void setAttemptFails(final String attemptFails, final double attemptFailsI) { - this.attemptFails = attemptFails; - this.attemptFailsI = attemptFailsI; - } - - public final String getAttemptFails() { - return attemptFails; - } - - public final double getAttemptFailsI() { - return attemptFailsI; - } - - public final String getAttemptFailsAsString() { - return attemptFails+" failed connection attempts"; - } - - public final void setEstabResets(final String estabResets, final double estabResetsI) { - this.estabResets = estabResets; - this.estabResetsI = estabResetsI; - } - - public final String getEstabResets() { - return estabResets; - } - - public final double getEstabResetsI() { - return estabResetsI; - } - - public final String getEstabResetsAsString() { - return estabResets+" connection resets received"; - } - - public final void setCurrEstab(final long currEstab) { - this.currEstab = currEstab; - } - - public final long getCurrEstab() { - return currEstab; - } - - public final String getCurrEstabAsString() { - return currEstab+" connections established"; - } - - public final void setInSegs(final String inSegs, final double inSegsI) { - this.inSegs = inSegs; - this.inSegsI = inSegsI; - } - - public final String getInSegs() { - return inSegs; - } - - public final double getInSegsI() { - return inSegsI; - } - - public final String getInSegsAsString() { - return inSegs+" segments received"; - } - - public final void setOutSegs(final String outSegs, final double outSegsI) { - this.outSegs = outSegs; - this.outSegsI = outSegsI; - } - - public final String getOutSegs() { - return outSegs; - } - - public final double getOutSegsI() { - return outSegsI; - } - - public final String getOutSegsAsString() { - return outSegs+" segments send out"; - } - - public final void setRetransSegs(final String retransSegs, final double retransSegsI) { - this.retransSegs = retransSegs; - this.retransSegsI = retransSegsI; - } - - public final String getRetransSegs() { - return retransSegs; - } - - public final double getRetransSegsI() { - return retransSegsI; - } - - public final String getRetransSegsAsString() { - return retransSegs+" segments retransmited"; - } - - public final void setInErrs(final String inErrs, final double inErrsI) { - this.inErrs = inErrs; - this.inErrsI = inErrsI; - } - - public final String getInErrs() { - return inErrs; - } - - public final double getInErrsI() { - return inErrsI; - } - - public final String getInErrsAsString() { - return inErrs+" bad segments received"; - } - - public final void setOutRsts(final String outRsts, final double outRstsI) { - this.outRsts = outRsts; - this.outRstsI = outRstsI; - } - - public final String getOutRsts() { - return outRsts; - } - - public final double getOutRstsI() { - return outRstsI; - } - - public final String getOutRstsAsString() { - return outRsts+" resets sent"; - } - - public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append("TCP Statistics:\n"); - buf.append(getRToMinAsString()).append("\n"); - buf.append(getRToMaxAsString()).append("\n"); - buf.append(getActiveOpensAsString()).append("\n"); - buf.append(getPassiveOpensAsString()).append("\n"); - buf.append(getAttemptFailsAsString()).append("\n"); - buf.append(getEstabResetsAsString()).append("\n"); - buf.append(getCurrEstabAsString()).append("\n"); - buf.append(getInSegsAsString()).append("\n"); - buf.append(getOutSegsAsString()).append("\n"); - buf.append(getRetransSegsAsString()).append("\n"); - buf.append(getInErrsAsString()).append("\n"); - buf.append(getOutRstsAsString()).append("\n"); - return buf.toString(); - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + /** + * The minimum restransmission timeout value + */ + protected long rToMin = 0; + + /** + * The maximum retransmission timeout value + */ + protected long rToMax = 0; + + /** + * Active connection openned + */ + protected String activeOpens = "0"; + protected double activeOpensI = 0D; + + /** + * Passive connection openned + */ + protected String passiveOpens = "0"; + protected double passiveOpensI = 0D; + + /** + * Failed connection attempts + */ + protected String attemptFails = "0"; + protected double attemptFailsI = 0D; + + /** + * Connection resets received + */ + protected String estabResets = "0"; + protected double estabResetsI = 0D; + + /** + * Connections established + */ + protected long currEstab = 0; + + /** + * Segments received + */ + protected String inSegs = "0"; + protected double inSegsI = 0; + + /** + * Segments sent + */ + protected String outSegs = "0"; + protected double outSegsI = 0D; + + /** + * Segments retransmitted + */ + protected String retransSegs = "0"; + protected double retransSegsI = 0D; + + /** + * Bad segments received + */ + protected String inErrs = "0"; + protected double inErrsI = 0D; + + /** + * Resets sent + */ + protected String outRsts = "0"; + protected double outRstsI = 0D; + + public TCPStatistics() { + super(); + } + + public final long getRToMin() { + return rToMin; + } + + public final void setRToMin(final long rToMin) { + this.rToMin = rToMin; + } + + public final String getRToMinAsString() { + return "The minimum retransmission timeout value is " + rToMin; + } + + public final long getRToMax() { + return rToMax; + } + + public final void setRToMax(final long rToMax) { + this.rToMax = rToMax; + } + + public final String getRToMaxAsString() { + return "The maximum retransmission timeout value is " + rToMax; + } + + public final void setActiveOpens(final String activeOpens, final double activeOpensI) { + this.activeOpens = activeOpens; + this.activeOpensI = activeOpensI; + } + + public final String getActiveOpens() { + return activeOpens; + } + + public final double getActiveOpensI() { + return activeOpensI; + } + + public final String getActiveOpensAsString() { + return activeOpens + " active connections openings"; + } + + public final void setPassiveOpens(final String passiveOpens, final double passiveOpensI) { + this.passiveOpens = passiveOpens; + this.passiveOpensI = passiveOpensI; + } + + public final String getPassiveOpens() { + return passiveOpens; + } + + public final double getPassiveOpensI() { + return passiveOpensI; + } + + public final String getPassiveOpensAsString() { + return passiveOpens + " passive connection openings"; + } + + public final void setAttemptFails(final String attemptFails, final double attemptFailsI) { + this.attemptFails = attemptFails; + this.attemptFailsI = attemptFailsI; + } + + public final String getAttemptFails() { + return attemptFails; + } + + public final double getAttemptFailsI() { + return attemptFailsI; + } + + public final String getAttemptFailsAsString() { + return attemptFails + " failed connection attempts"; + } + + public final void setEstabResets(final String estabResets, final double estabResetsI) { + this.estabResets = estabResets; + this.estabResetsI = estabResetsI; + } + + public final String getEstabResets() { + return estabResets; + } + + public final double getEstabResetsI() { + return estabResetsI; + } + + public final String getEstabResetsAsString() { + return estabResets + " connection resets received"; + } + + public final long getCurrEstab() { + return currEstab; + } + + public final void setCurrEstab(final long currEstab) { + this.currEstab = currEstab; + } + + public final String getCurrEstabAsString() { + return currEstab + " connections established"; + } + + public final void setInSegs(final String inSegs, final double inSegsI) { + this.inSegs = inSegs; + this.inSegsI = inSegsI; + } + + public final String getInSegs() { + return inSegs; + } + + public final double getInSegsI() { + return inSegsI; + } + + public final String getInSegsAsString() { + return inSegs + " segments received"; + } + + public final void setOutSegs(final String outSegs, final double outSegsI) { + this.outSegs = outSegs; + this.outSegsI = outSegsI; + } + + public final String getOutSegs() { + return outSegs; + } + + public final double getOutSegsI() { + return outSegsI; + } + + public final String getOutSegsAsString() { + return outSegs + " segments send out"; + } + + public final void setRetransSegs(final String retransSegs, final double retransSegsI) { + this.retransSegs = retransSegs; + this.retransSegsI = retransSegsI; + } + + public final String getRetransSegs() { + return retransSegs; + } + + public final double getRetransSegsI() { + return retransSegsI; + } + + public final String getRetransSegsAsString() { + return retransSegs + " segments retransmited"; + } + + public final void setInErrs(final String inErrs, final double inErrsI) { + this.inErrs = inErrs; + this.inErrsI = inErrsI; + } + + public final String getInErrs() { + return inErrs; + } + + public final double getInErrsI() { + return inErrsI; + } + + public final String getInErrsAsString() { + return inErrs + " bad segments received"; + } + + public final void setOutRsts(final String outRsts, final double outRstsI) { + this.outRsts = outRsts; + this.outRstsI = outRstsI; + } + + public final String getOutRsts() { + return outRsts; + } + + public final double getOutRstsI() { + return outRstsI; + } + + public final String getOutRstsAsString() { + return outRsts + " resets sent"; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("TCP Statistics:\n"); + buf.append(getRToMinAsString()).append("\n"); + buf.append(getRToMaxAsString()).append("\n"); + buf.append(getActiveOpensAsString()).append("\n"); + buf.append(getPassiveOpensAsString()).append("\n"); + buf.append(getAttemptFailsAsString()).append("\n"); + buf.append(getEstabResetsAsString()).append("\n"); + buf.append(getCurrEstabAsString()).append("\n"); + buf.append(getInSegsAsString()).append("\n"); + buf.append(getOutSegsAsString()).append("\n"); + buf.append(getRetransSegsAsString()).append("\n"); + buf.append(getInErrsAsString()).append("\n"); + buf.append(getOutRstsAsString()).append("\n"); + return buf.toString(); + } + } // end of class TCPStatistics diff --git a/src/lia/util/net/copy/monitoring/lisa/net/statistics/UDPStatistics.java b/src/lia/util/net/copy/monitoring/lisa/net/statistics/UDPStatistics.java index 15e9a2a..5d5e804 100644 --- a/src/lia/util/net/copy/monitoring/lisa/net/statistics/UDPStatistics.java +++ b/src/lia/util/net/copy/monitoring/lisa/net/statistics/UDPStatistics.java @@ -7,112 +7,121 @@ /** * Statistics regarding the udp protocol suite + * * @author Ciprian Dobre */ public class UDPStatistics extends Statistics { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1988671591829311032L; - - /** Datagrams received */ - protected String inDatagrams = "0"; - protected double inDatagramsI = 0D; - - /** Datagrams to unknown ports received */ - protected String noPorts = "0"; - protected double noPortsI = 0D; - - /** Datagrams with errors received */ - protected String inErrors = "0"; - protected double inErrorsI = 0D; - - /** Sent datagrams */ - protected String outDatagrams = "0"; - protected double outDatagramsI = 0D; - - public UDPStatistics() { - super(); - } - - public final void setInDatagrams(final String inDatagrams, final double inDatagramsI) { - this.inDatagrams = inDatagrams; - this.inDatagramsI = inDatagramsI; - } - - public final String getInDatagrams() { - return inDatagrams; - } - - public final double getInDatagramsI() { - return inDatagramsI; - } - - public final String getInDatagramsAsString() { - return inDatagrams+" datagrams received"; - } - - public final void setNoPorts(final String noPorts, final double noPortsI) { - this.noPorts = noPorts; - this.noPortsI = noPortsI; - } - - public final String getNoPorts() { - return noPorts; - } - - public final double getNoPortsI() { - return noPortsI; - } - - public final String getNoPortsAsString() { - return noPorts+" datagrams to unknown port received"; - } - - public final void setInErrors(final String inErrors, final double inErrorsI) { - this.inErrors = inErrors; - this.inErrorsI = inErrorsI; - } - - public final String getInErrors() { - return inErrors; - } - - public final double getInErrorsI() { - return inErrorsI; - } - - public final String getInErrorsAsString() { - return inErrors+" datagrams received with errors"; - } - - public final void setOutDatagrams(final String outDatagrams, final double outDatagramsI) { - this.outDatagrams = outDatagrams; - this.outDatagramsI = outDatagramsI; - } - - public final String getOutDatagrams() { - return outDatagrams; - } - - public final double getOutDatagramsI() { - return outDatagramsI; - } - - public final String getOutDatagramsAsString() { - return outDatagrams+" datagrams sent"; - } - - public String toString() { - final StringBuffer buf = new StringBuffer(); - buf.append("UDP Statistics:\n"); - buf.append(getInDatagramsAsString()).append("\n"); - buf.append(getNoPortsAsString()).append("\n"); - buf.append(getInErrorsAsString()).append("\n"); - buf.append(getOutDatagramsAsString()).append("\n"); - return buf.toString(); - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1988671591829311032L; + + /** + * Datagrams received + */ + protected String inDatagrams = "0"; + protected double inDatagramsI = 0D; + + /** + * Datagrams to unknown ports received + */ + protected String noPorts = "0"; + protected double noPortsI = 0D; + + /** + * Datagrams with errors received + */ + protected String inErrors = "0"; + protected double inErrorsI = 0D; + + /** + * Sent datagrams + */ + protected String outDatagrams = "0"; + protected double outDatagramsI = 0D; + + public UDPStatistics() { + super(); + } + + public final void setInDatagrams(final String inDatagrams, final double inDatagramsI) { + this.inDatagrams = inDatagrams; + this.inDatagramsI = inDatagramsI; + } + + public final String getInDatagrams() { + return inDatagrams; + } + + public final double getInDatagramsI() { + return inDatagramsI; + } + + public final String getInDatagramsAsString() { + return inDatagrams + " datagrams received"; + } + + public final void setNoPorts(final String noPorts, final double noPortsI) { + this.noPorts = noPorts; + this.noPortsI = noPortsI; + } + + public final String getNoPorts() { + return noPorts; + } + + public final double getNoPortsI() { + return noPortsI; + } + + public final String getNoPortsAsString() { + return noPorts + " datagrams to unknown port received"; + } + + public final void setInErrors(final String inErrors, final double inErrorsI) { + this.inErrors = inErrors; + this.inErrorsI = inErrorsI; + } + + public final String getInErrors() { + return inErrors; + } + + public final double getInErrorsI() { + return inErrorsI; + } + + public final String getInErrorsAsString() { + return inErrors + " datagrams received with errors"; + } + + public final void setOutDatagrams(final String outDatagrams, final double outDatagramsI) { + this.outDatagrams = outDatagrams; + this.outDatagramsI = outDatagramsI; + } + + public final String getOutDatagrams() { + return outDatagrams; + } + + public final double getOutDatagramsI() { + return outDatagramsI; + } + + public final String getOutDatagramsAsString() { + return outDatagrams + " datagrams sent"; + } + + public String toString() { + final StringBuffer buf = new StringBuffer(); + buf.append("UDP Statistics:\n"); + buf.append(getInDatagramsAsString()).append("\n"); + buf.append(getNoPortsAsString()).append("\n"); + buf.append(getInErrorsAsString()).append("\n"); + buf.append(getOutDatagramsAsString()).append("\n"); + return buf.toString(); + } + } // end of class UDPStatistics diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/SocketFactory.java b/src/lia/util/net/copy/monitoring/lisa/xdr/SocketFactory.java index 0993c53..07eca34 100755 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/SocketFactory.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/SocketFactory.java @@ -4,6 +4,7 @@ package lia.util.net.copy.monitoring.lisa.xdr; +import javax.net.ssl.*; import java.io.FileInputStream; import java.io.IOException; import java.net.InetAddress; @@ -14,204 +15,198 @@ import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - /** * Factory used to create ssl server sockets. NO client authentication is done. Usefull when a password-based authentication over a secure channel is needed - * + * * @author Adrian Muraru */ public class SocketFactory { - /** Logger name */ - private static final transient String COMPONENT = "lisa.comm.net.TLSSocketFactory"; - - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger(COMPONENT); - - /** maximum time to connect with the other endPoint */ - private static final int CONNECT_TIMEOUT = 15 * 1000; // 15s - - public static SSLServerSocket createServerSocket(int port, String keystore, String password) throws IOException { - return createServerSocket(port, keystore, password, false); - } - - /** - * @param port: - * port to listen on - * @param store: - * the path to keystore file containing server key pair (private/public key); if null is passed - * @param passwd: - * password needed to access keystore file - * @return a SSL Socket bound on port specified - * @throws IOException - */ - public static SSLServerSocket createServerSocket(int port, String keystore, String password, boolean needClientAuth) throws IOException { - SSLServerSocketFactory ssf = null; - SSLServerSocket ss = null; - - try { - SSLContext ctx; - KeyManagerFactory kmf; - KeyStore ks; - ctx = SSLContext.getInstance("TLS"); - kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - ks = KeyStore.getInstance(KeyStore.getDefaultType()); - FileInputStream is = new FileInputStream(keystore); - ks.load(is, password.toCharArray()); - kmf.init(ks, password.toCharArray()); - if (logger.isLoggable(Level.FINER)) - logger.log(Level.FINER, "Server keys loaded"); - - ctx.init(kmf.getKeyManagers(), null, new java.security.SecureRandom()); - ssf = ctx.getServerSocketFactory(); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "Creating SSocket"); - } - ss = (SSLServerSocket) ssf.createServerSocket(); - - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "SSocket created!"); - } - - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "SSocket binding on port " + port); - } - ss.bind(new InetSocketAddress(port)); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "SSocket bounded on port " + port); - } - ss.setNeedClientAuth(needClientAuth); - // this socket will try to authenticate clients based on X.509 Certificates - // ss.setWantClientAuth(true); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "SSocket FINISHED ok! Bounded on " + port); - } - - } catch (Throwable t) { - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "Got Exception", t); - } - t.printStackTrace(); - throw new IOException(t.getMessage()); - } - return ss; - } - - /** - * Creates a client socket connected to a TLS capable server - no server authentication is performed, it accept any server certificate (anonymous TLS sessions) todo, host-based - * authentication should be performed in future - * - * @param host - * @param port - * @return - * @throws IOException - */ - public static Socket createClientSocket(String host, int port, boolean ssl) throws IOException { - - if (!ssl) { - // Create a socket with a timeout - InetAddress addr = InetAddress.getByName(host); - SocketAddress sockaddr = new InetSocketAddress(addr, port); - // Create an unbound socket - Socket sock = new Socket(); - // This method will block no more than timeoutMs. - // If the timeout occurs, SocketTimeoutException is thrown. - int timeoutMs = 2000; // 2 seconds - sock.connect(sockaddr, timeoutMs); - return sock; - } - - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCertsTM = new TrustManager[] { new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - public void checkClientTrusted(@SuppressWarnings("unused") - java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") - String authType) { - } - - public void checkServerTrusted(@SuppressWarnings("unused") - java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") - String authTsype) { - } - } }; - - SSLSocketFactory factory = null; - SSLContext ctx; - - try { - ctx = SSLContext.getInstance("TLS"); - ctx.init(null, trustAllCertsTM, null); - factory = ctx.getSocketFactory(); - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - - SSLSocket socket = (SSLSocket) factory.createSocket(); - socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT); - return socket; - } - - /** - * Create an SSL client socket that will provide authentication for itself based on the supplied keystore - * - * @param host - * @param port - * @param keystore - * @param password - * @return - * @throws IOException - */ - public static Socket createAuthClientSocket(String host, int port, String keystore, String password) throws IOException { - - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCertsTM = new TrustManager[] { new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - public void checkClientTrusted(@SuppressWarnings("unused") - java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") - String authType) { - } - - public void checkServerTrusted(@SuppressWarnings("unused") - java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") - String authTsype) { - } - } }; - - SSLSocketFactory factory = null; - SSLContext ctx; - - try { - KeyManagerFactory kmf; - KeyStore ks; - ctx = SSLContext.getInstance("TLS"); - kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - ks = KeyStore.getInstance(KeyStore.getDefaultType()); - FileInputStream is = new FileInputStream(keystore); - ks.load(is, password.toCharArray()); - kmf.init(ks, password.toCharArray()); - ctx.init(kmf.getKeyManagers(), trustAllCertsTM, null); - factory = ctx.getSocketFactory(); - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - - SSLSocket socket = (SSLSocket) factory.createSocket(); - socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT); - return socket; - } + /** + * Logger name + */ + private static final transient String COMPONENT = "lisa.comm.net.TLSSocketFactory"; + + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger(COMPONENT); + + /** + * maximum time to connect with the other endPoint + */ + private static final int CONNECT_TIMEOUT = 15 * 1000; // 15s + + public static SSLServerSocket createServerSocket(int port, String keystore, String password) throws IOException { + return createServerSocket(port, keystore, password, false); + } + + /** + * @param port: port to listen on + * @param keystore: the path to keystore file containing server key pair (private/public key); if null is passed + * @param password: password needed to access keystore file + * @return a SSL Socket bound on port specified + * @throws IOException + */ + public static SSLServerSocket createServerSocket(int port, String keystore, String password, boolean needClientAuth) throws IOException { + SSLServerSocketFactory ssf = null; + SSLServerSocket ss = null; + + try { + SSLContext ctx; + KeyManagerFactory kmf; + KeyStore ks; + ctx = SSLContext.getInstance("TLS"); + kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + FileInputStream is = new FileInputStream(keystore); + ks.load(is, password.toCharArray()); + kmf.init(ks, password.toCharArray()); + if (logger.isLoggable(Level.FINER)) + logger.log(Level.FINER, "Server keys loaded"); + + ctx.init(kmf.getKeyManagers(), null, new java.security.SecureRandom()); + ssf = ctx.getServerSocketFactory(); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "Creating SSocket"); + } + ss = (SSLServerSocket) ssf.createServerSocket(); + + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "SSocket created!"); + } + + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "SSocket binding on port " + port); + } + ss.bind(new InetSocketAddress(port)); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "SSocket bounded on port " + port); + } + ss.setNeedClientAuth(needClientAuth); + // this socket will try to authenticate clients based on X.509 Certificates + // ss.setWantClientAuth(true); + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "SSocket FINISHED ok! Bounded on " + port); + } + + } catch (Throwable t) { + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "Got Exception", t); + } + t.printStackTrace(); + throw new IOException(t.getMessage()); + } + return ss; + } + + /** + * Creates a client socket connected to a TLS capable server - no server authentication is performed, it accept any server certificate (anonymous TLS sessions) todo, host-based + * authentication should be performed in future + * + * @param host + * @param port + * @return + * @throws IOException + */ + public static Socket createClientSocket(String host, int port, boolean ssl) throws IOException { + + if (!ssl) { + // Create a socket with a timeout + InetAddress addr = InetAddress.getByName(host); + SocketAddress sockaddr = new InetSocketAddress(addr, port); + // Create an unbound socket + Socket sock = new Socket(); + // This method will block no more than timeoutMs. + // If the timeout occurs, SocketTimeoutException is thrown. + int timeoutMs = 2000; // 2 seconds + sock.connect(sockaddr, timeoutMs); + return sock; + } + + // Create a trust manager that does not validate certificate chains + TrustManager[] trustAllCertsTM = new TrustManager[]{new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[0]; + } + + public void checkClientTrusted(@SuppressWarnings("unused") + java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") + String authType) { + } + + public void checkServerTrusted(@SuppressWarnings("unused") + java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") + String authTsype) { + } + }}; + + SSLSocketFactory factory = null; + SSLContext ctx; + + try { + ctx = SSLContext.getInstance("TLS"); + ctx.init(null, trustAllCertsTM, null); + factory = ctx.getSocketFactory(); + } catch (Exception e) { + throw new IOException(e.getMessage()); + } + + SSLSocket socket = (SSLSocket) factory.createSocket(); + socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT); + return socket; + } + + /** + * Create an SSL client socket that will provide authentication for itself based on the supplied keystore + * + * @param host + * @param port + * @param keystore + * @param password + * @return + * @throws IOException + */ + public static Socket createAuthClientSocket(String host, int port, String keystore, String password) throws IOException { + + // Create a trust manager that does not validate certificate chains + TrustManager[] trustAllCertsTM = new TrustManager[]{new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[0]; + } + + public void checkClientTrusted(@SuppressWarnings("unused") + java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") + String authType) { + } + + public void checkServerTrusted(@SuppressWarnings("unused") + java.security.cert.X509Certificate[] certs, @SuppressWarnings("unused") + String authTsype) { + } + }}; + + SSLSocketFactory factory = null; + SSLContext ctx; + + try { + KeyManagerFactory kmf; + KeyStore ks; + ctx = SSLContext.getInstance("TLS"); + kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + FileInputStream is = new FileInputStream(keystore); + ks.load(is, password.toCharArray()); + kmf.init(ks, password.toCharArray()); + ctx.init(kmf.getKeyManagers(), trustAllCertsTM, null); + factory = ctx.getSocketFactory(); + } catch (Exception e) { + throw new IOException(e.getMessage()); + } + + SSLSocket socket = (SSLSocket) factory.createSocket(); + socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT); + return socket; + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRClient.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRClient.java index b23d962..235d4cd 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRClient.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRClient.java @@ -7,135 +7,137 @@ /** * java implementation of XDR Client. extends - * + * * @author Lucian Musat */ public final class XDRClient extends XDRTcpSocket { - /** state of xdr connection * */ - // private int state = NO_STATE; - // public static final int NO_STATE = 0; - // public static final int STATE_INIT = 1; - // public static final int STATE_SESSION = 2; - // public static final int STATE_CLOSED = 3; - /** puts the xdr session in wait mode because it is not used, at least not yet */ - private Object waitObj = new Object(); - /** if should wait on the wait object or not */ - private volatile boolean bWait = true; - - protected XDRClient(String sHost, int nPort) throws IOException { - super(SocketFactory.createClientSocket(sHost, nPort, false)); - } - - protected XDRClient(String sHost, int nPort, boolean ssl) throws IOException { - super(SocketFactory.createClientSocket(sHost, nPort, ssl)); - } - - /** - * creates a new client connection with the xdr lisa server - * - * @param sHost - * hostname of lisa - * @param nPort - * port on which the xdr daemon listens - * @return new xdr client or null if it could not be created or started - */ - public static XDRClient getClient(String sHost, int nPort, boolean ssl) { - XDRClient cl = null; - try { - cl = new XDRClient(sHost, nPort,ssl); - new Thread(cl).start(); - } catch (IOException e) { - e.printStackTrace(); - cl = null; - } - return cl; - } - - public static XDRClient getClient(String sHost, int nPort) { - return getClient(sHost, nPort, false); - } - - /** - * @param args - */ - public static void main(String[] args) { - if (args.length < 2) { - System.err.println("Too few parameters.\nUsage: "); - System.exit(-1); - } - int nPort = 0; - try { - nPort = Integer.parseInt(args[1]); - } catch (Exception ex) { - System.err.println("Invalid port number.\nUsage: "); - System.exit(-1); - } - try { - new Thread(new XDRClient(args[0], nPort)).start(); - } catch (IOException e) { - e.printStackTrace(); - System.exit(-1); - } - } - - @Override - protected void initSession() throws Exception { - // TODO do authentication - System.out.println("Init connection"); - // state = STATE_INIT; - } - - @Override - protected void xdrSession() throws Exception { - // TODO do xdr session: exchange of information, main loop - System.out.println("XDR session started"); - // state = STATE_SESSION; - if (bWait) { - try { - synchronized (waitObj) { - waitObj.wait(); - } - } catch (InterruptedException ex) { - - } - } - System.out.println("XDR session ended"); - } - - public void close() { - super.close(); - // notify the xdr session that should finish - // why here and not in notify... is because the notify will be called after xdrSession ends - bWait = false; - synchronized (waitObj) { - waitObj.notify(); - } - } - - @Override - protected void notifyXDRCommClosed() { - // TODO this end was just notified of connection closed - System.out.println("Connection closed"); - // state = STATE_CLOSED; - } - - synchronized public String sendCommand(String sCommand) throws Exception { - XDRMessage msg = XDRMessage.getSuccessMessage(sCommand); - XDRMessage resMsg = null; - write(msg); - resMsg = read(); - - if (resMsg == null) - throw new Exception("no connection!!!"); - if (resMsg.status == XDRMessage.ERROR) - throw new Exception(resMsg.payload); - - return resMsg.payload; - } - - public boolean isClosed() { - return super.closed; - } + /** state of xdr connection * */ + // private int state = NO_STATE; + // public static final int NO_STATE = 0; + // public static final int STATE_INIT = 1; + // public static final int STATE_SESSION = 2; + // public static final int STATE_CLOSED = 3; + /** + * puts the xdr session in wait mode because it is not used, at least not yet + */ + private Object waitObj = new Object(); + /** + * if should wait on the wait object or not + */ + private volatile boolean bWait = true; + + protected XDRClient(String sHost, int nPort) throws IOException { + super(SocketFactory.createClientSocket(sHost, nPort, false)); + } + + protected XDRClient(String sHost, int nPort, boolean ssl) throws IOException { + super(SocketFactory.createClientSocket(sHost, nPort, ssl)); + } + + /** + * creates a new client connection with the xdr lisa server + * + * @param sHost hostname of lisa + * @param nPort port on which the xdr daemon listens + * @return new xdr client or null if it could not be created or started + */ + public static XDRClient getClient(String sHost, int nPort, boolean ssl) { + XDRClient cl = null; + try { + cl = new XDRClient(sHost, nPort, ssl); + new Thread(cl).start(); + } catch (IOException e) { + e.printStackTrace(); + cl = null; + } + return cl; + } + + public static XDRClient getClient(String sHost, int nPort) { + return getClient(sHost, nPort, false); + } + + /** + * @param args + */ + public static void main(String[] args) { + if (args.length < 2) { + System.err.println("Too few parameters.\nUsage: "); + System.exit(-1); + } + int nPort = 0; + try { + nPort = Integer.parseInt(args[1]); + } catch (Exception ex) { + System.err.println("Invalid port number.\nUsage: "); + System.exit(-1); + } + try { + new Thread(new XDRClient(args[0], nPort)).start(); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + } + + @Override + protected void initSession() throws Exception { + // TODO do authentication + System.out.println("Init connection"); + // state = STATE_INIT; + } + + @Override + protected void xdrSession() throws Exception { + // TODO do xdr session: exchange of information, main loop + System.out.println("XDR session started"); + // state = STATE_SESSION; + if (bWait) { + try { + synchronized (waitObj) { + waitObj.wait(); + } + } catch (InterruptedException ex) { + + } + } + System.out.println("XDR session ended"); + } + + public void close() { + super.close(); + // notify the xdr session that should finish + // why here and not in notify... is because the notify will be called after xdrSession ends + bWait = false; + synchronized (waitObj) { + waitObj.notify(); + } + } + + @Override + protected void notifyXDRCommClosed() { + // TODO this end was just notified of connection closed + System.out.println("Connection closed"); + // state = STATE_CLOSED; + } + + synchronized public String sendCommand(String sCommand) throws Exception { + XDRMessage msg = XDRMessage.getSuccessMessage(sCommand); + XDRMessage resMsg = null; + write(msg); + resMsg = read(); + + if (resMsg == null) + throw new Exception("no connection!!!"); + if (resMsg.status == XDRMessage.ERROR) + throw new Exception(resMsg.payload); + + return resMsg.payload; + } + + public boolean isClosed() { + return super.closed; + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataInput.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataInput.java index c4844a2..2d0ad52 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataInput.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataInput.java @@ -9,56 +9,56 @@ /** * An interface implemented by input streams that support XDR. - * + *

    * The XDR format is almost identical to the normal Java DataInput - * format, except that items are padded to 4 byte boundaries, and + * format, except that items are padded to 4 byte boundaries, and * strings are normally stored in ASCII format. + * * @author Tony Johnson (tonyj@slac.stanford.edu) */ -public interface XDRDataInput extends DataInput -{ - /** - * Skips appropriate amount to bring stream to 4-byte boundary. - */ - void pad() throws IOException; +public interface XDRDataInput extends DataInput { + /** + * Skips appropriate amount to bring stream to 4-byte boundary. + */ + void pad() throws IOException; - /** - * Reads a double array. Assumes int length proceeds array. - * Throws an exception if array length > 32767 to protect - * against bad data exhausting memory. If buffer is not null, - * and is large enough to hold array, it is filled and returned, - * otherwise a new array is allocated and returned. - */ - double[] readDoubleArray(double[] buffer) throws IOException; + /** + * Reads a double array. Assumes int length proceeds array. + * Throws an exception if array length > 32767 to protect + * against bad data exhausting memory. If buffer is not null, + * and is large enough to hold array, it is filled and returned, + * otherwise a new array is allocated and returned. + */ + double[] readDoubleArray(double[] buffer) throws IOException; - /** - * Reads a float array. Assumes int length proceeds array. - * Throws an exception if array length > 32767 to protect - * against bad data exhausting memory. If buffer is not null, - * and is large enough to hold array, it is filled and returned, - * otherwise a new array is allocated and returned. - */ - float[] readFloatArray(float[] buffer) throws IOException; + /** + * Reads a float array. Assumes int length proceeds array. + * Throws an exception if array length > 32767 to protect + * against bad data exhausting memory. If buffer is not null, + * and is large enough to hold array, it is filled and returned, + * otherwise a new array is allocated and returned. + */ + float[] readFloatArray(float[] buffer) throws IOException; - /** - * Reads an integer array. Assumes int length proceeds array. - * Throws an exception if array length > 32767 to protect - * against bad data exhausting memory. If buffer is not null, - * and is large enough to hold array, it is filled and returned, - * otherwise a new array is allocated and returned. - */ - int[] readIntArray(int[] buffer) throws IOException; + /** + * Reads an integer array. Assumes int length proceeds array. + * Throws an exception if array length > 32767 to protect + * against bad data exhausting memory. If buffer is not null, + * and is large enough to hold array, it is filled and returned, + * otherwise a new array is allocated and returned. + */ + int[] readIntArray(int[] buffer) throws IOException; - /** - * Read a String. Assumes int length proceeds String. - * Throws an exception if string length > 32767 to protect - * against bad data exhausting memory. - */ - String readString() throws IOException; + /** + * Read a String. Assumes int length proceeds String. + * Throws an exception if string length > 32767 to protect + * against bad data exhausting memory. + */ + String readString() throws IOException; - /** - * Reads a String of length l bytes, and skips appropriate - * amount to bring stream to 4-byte boundary. - */ - String readString(int l) throws IOException; + /** + * Reads a String of length l bytes, and skips appropriate + * amount to bring stream to 4-byte boundary. + */ + String readString(int l) throws IOException; } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataOutput.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataOutput.java index b2bdf1f..a40207b 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataOutput.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRDataOutput.java @@ -9,31 +9,31 @@ /** * An interface implemented by output streams that support XDR. + * * @author Tony Johnson (tonyj@slac.stanford.edu) */ -public interface XDRDataOutput extends DataOutput -{ - void pad() throws IOException; +public interface XDRDataOutput extends DataOutput { + void pad() throws IOException; - void writeDoubleArray(double[] array) throws IOException; + void writeDoubleArray(double[] array) throws IOException; - void writeDoubleArray(double[] array, int start, int n) throws IOException; + void writeDoubleArray(double[] array, int start, int n) throws IOException; - void writeFloatArray(float[] array) throws IOException; + void writeFloatArray(float[] array) throws IOException; - void writeFloatArray(float[] array, int start, int n) throws IOException; + void writeFloatArray(float[] array, int start, int n) throws IOException; - void writeIntArray(int[] array) throws IOException; + void writeIntArray(int[] array) throws IOException; - void writeIntArray(int[] array, int start, int n) throws IOException; + void writeIntArray(int[] array, int start, int n) throws IOException; - /** - * Write a string preceeded by its (int) length - */ - void writeString(String string) throws IOException; + /** + * Write a string preceeded by its (int) length + */ + void writeString(String string) throws IOException; - /** - * Write a string (no length is written) - */ - void writeStringChars(String string) throws IOException; + /** + * Write a string (no length is written) + */ + void writeStringChars(String string) throws IOException; } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRGenericComm.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRGenericComm.java index 9cda1ed..560c41a 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRGenericComm.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRGenericComm.java @@ -10,213 +10,213 @@ import java.util.logging.Logger; /** - * * @author Adrian Muraru */ public abstract class XDRGenericComm - implements Runnable { - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger(XDRGenericComm.class.getName()); - - //XDRSessionHandler sessionHandler; - String myName; - protected XDRInputStream xdris; - protected XDROutputStream xdros; - protected boolean closed; - private static long keys; - private static Object keyLock; - private String key; - - static { - keyLock = new Object(); - synchronized (keyLock) { - keys = 0; - } - } - - public XDRGenericComm(String myName, XDROutputStream xdros, XDRInputStream xdris, boolean closed) { - //this.sessionHandler = sessionHandler; - setMyName(myName); - this.xdris = xdris; - this.xdros = xdros; - this.closed = closed; - key = null; - - } - - public XDRGenericComm(String myName, XDROutputStream xdros, XDRInputStream xdris) { - this(myName,xdros,xdris,false); - } - - - public XDRGenericComm(String myName) { - this(myName,null,null,true); - } - - /** - * Called just before the session start - * Can be used for authentication - * - * @throws {@link Exception} - * if comm. session could not be established - */ - protected abstract void initSession() throws Exception; - - /** - * XDR Session protocol - * - * @throws {@link Exception} - * if session is broken and it should be closed - */ - protected abstract void xdrSession() throws Exception; - - /** - * Called just before the session closing. Should be used to do upper-layers - * cleanups - */ - protected abstract void notifyXDRCommClosed(); - - public void run() { - try { - initSession(); - } catch (Exception e) { - if (logger.isLoggable(Level.WARNING)) - logger.log(Level.WARNING, "Session [" + System.currentTimeMillis() + " ] " + myName + " K: [" + getKey() + "] cannot be initialized..closing", e); - notifyXDRCommClosed(); - close(); - return; - } - // else: session successfully init'ed ...enter main loop - if (logger.isLoggable(Level.FINE)) - logger.log(Level.FINE," [ " + System.currentTimeMillis() + " ] " + getMyName() + " enter main-loop. "); - - try { - /* + implements Runnable { + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger(XDRGenericComm.class.getName()); + private static long keys; + private static Object keyLock; + + static { + keyLock = new Object(); + synchronized (keyLock) { + keys = 0; + } + } + + protected XDRInputStream xdris; + protected XDROutputStream xdros; + protected boolean closed; + //XDRSessionHandler sessionHandler; + String myName; + private String key; + + public XDRGenericComm(String myName, XDROutputStream xdros, XDRInputStream xdris, boolean closed) { + //this.sessionHandler = sessionHandler; + setMyName(myName); + this.xdris = xdris; + this.xdros = xdros; + this.closed = closed; + key = null; + + } + + public XDRGenericComm(String myName, XDROutputStream xdros, XDRInputStream xdris) { + this(myName, xdros, xdris, false); + } + + + public XDRGenericComm(String myName) { + this(myName, null, null, true); + } + + private static String nextKey() { + synchronized (keyLock) { + return "" + keys++; + } + } + + /** + * Called just before the session start + * Can be used for authentication + * + * @throws {@link Exception} + * if comm. session could not be established + */ + protected abstract void initSession() throws Exception; + + /** + * XDR Session protocol + * + * @throws {@link Exception} + * if session is broken and it should be closed + */ + protected abstract void xdrSession() throws Exception; + + /** + * Called just before the session closing. Should be used to do upper-layers + * cleanups + */ + protected abstract void notifyXDRCommClosed(); + + public void run() { + try { + initSession(); + } catch (Exception e) { + if (logger.isLoggable(Level.WARNING)) + logger.log(Level.WARNING, "Session [" + System.currentTimeMillis() + " ] " + myName + " K: [" + getKey() + "] cannot be initialized..closing", e); + notifyXDRCommClosed(); + close(); + return; + } + // else: session successfully init'ed ...enter main loop + if (logger.isLoggable(Level.FINE)) + logger.log(Level.FINE, " [ " + System.currentTimeMillis() + " ] " + getMyName() + " enter main-loop. "); + + try { + /* * XDRMessage xdrMsg = read(); if (xdrMsg == null) continue; * notifier.notifyXDRMessage(xdrMsg, this); */ - xdrSession(); - } catch (Throwable t) { - - if (logger.isLoggable(Level.WARNING)) - logger.log(Level.WARNING, " [ " + System.currentTimeMillis() + " ] " + getMyName() + " is broken. Closing it.. "); - - StringWriter sw = new StringWriter(); - t.printStackTrace(new PrintWriter(sw)); - XDRMessage msg = XDRMessage.getErrorMessage(sw.getBuffer().toString()); - try { - write(msg); - } catch (Throwable tsend) { - } - } - - notifyXDRCommClosed(); - if (logger.isLoggable(Level.INFO)) - logger.log(Level.INFO, " [ " + System.currentTimeMillis() + " ] " + myName + " K: [" + getKey() + "] exits now .... \n\n"); - close(); - } - - public XDRMessage read() throws IOException { - try { - XDRMessage msg = new XDRMessage(); - msg.xdrMessageSize = xdris.readInt(); - xdris.pad(); - msg.status = xdris.readInt(); - xdris.pad(); - msg.payload = xdris.readString(); - xdris.pad(); - return msg; - }catch(java.io.EOFException eofe){ - logger.log(Level.INFO, " [ " + System.currentTimeMillis() + " ] " + getMyName() + ": Connection closed by remote host"); - throw new IOException (" [ " + System.currentTimeMillis() + " ] " + getMyName() + ": Connection closed by remote host"); - } catch (Throwable t) { - if (logger.isLoggable(Level.FINEST)) - logger.log(Level.FINEST, "XDR Read Error: Cause:", t); - else if ( logger.isLoggable(Level.WARNING) ) - logger.log(Level.WARNING, "XDR Read Error. Cause:["+t.getMessage()+"]"); - throw new IOException ("XDR Read Error: ["+t.getMessage()+"]"); - } - } - - public synchronized void write(XDRMessage msg) throws IOException { - try { - msg.xdrMessageSize = getXDRSize(msg); - - xdros.writeInt(msg.xdrMessageSize); - xdros.pad(); - xdros.writeInt(msg.status); - xdros.pad(); - xdros.writeString(msg.payload); - xdros.pad(); - xdros.flush(); - } catch (Throwable t) { - if (logger.isLoggable(Level.FINEST)) - logger.log(Level.FINEST, "XDR Write Error", t); - else if ( logger.isLoggable(Level.WARNING) ) - logger.log(Level.WARNING, "XDR Write Error. Cause:["+t.getMessage()+"]"); - throw new IOException ("XDR Write error: ["+t.getMessage()+"]"); - } - } - - public void close() { - if (!closed) {// allow multiple invocation for close() - closed = true; - try { - if (xdris != null) - xdris.close(); - if (xdros != null) - xdros.close(); - } catch (Throwable t) { - - } - } - } - - private static String nextKey() { - synchronized (keyLock) { - return "" + keys++; - } - } - - public String getKey() { - if (key == null) { - key = nextKey(); - } - return key; - } - - private int getXDRSize(String data) { - int size = 0; - if (data != null && data.length() != 0) { - size = data.length() + 4; + xdrSession(); + } catch (Throwable t) { + + if (logger.isLoggable(Level.WARNING)) + logger.log(Level.WARNING, " [ " + System.currentTimeMillis() + " ] " + getMyName() + " is broken. Closing it.. "); + + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + XDRMessage msg = XDRMessage.getErrorMessage(sw.getBuffer().toString()); + try { + write(msg); + } catch (Throwable tsend) { + } + } + + notifyXDRCommClosed(); + if (logger.isLoggable(Level.INFO)) + logger.log(Level.INFO, " [ " + System.currentTimeMillis() + " ] " + myName + " K: [" + getKey() + "] exits now .... \n\n"); + close(); + } + + public XDRMessage read() throws IOException { + try { + XDRMessage msg = new XDRMessage(); + msg.xdrMessageSize = xdris.readInt(); + xdris.pad(); + msg.status = xdris.readInt(); + xdris.pad(); + msg.payload = xdris.readString(); + xdris.pad(); + return msg; + } catch (java.io.EOFException eofe) { + logger.log(Level.INFO, " [ " + System.currentTimeMillis() + " ] " + getMyName() + ": Connection closed by remote host"); + throw new IOException(" [ " + System.currentTimeMillis() + " ] " + getMyName() + ": Connection closed by remote host"); + } catch (Throwable t) { + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINEST, "XDR Read Error: Cause:", t); + else if (logger.isLoggable(Level.WARNING)) + logger.log(Level.WARNING, "XDR Read Error. Cause:[" + t.getMessage() + "]"); + throw new IOException("XDR Read Error: [" + t.getMessage() + "]"); + } + } + + public synchronized void write(XDRMessage msg) throws IOException { + try { + msg.xdrMessageSize = getXDRSize(msg); + + xdros.writeInt(msg.xdrMessageSize); + xdros.pad(); + xdros.writeInt(msg.status); + xdros.pad(); + xdros.writeString(msg.payload); + xdros.pad(); + xdros.flush(); + } catch (Throwable t) { + if (logger.isLoggable(Level.FINEST)) + logger.log(Level.FINEST, "XDR Write Error", t); + else if (logger.isLoggable(Level.WARNING)) + logger.log(Level.WARNING, "XDR Write Error. Cause:[" + t.getMessage() + "]"); + throw new IOException("XDR Write error: [" + t.getMessage() + "]"); + } + } + + public void close() { + if (!closed) {// allow multiple invocation for close() + closed = true; + try { + if (xdris != null) + xdris.close(); + if (xdros != null) + xdros.close(); + } catch (Throwable t) { + + } + } + } + + public String getKey() { + if (key == null) { + key = nextKey(); + } + return key; + } + + private int getXDRSize(String data) { + int size = 0; + if (data != null && data.length() != 0) { + size = data.length() + 4; /* * the length of the XDR representation must be a multiple of 4, so * there might be some extra bytes added */ - if (size % 4 != 0) - size += (4 - size % 4); - } - return size; - } - - private int getXDRSize(XDRMessage msg) { - int size = 8; // two integers (size and status) - size += getXDRSize(msg.payload); - return size; - } - - /** - * @return the friendly name of this communication endpoint - */ - public String getMyName() { - return this.myName; - } - - /** - * @param myName: - * communication endpoint friendly name - */ - public void setMyName(String myName) { - this.myName = myName; - } + if (size % 4 != 0) + size += (4 - size % 4); + } + return size; + } + + private int getXDRSize(XDRMessage msg) { + int size = 8; // two integers (size and status) + size += getXDRSize(msg.payload); + return size; + } + + /** + * @return the friendly name of this communication endpoint + */ + public String getMyName() { + return this.myName; + } + + /** + * @param myName: communication endpoint friendly name + */ + public void setMyName(String myName) { + this.myName = myName; + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRInputStream.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRInputStream.java index b6add53..7a6f054 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRInputStream.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRInputStream.java @@ -3,215 +3,187 @@ */ package lia.util.net.copy.monitoring.lisa.xdr; -import java.io.BufferedInputStream; -import java.io.DataInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; /** * A class for reading XDR files. Not too hard to do in Java since the XDR format is very * similar to the Java native DataStream format, except for String and the fact that elements * (ro an array of elements) are always padded to a multiple of 4 bytes. - * + *

    * This class requires the user to call the pad method, to skip to the next * 4-byte boundary after reading an element or array of elements that may not * span a multiple of 4 bytes. + * * @author Tony Johnson (tonyj@slac.stanford.edu) */ -public class XDRInputStream extends DataInputStream implements XDRDataInput -{ - private CountedInputStream cin; - - public XDRInputStream(InputStream in) - { - super(new CountedInputStream(in)); - cin = (CountedInputStream) this.in; - } - - public long getBytesRead() - { - return cin.getBytesRead(); - } - - /** - * Sets a limit on the number of bytes that can be read from this file - * before an EOF will be generated - */ - public void setReadLimit(int bytes) - { - cin.setReadLimit(bytes); - } - - public void clearReadLimit() - { - cin.clearReadLimit(); - } - - /** - * Skips appropriate amount to bring stream to 4-byte boundary. - */ - public void pad() throws IOException - { - int offset = (int) (getBytesRead() % 4); - if (offset != 0) - skipBytes(4 - offset); - } - - public double[] readDoubleArray(double[] buffer) throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - - double[] result = buffer; - if ((buffer == null) || (l > buffer.length)) - result = new double[l]; - for (int i = 0; i < l; i++) - result[i] = readDouble(); - return result; - } - - public float[] readFloatArray(float[] buffer) throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - - float[] result = buffer; - if ((buffer == null) || (l > buffer.length)) - result = new float[l]; - for (int i = 0; i < l; i++) - result[i] = readFloat(); - return result; - } - - public int[] readIntArray(int[] buffer) throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - - int[] result = buffer; - if ((buffer == null) || (l > buffer.length)) - result = new int[l]; - for (int i = 0; i < l; i++) - result[i] = readInt(); - return result; - } - - public String readString(int l) throws IOException - { - byte[] ascii = new byte[l]; - readFully(ascii); - pad(); - return new String(ascii); //BUG: what is default locale is not US-ASCII - } - - public String readString() throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - return readString(l); - } - - private static final class CountedInputStream extends BufferedInputStream - { - private long bcount = 0; - private long limit = -1; - private long mark = 0; - - CountedInputStream(InputStream in) - { - super(in); - } - - public long getBytesRead() - { - return bcount; - } - - public int available() throws IOException - { - return Math.min((int) (limit - bcount), super.available()); - } - - public synchronized void mark(int readlimit) - { - mark = bcount; - super.mark(readlimit); - } - - public int read() throws IOException - { - int available = checkLimit(1); - int rc = super.read(); - if (rc >= 0) - bcount++; - return rc; - } - - public int read(byte[] data) throws IOException - { - return read(data, 0, data.length); - } - - public int read(byte[] data, int off, int len) throws IOException - { - int available = checkLimit(len); - int rc = super.read(data, off, available); - if (rc > 0) - bcount += rc; - return rc; - } - - public synchronized void reset() throws IOException - { - bcount = mark; - super.reset(); - } - - public long skip(long bytes) throws IOException - { - long available = checkLimit(bytes); - long rc = super.skip(available); - if (rc > 0) - bcount += rc; - return rc; - } - - /** - * Sets a limit on the number of bytes that can be read from this file - * before an EOF will be generated - */ - void setReadLimit(int bytes) - { - limit = bcount + bytes; - } - - void clearReadLimit() - { - limit = -1; - } - - private int checkLimit(int request) throws IOException - { - if (limit < 0) - return request; - else if (limit <= bcount) - throw new EOFException(); - return Math.min(request, (int) (limit - bcount)); - } - - private long checkLimit(long request) throws IOException - { - if (limit < 0) - return request; - else if (limit <= bcount) - throw new EOFException(); - return Math.min(request, limit - bcount); - } - } +public class XDRInputStream extends DataInputStream implements XDRDataInput { + private CountedInputStream cin; + + public XDRInputStream(InputStream in) { + super(new CountedInputStream(in)); + cin = (CountedInputStream) this.in; + } + + public long getBytesRead() { + return cin.getBytesRead(); + } + + /** + * Sets a limit on the number of bytes that can be read from this file + * before an EOF will be generated + */ + public void setReadLimit(int bytes) { + cin.setReadLimit(bytes); + } + + public void clearReadLimit() { + cin.clearReadLimit(); + } + + /** + * Skips appropriate amount to bring stream to 4-byte boundary. + */ + public void pad() throws IOException { + int offset = (int) (getBytesRead() % 4); + if (offset != 0) + skipBytes(4 - offset); + } + + public double[] readDoubleArray(double[] buffer) throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + + double[] result = buffer; + if ((buffer == null) || (l > buffer.length)) + result = new double[l]; + for (int i = 0; i < l; i++) + result[i] = readDouble(); + return result; + } + + public float[] readFloatArray(float[] buffer) throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + + float[] result = buffer; + if ((buffer == null) || (l > buffer.length)) + result = new float[l]; + for (int i = 0; i < l; i++) + result[i] = readFloat(); + return result; + } + + public int[] readIntArray(int[] buffer) throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + + int[] result = buffer; + if ((buffer == null) || (l > buffer.length)) + result = new int[l]; + for (int i = 0; i < l; i++) + result[i] = readInt(); + return result; + } + + public String readString(int l) throws IOException { + byte[] ascii = new byte[l]; + readFully(ascii); + pad(); + return new String(ascii); //BUG: what is default locale is not US-ASCII + } + + public String readString() throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + return readString(l); + } + + private static final class CountedInputStream extends BufferedInputStream { + private long bcount = 0; + private long limit = -1; + private long mark = 0; + + CountedInputStream(InputStream in) { + super(in); + } + + public long getBytesRead() { + return bcount; + } + + public int available() throws IOException { + return Math.min((int) (limit - bcount), super.available()); + } + + public synchronized void mark(int readlimit) { + mark = bcount; + super.mark(readlimit); + } + + public int read() throws IOException { + int available = checkLimit(1); + int rc = super.read(); + if (rc >= 0) + bcount++; + return rc; + } + + public int read(byte[] data) throws IOException { + return read(data, 0, data.length); + } + + public int read(byte[] data, int off, int len) throws IOException { + int available = checkLimit(len); + int rc = super.read(data, off, available); + if (rc > 0) + bcount += rc; + return rc; + } + + public synchronized void reset() throws IOException { + bcount = mark; + super.reset(); + } + + public long skip(long bytes) throws IOException { + long available = checkLimit(bytes); + long rc = super.skip(available); + if (rc > 0) + bcount += rc; + return rc; + } + + /** + * Sets a limit on the number of bytes that can be read from this file + * before an EOF will be generated + */ + void setReadLimit(int bytes) { + limit = bcount + bytes; + } + + void clearReadLimit() { + limit = -1; + } + + private int checkLimit(int request) throws IOException { + if (limit < 0) + return request; + else if (limit <= bcount) + throw new EOFException(); + return Math.min(request, (int) (limit - bcount)); + } + + private long checkLimit(long request) throws IOException { + if (limit < 0) + return request; + else if (limit <= bcount) + throw new EOFException(); + return Math.min(request, limit - bcount); + } + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMLMappings.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMLMappings.java index 95a9868..7e8cd25 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMLMappings.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMLMappings.java @@ -4,14 +4,13 @@ package lia.util.net.copy.monitoring.lisa.xdr; /** - * * @author ramiro */ public final class XDRMLMappings { - public static final int XDR_STRING = 0; - public static final int XDR_INT16 = 1; - public static final int XDR_INT32 = 2; - public static final int XDR_INT64 = 3; - public static final int XDR_REAL32 = 4; - public static final int XDR_REAL64 = 5; + public static final int XDR_STRING = 0; + public static final int XDR_INT16 = 1; + public static final int XDR_INT32 = 2; + public static final int XDR_INT64 = 3; + public static final int XDR_REAL32 = 4; + public static final int XDR_REAL64 = 5; } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessage.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessage.java index 5a13490..2373589 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessage.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessage.java @@ -8,60 +8,58 @@ /** * XDR message used by LisaDaemon and different API libraries + * * @author Adrian Muraru */ public class XDRMessage { - public static final int SUCCESS = 0; - public static final int ERROR = 1; + public static final int SUCCESS = 0; + public static final int ERROR = 1; + public int status; + public String payload; + protected int xdrMessageSize; - protected int xdrMessageSize; + /** + * constructs a xdr message containing a text and default status of SUCCESS xdrMessageSize remains uninitialized + * + * @param msg + * @return + */ + public static final XDRMessage getSuccessMessage(String msg) { + XDRMessage m = new XDRMessage(); + m.payload = msg; + m.status = SUCCESS; - public int status; + return m; + } - public String payload; + /** + * constructs a xdr message containing a text and default message tag xdrMessageSize remains uninitialized + * + * @param msg + * @return + */ + public static final XDRMessage getMessage(String msg, int tag) { + XDRMessage m = new XDRMessage(); + m.payload = msg; + m.status = tag; + return m; + } - /** - * constructs a xdr message containing a text and default status of SUCCESS xdrMessageSize remains uninitialized - * - * @param msg - * @return - */ - public static final XDRMessage getSuccessMessage(String msg) { - XDRMessage m = new XDRMessage(); - m.payload = msg; - m.status = SUCCESS; + public static final XDRMessage getErrorMessage(String cause) { + XDRMessage retMsg = new XDRMessage(); + retMsg.payload = cause; + retMsg.status = ERROR; + return retMsg; + } - return m; - } + public static final XDRMessage getErrorMessage(Throwable t) { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + return getErrorMessage(sw.getBuffer().toString()); + } - /** - * constructs a xdr message containing a text and default message tag xdrMessageSize remains uninitialized - * - * @param msg - * @return - */ - public static final XDRMessage getMessage(String msg, int tag) { - XDRMessage m = new XDRMessage(); - m.payload = msg; - m.status = tag; - return m; - } - - public static final XDRMessage getErrorMessage(String cause) { - XDRMessage retMsg = new XDRMessage(); - retMsg.payload = cause; - retMsg.status = ERROR; - return retMsg; - } - - public static final XDRMessage getErrorMessage(Throwable t) { - StringWriter sw = new StringWriter(); - t.printStackTrace(new PrintWriter(sw)); - return getErrorMessage(sw.getBuffer().toString()); - } - - public String toString() { - return "[" + (status == SUCCESS ? "SUCCESS" : status == ERROR ? "ERROR" : String.valueOf(status)) + "] " + payload; - } + public String toString() { + return "[" + (status == SUCCESS ? "SUCCESS" : status == ERROR ? "ERROR" : String.valueOf(status)) + "] " + payload; + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessageNotifier.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessageNotifier.java index e10ad97..42afdc4 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessageNotifier.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRMessageNotifier.java @@ -4,10 +4,10 @@ package lia.util.net.copy.monitoring.lisa.xdr; /** - * * @author Adrian Muraru */ public interface XDRMessageNotifier { public void notifyXDRMessage(XDRMessage message, XDRGenericComm comm); + public void notifyXDRCommClosed(XDRGenericComm comm); } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRNamedPipe.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRNamedPipe.java index 23cf6c6..82ddded 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRNamedPipe.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRNamedPipe.java @@ -12,42 +12,41 @@ import java.io.IOException; /** - * * @author Adrian Muraru */ public class XDRNamedPipe extends XDRGenericComm { - protected File pipeIn; - protected File pipeOut; - - public XDRNamedPipe(String pipeNameIn, String pipeNameOut) throws IOException { - super("XDRNamedPipe for [ " + pipeNameIn + " - " + pipeNameOut + " ] ", new XDROutputStream(new FileOutputStream(new File(pipeNameOut))), new XDRInputStream( - new FileInputStream(new File(pipeNameIn)))); - } - - //create a pipe (not open by default) - public XDRNamedPipe(File pipeIn, File pipeOut) { - super("XDRNamedPipe for [ " + pipeIn + " - " + pipeOut + " ] "); - this.pipeIn = pipeIn; - this.pipeOut=pipeOut; - } - - @Override - protected void initSession() throws Exception { - this.xdris = new XDRInputStream(new FileInputStream(this.pipeIn)); - this.xdros = new XDROutputStream(new FileOutputStream(pipeOut)); - super.closed = false; - } - - @Override - protected void notifyXDRCommClosed() { - - } - - @Override - protected void xdrSession() throws Exception { - // nothing to do on pipes - - } + protected File pipeIn; + protected File pipeOut; + + public XDRNamedPipe(String pipeNameIn, String pipeNameOut) throws IOException { + super("XDRNamedPipe for [ " + pipeNameIn + " - " + pipeNameOut + " ] ", new XDROutputStream(new FileOutputStream(new File(pipeNameOut))), new XDRInputStream( + new FileInputStream(new File(pipeNameIn)))); + } + + //create a pipe (not open by default) + public XDRNamedPipe(File pipeIn, File pipeOut) { + super("XDRNamedPipe for [ " + pipeIn + " - " + pipeOut + " ] "); + this.pipeIn = pipeIn; + this.pipeOut = pipeOut; + } + + @Override + protected void initSession() throws Exception { + this.xdris = new XDRInputStream(new FileInputStream(this.pipeIn)); + this.xdros = new XDROutputStream(new FileOutputStream(pipeOut)); + super.closed = false; + } + + @Override + protected void notifyXDRCommClosed() { + + } + + @Override + protected void xdrSession() throws Exception { + // nothing to do on pipes + + } } \ No newline at end of file diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDROutputStream.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDROutputStream.java index 1338bb0..a85a5cc 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDROutputStream.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDROutputStream.java @@ -12,102 +12,102 @@ * A class for writing XDR files. Not too hard to do in Java since the XDR format is very * similar to the Java native DataStream format, except for String and the fact that elements * (ro an array of elements) are always padded to a multiple of 4 bytes. - * + *

    * This class requires the user to call the pad method, to skip to the next * 4-byte boundary after writing an element or array of elements that may not * span a multiple of 4 bytes. + * * @author Tony Johnson (tonyj@slac.stanford.edu) */ -public class XDROutputStream extends DataOutputStream implements XDRDataOutput -{ - public XDROutputStream(OutputStream out) - { - super(new CountedOutputStream(out)); - cout = (CountedOutputStream) this.out; - } - public void writeString(String s) throws IOException - { - writeInt(s.length()); - byte[] ascii = s.getBytes(); - write(ascii); - pad(); - } - public void writeStringChars(String s) throws IOException - { - byte[] ascii = s.getBytes(); - write(ascii); - pad(); - } - public void writeIntArray(int[] array) throws IOException - { - writeInt(array.length); - for (int i=0; i 32767) - throw new IOException("String too long: " + l); - - double[] result = buffer; - if ((buffer == null) || (l > buffer.length)) - result = new double[l]; - for (int i = 0; i < l; i++) - result[i] = readDouble(); - return result; - } - - public float[] readFloatArray(float[] buffer) throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - - float[] result = buffer; - if ((buffer == null) || (l > buffer.length)) - result = new float[l]; - for (int i = 0; i < l; i++) - result[i] = readFloat(); - return result; - } - - public int[] readIntArray(int[] buffer) throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - - int[] result = buffer; - if ((buffer == null) || (l > buffer.length)) - result = new int[l]; - for (int i = 0; i < l; i++) - result[i] = readInt(); - return result; - } - - public String readString(int l) throws IOException - { - byte[] ascii = new byte[l]; - readFully(ascii); - pad(); - return new String(ascii); //BUG: what is default locale is not US-ASCII - } - - public String readString() throws IOException - { - int l = readInt(); - if (l > 32767) - throw new IOException("String too long: " + l); - return readString(l); - } - - public void writeDoubleArray(double[] array) throws IOException - { - writeInt(array.length); - for (int i = 0; i < array.length; i++) - writeDouble(array[i]); - } - - public void writeDoubleArray(double[] array, int start, int n) throws IOException - { - writeInt(n); - for (int i = start; i < n; i++) - writeDouble(array[i]); - } - - public void writeFloatArray(float[] array) throws IOException - { - writeInt(array.length); - for (int i = 0; i < array.length; i++) - writeFloat(array[i]); - } - - public void writeFloatArray(float[] array, int start, int n) throws IOException - { - writeInt(n); - for (int i = start; i < n; i++) - writeFloat(array[i]); - } - - public void writeIntArray(int[] array) throws IOException - { - writeInt(array.length); - for (int i = 0; i < array.length; i++) - writeInt(array[i]); - } - - public void writeIntArray(int[] array, int start, int n) throws IOException - { - writeInt(n); - for (int i = start; i < n; i++) - writeInt(array[i]); - } - - public void writeString(String s) throws IOException - { - writeInt(s.length()); - - byte[] ascii = s.getBytes(); - write(ascii); - pad(); - } - - public void writeStringChars(String s) throws IOException - { - byte[] ascii = s.getBytes(); - write(ascii); - pad(); - } +public class XDRRandomAccessFile extends RandomAccessFile implements XDRDataInput, XDRDataOutput { + public XDRRandomAccessFile(String name, String mode) throws IOException { + super(name, mode); + } + + public void pad() throws IOException { + int offset = (int) (getFilePointer() % 4); + if (offset != 0) + skipBytes(4 - offset); + } + + public double[] readDoubleArray(double[] buffer) throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + + double[] result = buffer; + if ((buffer == null) || (l > buffer.length)) + result = new double[l]; + for (int i = 0; i < l; i++) + result[i] = readDouble(); + return result; + } + + public float[] readFloatArray(float[] buffer) throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + + float[] result = buffer; + if ((buffer == null) || (l > buffer.length)) + result = new float[l]; + for (int i = 0; i < l; i++) + result[i] = readFloat(); + return result; + } + + public int[] readIntArray(int[] buffer) throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + + int[] result = buffer; + if ((buffer == null) || (l > buffer.length)) + result = new int[l]; + for (int i = 0; i < l; i++) + result[i] = readInt(); + return result; + } + + public String readString(int l) throws IOException { + byte[] ascii = new byte[l]; + readFully(ascii); + pad(); + return new String(ascii); //BUG: what is default locale is not US-ASCII + } + + public String readString() throws IOException { + int l = readInt(); + if (l > 32767) + throw new IOException("String too long: " + l); + return readString(l); + } + + public void writeDoubleArray(double[] array) throws IOException { + writeInt(array.length); + for (int i = 0; i < array.length; i++) + writeDouble(array[i]); + } + + public void writeDoubleArray(double[] array, int start, int n) throws IOException { + writeInt(n); + for (int i = start; i < n; i++) + writeDouble(array[i]); + } + + public void writeFloatArray(float[] array) throws IOException { + writeInt(array.length); + for (int i = 0; i < array.length; i++) + writeFloat(array[i]); + } + + public void writeFloatArray(float[] array, int start, int n) throws IOException { + writeInt(n); + for (int i = start; i < n; i++) + writeFloat(array[i]); + } + + public void writeIntArray(int[] array) throws IOException { + writeInt(array.length); + for (int i = 0; i < array.length; i++) + writeInt(array[i]); + } + + public void writeIntArray(int[] array, int start, int n) throws IOException { + writeInt(n); + for (int i = start; i < n; i++) + writeInt(array[i]); + } + + public void writeString(String s) throws IOException { + writeInt(s.length()); + + byte[] ascii = s.getBytes(); + write(ascii); + pad(); + } + + public void writeStringChars(String s) throws IOException { + byte[] ascii = s.getBytes(); + write(ascii); + pad(); + } } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRSerializable.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRSerializable.java index 9829d63..a34b9bb 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRSerializable.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRSerializable.java @@ -8,11 +8,11 @@ /** * An interface to be implemented by objects than * can be read and written using XDR + * * @author Tony Johnson (tonyj@slac.stanford.edu) */ -public interface XDRSerializable -{ - public void read(XDRDataInput in) throws IOException; +public interface XDRSerializable { + public void read(XDRDataInput in) throws IOException; - public void write(XDRDataOutput out) throws IOException; + public void write(XDRDataOutput out) throws IOException; } diff --git a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRTcpSocket.java b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRTcpSocket.java index ce263c7..596f52f 100644 --- a/src/lia/util/net/copy/monitoring/lisa/xdr/XDRTcpSocket.java +++ b/src/lia/util/net/copy/monitoring/lisa/xdr/XDRTcpSocket.java @@ -9,32 +9,31 @@ import java.util.logging.Logger; /** - * * @author Adrian Muraru */ -public abstract class XDRTcpSocket - extends XDRGenericComm { - - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger("lisa.comm.XDRTcpSocket"); +public abstract class XDRTcpSocket + extends XDRGenericComm { - private Socket rawSocket; - protected boolean closed; + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger("lisa.comm.XDRTcpSocket"); + protected boolean closed; + private Socket rawSocket; /* public static final int SERVER_MODE = 0; - public static final int CLIENT_MODE = 1; + public static final int CLIENT_MODE = 1; private int mode = CLIENT_MODE; */ - - - public XDRTcpSocket(Socket s) throws IOException { - super("XDRTcpSocket for [ " + s.getInetAddress() + ":" + s.getPort() + " ] ", new XDROutputStream(s.getOutputStream()), new XDRInputStream(s.getInputStream())); - this.rawSocket = s; - closed = false; + + public XDRTcpSocket(Socket s) throws IOException { + super("XDRTcpSocket for [ " + s.getInetAddress() + ":" + s.getPort() + " ] ", new XDROutputStream(s.getOutputStream()), new XDRInputStream(s.getInputStream())); + this.rawSocket = s; + closed = false; /*this.mode = mode; this.auth = auth;*/ - } + } /*@Override protected void initSession() throws Exception { @@ -48,36 +47,36 @@ protected void initSession() throws Exception { else auth.initSession(rawSocket); }*/ - - public int getPort() { - return rawSocket.getPort(); - } - public int getLocalPort() { - return rawSocket.getLocalPort(); - } + public int getPort() { + return rawSocket.getPort(); + } - public InetAddress getInetAddress() { - return rawSocket.getInetAddress(); - } + public int getLocalPort() { + return rawSocket.getLocalPort(); + } - public InetAddress getLocalAddress() { - return rawSocket.getLocalAddress(); - } + public InetAddress getInetAddress() { + return rawSocket.getInetAddress(); + } - public void close() { - try { - super.close(); - if (!closed) {// allow multiple invocation for close() - closed = true; - if (rawSocket != null) { - rawSocket.close(); - } - } - } catch (Throwable t) { - t.printStackTrace(); - } - } + public InetAddress getLocalAddress() { + return rawSocket.getLocalAddress(); + } + + public void close() { + try { + super.close(); + if (!closed) {// allow multiple invocation for close() + closed = true; + if (rawSocket != null) { + rawSocket.close(); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } } diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index 2db673a..4900ee1 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -5,7 +5,6 @@ import lia.gsi.GSIServer; import lia.gsi.net.GSIGssSocketFactory; -import lia.util.net.copy.transport.FDTListFilesMsg; import lia.util.net.common.*; import javax.security.auth.Subject; @@ -28,79 +27,30 @@ */ public class ControlChannel extends AbstractFDTCloseable implements Runnable { + public static final int CONNECT_TIMEOUT = 20 * 1000; + public static final int SOCKET_TIMEOUT = 60 * 1000; + public static final int MAX_RETRIES = 3; private static final Logger logger = Logger.getLogger(ControlChannel.class.getName()); - private static final CtrlMsg versionMsg = new CtrlMsg(CtrlMsg.PROTOCOL_VERSION, Config.FDT_FULL_VERSION + "-" + Config.FDT_RELEASE_DATE); - private static final Config config = Config.getInstance(); - - public static final int CONNECT_TIMEOUT = 20 * 1000; - - public static final int SOCKET_TIMEOUT = 60 * 1000; - - public static final int MAX_RETRIES = 3; - + public final InetAddress remoteAddress; + public final int remotePort; + public final int localPort; private final Socket controlSocket; - private final ConcurrentLinkedQueue qToSend = new ConcurrentLinkedQueue(); - private final AtomicBoolean cleanupFinished = new AtomicBoolean(false); - + private final ControlChannelNotifier notifier; + public Map remoteConf; + public volatile Subject subject; private UUID fdtSessionID; - private volatile ObjectOutputStream oos = null; - private volatile ObjectInputStream ois = null; - - private final ControlChannelNotifier notifier; - private volatile String fullRemoteVersion; - - public Map remoteConf; - - public final InetAddress remoteAddress; - - public final int remotePort; - - public final int localPort; - - public volatile Subject subject; - private volatile String myName; private volatile ScheduledFuture ccptFuture; - private static final class ControlChannelPingerTask implements Runnable { - - public static final CtrlMsg pingMsg = new CtrlMsg(CtrlMsg.KEEP_ALIVE_MSG, new byte[1]); - - private final ControlChannel cc; - - ControlChannelPingerTask(ControlChannel cc) { - this.cc = cc; - logger.log(Level.INFO, "[ ControlChannelPingerTask ] initialized"); - } - - @Override - public void run() { - - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ ControlChannelPingerTask ] sending KEEP_ALIVE_MSG"); - } - - try { - this.cc.sendCtrlMessage(pingMsg); - } catch (Throwable t) { - logger.log( - Level.WARNING, - " [ ContrlChannelPingerTask ] Unable to send msg ... Close the socket ??? This should not happen", - t); - } - - } - } - /** * Try to connect to a remote FDT instance * @@ -160,10 +110,6 @@ public ControlChannel(InetAddress inetAddress, int port, UUID fdtSessionID, Cont } } - public boolean isSocketClosed() { - return (this.controlSocket == null) ? true : controlSocket.isClosed(); - } - /** * A remote peer connected to FDT * @@ -213,6 +159,10 @@ public ControlChannel(GSIServer parent, Socket s, Subject peerSubject, ControlCh } } + public boolean isSocketClosed() { + return (this.controlSocket == null) ? true : controlSocket.isClosed(); + } + public UUID fdtSessionID() { return fdtSessionID; } @@ -675,4 +625,34 @@ public void run() { } } } + + private static final class ControlChannelPingerTask implements Runnable { + + public static final CtrlMsg pingMsg = new CtrlMsg(CtrlMsg.KEEP_ALIVE_MSG, new byte[1]); + + private final ControlChannel cc; + + ControlChannelPingerTask(ControlChannel cc) { + this.cc = cc; + logger.log(Level.INFO, "[ ControlChannelPingerTask ] initialized"); + } + + @Override + public void run() { + + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, "[ ControlChannelPingerTask ] sending KEEP_ALIVE_MSG"); + } + + try { + this.cc.sendCtrlMessage(pingMsg); + } catch (Throwable t) { + logger.log( + Level.WARNING, + " [ ContrlChannelPingerTask ] Unable to send msg ... Close the socket ??? This should not happen", + t); + } + + } + } } diff --git a/src/lia/util/net/copy/transport/ControlChannelNotifier.java b/src/lia/util/net/copy/transport/ControlChannelNotifier.java index 5b780b0..8350105 100644 --- a/src/lia/util/net/copy/transport/ControlChannelNotifier.java +++ b/src/lia/util/net/copy/transport/ControlChannelNotifier.java @@ -6,16 +6,15 @@ import lia.util.net.common.FDTCloseable; /** - * * Basic interface for all the classes interested in receiving the control * messages between the FDT peers - * - * @author ramiro * + * @author ramiro */ public interface ControlChannelNotifier extends FDTCloseable { - + public void notifyCtrlMsg(ControlChannel controlChannel, Object ctrlMessage) throws FDTProcolException; + public void notifyCtrlSessionDown(ControlChannel controlChannel, Throwable cause); - + } diff --git a/src/lia/util/net/copy/transport/CtrlMsg.java b/src/lia/util/net/copy/transport/CtrlMsg.java index 4c297cb..2dc94ab 100644 --- a/src/lia/util/net/copy/transport/CtrlMsg.java +++ b/src/lia/util/net/copy/transport/CtrlMsg.java @@ -7,105 +7,116 @@ /** * This class will be the only message that will be sent over the control channel - * it will encapsulate message between two endpoint FDTSession-s - * + * it will encapsulate message between two endpoint FDTSession-s + * * @author ramiro */ public class CtrlMsg implements Serializable { - - private static final long serialVersionUID = 6815237091777780075L; - - /** a ping like message used to test the connection - at the connection Level*/ - public static final int KEEP_ALIVE_MSG = 0; - - /** message will be a string in the followinf format "major.minor.maintenance-releaseDate" */ - public static final int PROTOCOL_VERSION = 1; - - /** message will be the UUID ( aka the sessionID which will be the same at both ends )*/ - public static final int SESSION_ID = 2; - /** message will be an Short */ - public static final int SESSION_TYPE = 3; - - /** message will be a FDTInitMsg */ - public static final int INIT_FDT_CONF = 4; - + /** + * a ping like message used to test the connection - at the connection Level + */ + public static final int KEEP_ALIVE_MSG = 0; + /** + * message will be a string in the followinf format "major.minor.maintenance-releaseDate" + */ + public static final int PROTOCOL_VERSION = 1; + /** + * message will be the UUID ( aka the sessionID which will be the same at both ends ) + */ + public static final int SESSION_ID = 2; + /** + * message will be an Short + */ + public static final int SESSION_TYPE = 3; + /** + * message will be a FDTInitMsg + */ + public static final int INIT_FDT_CONF = 4; + /** + * ping-like message - this is at the session LEVEL + */ + public static final int PING_SESSION = 5; + //FDTSession messages - - /** ping-like message - this is at the session LEVEL*/ - public static final int PING_SESSION = 5; - - //From here on are ctrl messages used by the managers - - /** message will be a SessionConfig message */ - public static final int INIT_FDTSESSION_CONF = 6; - - /** message will be a SessionConfig message */ - public static final int FINAL_FDTSESSION_CONF = 7; - - /** message will be a SessionConfig message */ - public static final int FINISHED_FILE_SESSIONS = 8; - - /** Notified whenever the other END is able to start */ - public static final int START_SESSION = 9; - - /** message can be null or a String representing the cause*/ - public static final int END_SESSION = 10; - - /** message types designated to GUI */ - public static final int GUI_MSG = 11; + /** + * message will be a SessionConfig message + */ + public static final int INIT_FDTSESSION_CONF = 6; + //From here on are ctrl messages used by the managers + /** + * message will be a SessionConfig message + */ + public static final int FINAL_FDTSESSION_CONF = 7; + /** + * message will be a SessionConfig message + */ + public static final int FINISHED_FILE_SESSIONS = 8; + /** + * Notified whenever the other END is able to start + */ + public static final int START_SESSION = 9; + /** + * message can be null or a String representing the cause + */ + public static final int END_SESSION = 10; + /** + * message types designated to GUI + */ + public static final int GUI_MSG = 11; /** - * * sent, eventually, by the FDTWriter session to notify ONLY * the ControlChannel that it may close the socket... - * **/ - public static final int END_SESSION_FIN2 = 12; - - /** message types designated for third party copy feature */ - public static final int THIRD_PARTY_COPY = 13; - - /** message types designated for list files feature */ - public static final int LIST_FILES = 14; + public static final int END_SESSION_FIN2 = 12; + /** + * message types designated for third party copy feature + */ + public static final int THIRD_PARTY_COPY = 13; + /** + * message types designated for list files feature + */ + public static final int LIST_FILES = 14; + /** + * message types designated for remote transfer port feature + */ + public static final int REMOTE_TRANSFER_PORT = 15; + private static final long serialVersionUID = 6815237091777780075L; + private static final String[] CTRL_MSG_TAGS = new String[]{ + "KEEP_ALIVE_MSG", "PROTOCOL_VERSION", "SESSION_ID", "SESSION_TYPE", "INIT_FDT_CONF", "PING_SESSION", + "INIT_FDTSESSION_CONF", "FINAL_FDTSESSION_CONF", "FINISHED_FILE_SESSIONS", "START_SESSION", "END_SESSION", + "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES", "REMOTE_TRANSFER_PORT" + }; - /** message types designated for remote transfer port feature */ - public static final int REMOTE_TRANSFER_PORT = 15; - - private static final String[] CTRL_MSG_TAGS = new String[] { - "KEEP_ALIVE_MSG", "PROTOCOL_VERSION", "SESSION_ID", "SESSION_TYPE", "INIT_FDT_CONF", "PING_SESSION", - "INIT_FDTSESSION_CONF", "FINAL_FDTSESSION_CONF", "FINISHED_FILE_SESSIONS", "START_SESSION", "END_SESSION", - "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES", "REMOTE_TRANSFER_PORT" - }; - /** * the tag of the REQ/RESPONSE; based on this message instanceof can be avoided, * though I do not think is such a big performance gain */ public final int tag; - - + + /** - * the message, the one and only + * the message, the one and only */ public final Object message; - + public CtrlMsg(int tag, Object message) { this.tag = tag; this.message = message; } - + public String toString() { StringBuilder sb = new StringBuilder(); - + sb.append("tag ( ").append(tag).append(" ): "); - if(tag < 0 || tag >= CTRL_MSG_TAGS.length) { + if (tag < 0 || tag >= CTRL_MSG_TAGS.length) { sb.append("UNKNOWN_TAG"); } else { sb.append(CTRL_MSG_TAGS[tag]); } sb.append(" msg: ").append(message); - + return sb.toString(); } } diff --git a/src/lia/util/net/copy/transport/FDTKeyAttachement.java b/src/lia/util/net/copy/transport/FDTKeyAttachement.java index 65a9b13..aacd8b5 100644 --- a/src/lia/util/net/copy/transport/FDTKeyAttachement.java +++ b/src/lia/util/net/copy/transport/FDTKeyAttachement.java @@ -3,18 +3,18 @@ */ package lia.util.net.copy.transport; -import java.nio.ByteBuffer; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - import lia.util.net.common.DirectByteBufferPool; import lia.util.net.common.HeaderBufferPool; import lia.util.net.copy.transport.internal.FDTSelectionKey; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + /** * Abstract class implemented by both the reader and writer keys TODO - Use finer grained synchronization mechanism * instead of syncronized on the entire key - * + * * @author ramiro */ public abstract class FDTKeyAttachement { @@ -30,18 +30,12 @@ public abstract class FDTKeyAttachement { * - timeStamp 64 bits ( 8 bytes ) long - seq 64 bits ( 8 bytes ) int - packet type 32 bits ( 4 bytes ) UUID - 2 * long-s 128 bits ( 16 bytes ) long - offset in file 64 bits ( 8 bytes ) */ - - private ByteBuffer header; - - private ByteBuffer payload; - - private final ByteBuffer[] _array = new ByteBuffer[2]; - protected final int seq; - + private final ByteBuffer[] _array = new ByteBuffer[2]; public FDTSelectionKey fdtSelectionKey; - protected boolean useFixedSizeBlocks; + private ByteBuffer header; + private ByteBuffer payload; public FDTKeyAttachement(FDTSelectionKey fdtSelectionKey, boolean useFixedSizeBlocks) { this.header = null; @@ -53,7 +47,7 @@ public FDTKeyAttachement(FDTSelectionKey fdtSelectionKey, boolean useFixedSizeBl /** * This MUST stay synchronized - * + * * @param header * @param payload */ @@ -95,12 +89,12 @@ public synchronized void recycleAndSetPayload(ByteBuffer bb) { this.payload = bb; setArray(); } - + public synchronized void setPayload(ByteBuffer bb) { this.payload = bb; setArray(); } - + public synchronized void recycleHeader() { if (this.header != null) { headerPool.put(this.header); @@ -143,7 +137,7 @@ public synchronized boolean recycleAndSetBuffers() throws InterruptedException { return false; } } - + setBuffers(_header, _payload); return true; @@ -172,11 +166,11 @@ public final boolean useFixedSizeBlocks() { public synchronized final ByteBuffer header() { return header; } - + public synchronized final ByteBuffer payload() { return payload; } - + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("SocketAttachement :- header: ").append(header).append(" :- payload: ").append(payload); diff --git a/src/lia/util/net/copy/transport/FDTListFilesMsg.java b/src/lia/util/net/copy/transport/FDTListFilesMsg.java index 9606f63..cf38ff8 100644 --- a/src/lia/util/net/copy/transport/FDTListFilesMsg.java +++ b/src/lia/util/net/copy/transport/FDTListFilesMsg.java @@ -9,11 +9,12 @@ /** * The list files msg between FDT peers. + * * @author Raimondas Sirvinskas */ public class FDTListFilesMsg implements Serializable { - public String listFilesFrom; + public String listFilesFrom; public List filesInDir; public FDTListFilesMsg(String listFilesFrom) { diff --git a/src/lia/util/net/copy/transport/FDTProcolException.java b/src/lia/util/net/copy/transport/FDTProcolException.java index 07104c2..c7b54d3 100644 --- a/src/lia/util/net/copy/transport/FDTProcolException.java +++ b/src/lia/util/net/copy/transport/FDTProcolException.java @@ -4,26 +4,24 @@ package lia.util.net.copy.transport; /** - * * Exception used to signal protcol exception at the FDT Application level * Usually this kind of Exception happen if there is a BUG inside FDT protocol * or someone is trying to hijack the established FDT Session - * - * @author ramiro * + * @author ramiro */ public class FDTProcolException extends Exception { - + private static final long serialVersionUID = 4606073777542177510L; - + public FDTProcolException() { super(); } - + public FDTProcolException(String message) { super(message); } - + public FDTProcolException(String message, Throwable cause) { super(message, cause); } diff --git a/src/lia/util/net/copy/transport/FDTReaderKeyAttachement.java b/src/lia/util/net/copy/transport/FDTReaderKeyAttachement.java index 91462a4..19db03f 100644 --- a/src/lia/util/net/copy/transport/FDTReaderKeyAttachement.java +++ b/src/lia/util/net/copy/transport/FDTReaderKeyAttachement.java @@ -4,16 +4,16 @@ package lia.util.net.copy.transport; -import java.nio.ByteBuffer; -import java.util.UUID; - import lia.util.net.copy.FileBlock; import lia.util.net.copy.transport.internal.FDTSelectionKey; +import java.nio.ByteBuffer; +import java.util.UUID; + /** * The key selection used by the "read" sockets TODO - Use finer grained synchronization mechanism instead of * syncronized on the entire key - * + * * @author ramiro */ class FDTReaderKeyAttachement extends FDTKeyAttachement { diff --git a/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java b/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java index fad07ec..d26ee24 100644 --- a/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java +++ b/src/lia/util/net/copy/transport/FDTSessionConfigMsg.java @@ -10,30 +10,31 @@ import java.util.UUID; /** - * The config msg between FDT peers. + * The config msg between FDT peers. + * * @author ramiro */ public class FDTSessionConfigMsg implements Serializable { private static final long serialVersionUID = 691756564099111644L; - - - public String destinationDir; - public String destinationIP; - public int destinationPort; - public boolean recursive; - + + + public String destinationDir; + public String destinationIP; + public int destinationPort; + public boolean recursive; + //future? use public String dirOffset; public String sourceIP; - - public UUID[] fileIDs; + + public UUID[] fileIDs; public String[] fileLists; public String[] remappedFileLists; - public long[] fileSizes; - public long[] lastModifTimes; - + public long[] fileSizes; + public long[] lastModifTimes; + public FDTSessionConfigMsg() { } diff --git a/src/lia/util/net/copy/transport/FDTWriterKeyAttachement.java b/src/lia/util/net/copy/transport/FDTWriterKeyAttachement.java index 43bc63c..1c2befb 100644 --- a/src/lia/util/net/copy/transport/FDTWriterKeyAttachement.java +++ b/src/lia/util/net/copy/transport/FDTWriterKeyAttachement.java @@ -3,49 +3,45 @@ */ package lia.util.net.copy.transport; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - import lia.util.net.common.Config; import lia.util.net.common.HeaderBufferPool; import lia.util.net.common.Utils; import lia.util.net.copy.FileBlock; import lia.util.net.copy.transport.internal.FDTSelectionKey; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + /** - * - * Specific FDT attachement for a channel performing write. - * @author ramiro + * Specific FDT attachement for a channel performing write. * + * @author ramiro */ class FDTWriterKeyAttachement extends FDTKeyAttachement implements Comparable { - private static AtomicLong SEQ = new AtomicLong(0L); private static final HeaderBufferPool hbp = Utils.getHeaderBufferPool(); - + private static AtomicLong SEQ = new AtomicLong(0L); final AtomicLong lastOperation = new AtomicLong(0L); - + final AtomicBoolean connectCookieSent; volatile int payloadSize; - FileBlock fileBlock; - final AtomicBoolean connectCookieSent; - + public FDTWriterKeyAttachement(FDTSelectionKey fdtSelectionKey, boolean useFixedSizeBlocks, boolean connectCookieSent) { super(fdtSelectionKey, useFixedSizeBlocks); this.connectCookieSent = new AtomicBoolean(connectCookieSent); } public static final boolean fromFileBlock(FileBlock fileBlock, FDTWriterKeyAttachement wsa) throws InterruptedException { - if(fileBlock == null) return false; - + if (fileBlock == null) return false; + wsa.recycleBuffers(); - + ByteBuffer header = hbp.take(); - - if(header == null) return false; - + + if (header == null) return false; + long seq = SEQ.getAndIncrement(); //the version @@ -54,40 +50,40 @@ public static final boolean fromFileBlock(FileBlock fileBlock, FDTWriterKeyAttac //the packet type header.putInt(1); - + //Header Size header.putInt(Config.HEADER_SIZE); - + //payload size() header.putInt(fileBlock.buff.limit()); - + //the tstamp - future use for MUX streams? header.putLong(0L); - + //packet SEQ - future use for MUX streams header.putLong(seq); - - + + //the UUID header.putLong(fileBlock.fileSessionID.getMostSignificantBits()).putLong(fileBlock.fileSessionID.getLeastSignificantBits()); - + //the offset header.putLong(fileBlock.fileOffset); - + //make it ready :) header.flip(); - + //if used fixed size ... than increase the limit to the buffer capacity - if( wsa.useFixedSizeBlocks ) { + if (wsa.useFixedSizeBlocks) { fileBlock.buff.limit(fileBlock.buff.capacity()); } wsa.setBuffers(header, fileBlock.buff); wsa.payloadSize = fileBlock.buff.limit(); - + //keep the reference to the FileBlock to recycle it wsa.setBuffers(header, fileBlock.buff); - + return true; } @@ -102,19 +98,19 @@ public synchronized boolean isPayloadWritten() { public final void updateLastOperation() { this.lastOperation.set(System.nanoTime()); } - + public int compareTo(FDTWriterKeyAttachement o) { - - if(this == o) return 0; - + + if (this == o) return 0; + final long diff = this.lastOperation.get() - o.lastOperation.get(); - - if(diff < 0L) return -1; - if(diff > 0L) return 1; - + + if (diff < 0L) return -1; + if (diff > 0L) return 1; + //should return 0; check the seq anyway - if(this.seq < o.seq) return -1; - + if (this.seq < o.seq) return -1; + return 1; } diff --git a/src/lia/util/net/copy/transport/FDTWriterKeyAttachementComparator.java b/src/lia/util/net/copy/transport/FDTWriterKeyAttachementComparator.java index 14258ee..5bba5e1 100644 --- a/src/lia/util/net/copy/transport/FDTWriterKeyAttachementComparator.java +++ b/src/lia/util/net/copy/transport/FDTWriterKeyAttachementComparator.java @@ -3,30 +3,29 @@ */ package lia.util.net.copy.transport; +import lia.util.net.copy.transport.internal.FDTSelectionKey; + import java.io.Serializable; import java.util.Comparator; -import lia.util.net.copy.transport.internal.FDTSelectionKey; - /** - * * @author ramiro */ public class FDTWriterKeyAttachementComparator implements Comparator, Serializable { - + /** * findbugs suggested i should make the comparator .... Serializable */ private static final long serialVersionUID = -9190255291921632210L; public int compare(final FDTSelectionKey sk1, final FDTSelectionKey sk2) { - - if(sk1 == sk2) return 0; - - final FDTWriterKeyAttachement sk1Attach = (FDTWriterKeyAttachement)sk1.attachment(); - final FDTWriterKeyAttachement sk2Attach = (FDTWriterKeyAttachement)sk2.attachment(); - + + if (sk1 == sk2) return 0; + + final FDTWriterKeyAttachement sk1Attach = (FDTWriterKeyAttachement) sk1.attachment(); + final FDTWriterKeyAttachement sk2Attach = (FDTWriterKeyAttachement) sk2.attachment(); + return sk1Attach.compareTo(sk2Attach); } - + } \ No newline at end of file diff --git a/src/lia/util/net/copy/transport/PingDaemon.java b/src/lia/util/net/copy/transport/PingDaemon.java index 97a0847..de84db0 100644 --- a/src/lia/util/net/copy/transport/PingDaemon.java +++ b/src/lia/util/net/copy/transport/PingDaemon.java @@ -3,6 +3,8 @@ */ package lia.util.net.copy.transport; +import lia.util.net.common.Utils; + import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -12,217 +14,210 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Utils; - /** - * * Should provide RTT between peers - * + * * @author ramiro - * */ public class PingDaemon { private static final Logger logger = Logger.getLogger("lia.util.net.copy.transport.PingDaemon"); - + private static final int DEFAULT_CONNECT_DELAY = 20; + private static final int DEFAULT_PING_DELAY = 20; + private static final TimeUnit DEFAULT_PING_DELAY_TIME_UNIT = TimeUnit.SECONDS; + private static final int DEFAULT_PACKET_SIZE = 1500;//bytes + private static final int FDT_PING = 1; + private static final int FDT_PING_REPLY = 2; private long pingDelay; private TimeUnit pingDelayTimeUnit; private SocketChannel sc; private int packetSize; - - private static final int DEFAULT_CONNECT_DELAY = 20; - private static final int DEFAULT_PING_DELAY = 20; - private static final TimeUnit DEFAULT_PING_DELAY_TIME_UNIT = TimeUnit.SECONDS; - private static final int DEFAULT_PACKET_SIZE = 1500;//bytes - private ByteBuffer header; private ByteBuffer payload; - - private static final int FDT_PING = 1; - private static final int FDT_PING_REPLY = 2; - - private long SEQ = 0; - private ByteBuffer[] toSend = new ByteBuffer[2]; - + private long SEQ = 0; + private ByteBuffer[] toSend = new ByteBuffer[2]; + + public PingDaemon(SocketChannel sc) throws Exception { + + sc.configureBlocking(true); + this.sc = sc; + initChannels(); + + new PingServer().start(); + } + + public PingDaemon(InetAddress inetAddr, int port) throws Exception { + this(inetAddr, port, DEFAULT_CONNECT_DELAY, DEFAULT_PING_DELAY, DEFAULT_PING_DELAY_TIME_UNIT, DEFAULT_PACKET_SIZE); + } + + public PingDaemon(InetAddress inetAddr, int port, long pingDelay, + TimeUnit pingDelayTimeUnit, int packetSize) throws Exception { + this(inetAddr, port, DEFAULT_CONNECT_DELAY, pingDelay, pingDelayTimeUnit, packetSize); + } + + public PingDaemon(InetAddress inetAddr, int port, int connectDelay, long pingDelay, + TimeUnit pingDelayTimeUnit, int packetSize) throws Exception { + + this.packetSize = packetSize; + this.pingDelay = pingDelay; + this.pingDelayTimeUnit = pingDelayTimeUnit; + + sc = SocketChannel.open(); + sc.configureBlocking(true); + + sc.socket().connect(new InetSocketAddress(inetAddr, port), 30 * 1000); + + initChannels(); + + payload = ByteBuffer.allocateDirect(DEFAULT_PACKET_SIZE - header.capacity()); + + header.putInt(FDT_PING); + header.putInt(1); + header.putInt(payload.capacity()); + + toSend[0] = header; + toSend[1] = payload; + + Utils.getMonitoringExecService().scheduleWithFixedDelay(new PingerTask(), 5, 20, TimeUnit.SECONDS); + + } + + public static final void main(String[] args) throws Exception { + + try { + //Start in server mode + if (args == null || args.length == 0) { + } else if (args.length == 1) { + final int port = Integer.parseInt(args[0]); + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.configureBlocking(true); + ssc.socket().bind(new InetSocketAddress(port)); + + try { + SocketChannel sc = ssc.accept(); + new PingDaemon(sc); + } catch (Throwable t) { + t.printStackTrace(); + } + } else if (args.length == 2) { + InetAddress ia = InetAddress.getByName(args[0]); + final int port = Integer.parseInt(args[1]); + new PingDaemon(ia, port); + } + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + + + for (; ; ) { + try { + Thread.sleep(100000); + } catch (Throwable t1) { + } + } + } + + private void initChannels() throws Exception { + + header = ByteBuffer.allocateDirect(20); + + } + + public void setDelay(long pingDelay, TimeUnit pingDelayTimeUnit) { + this.pingDelay = pingDelay; + this.pingDelayTimeUnit = pingDelayTimeUnit; + } + + public void setPacketSize(int packetSize) { + this.packetSize = packetSize; + } + private final class PingServer extends Thread { - + public PingServer() { super(" (FDT) Ping Daemon"); this.setDaemon(true); } - + public void run() { - - for(;;) { + + for (; ; ) { try { - - if(!sc.isOpen() || sc.socket().isClosed()) { + + if (!sc.isOpen() || sc.socket().isClosed()) { break; } - + header.clear(); - if(sc.read(header) == -1) { + if (sc.read(header) == -1) { break; } - + header.flip(); - + final int type = header.getInt(); final int version = header.getInt(); final int payloadSize = header.getInt(); final long seq = header.getLong(); - + System.out.println("Read from client: [ SEQ: " + seq + " type: " + type + " version: " + version + " payloadSize: " + payloadSize + " ]"); - - if(payload == null || payload.capacity() != payloadSize) { + + if (payload == null || payload.capacity() != payloadSize) { payload = ByteBuffer.allocateDirect(payloadSize); toSend[0] = header; toSend[1] = payload; } - + payload.clear(); sc.read(payload); - + header.flip(); header.putInt(FDT_PING_REPLY); - + header.position(header.capacity()); payload.position(payload.capacity()); - + header.flip(); payload.flip(); - + sc.write(toSend); - - }catch(Throwable t){ + + } catch (Throwable t) { logger.log(Level.WARNING, "Got exception ", t); } }//end for - + System.out.println("Server exits!"); } } - + private final class PingerTask implements Runnable { - + public void run() { try { - + header.position(12); header.putLong(SEQ++); header.position(header.capacity()); - + payload.position(payload.capacity()); header.flip(); payload.flip(); - + final long sTime = System.nanoTime(); System.out.println(" Written: " + sc.write(toSend)); - + header.clear(); payload.clear(); - + sc.read(toSend); final long finishTime = System.nanoTime(); - - System.out.println(" DT = " + (finishTime - sTime)/(1000D*1000D) + " ms"); - }catch(Throwable t) { - logger.log(Level.WARNING, "Got exception", t); - } - } - } - public PingDaemon(SocketChannel sc) throws Exception { - - sc.configureBlocking(true); - this.sc = sc; - initChannels(); - - new PingServer().start(); - } - - public PingDaemon(InetAddress inetAddr, int port) throws Exception { - this(inetAddr, port, DEFAULT_CONNECT_DELAY, DEFAULT_PING_DELAY, DEFAULT_PING_DELAY_TIME_UNIT, DEFAULT_PACKET_SIZE); - } - - public PingDaemon(InetAddress inetAddr, int port, long pingDelay, - TimeUnit pingDelayTimeUnit, int packetSize) throws Exception { - this(inetAddr, port, DEFAULT_CONNECT_DELAY, pingDelay, pingDelayTimeUnit, packetSize); - } - - public PingDaemon(InetAddress inetAddr, int port, int connectDelay, long pingDelay, - TimeUnit pingDelayTimeUnit, int packetSize) throws Exception { - - this.packetSize = packetSize; - this.pingDelay = pingDelay; - this.pingDelayTimeUnit = pingDelayTimeUnit; - - sc = SocketChannel.open(); - sc.configureBlocking(true); - - sc.socket().connect(new InetSocketAddress(inetAddr, port), 30 * 1000); - - initChannels(); - - payload = ByteBuffer.allocateDirect(DEFAULT_PACKET_SIZE - header.capacity()); - - header.putInt(FDT_PING); - header.putInt(1); - header.putInt(payload.capacity()); - - toSend[0] = header; - toSend[1] = payload; - Utils.getMonitoringExecService().scheduleWithFixedDelay(new PingerTask(), 5, 20, TimeUnit.SECONDS); - - } - - private void initChannels() throws Exception { - - header = ByteBuffer.allocateDirect(20); - - } - - public void setDelay(long pingDelay, TimeUnit pingDelayTimeUnit) { - this.pingDelay = pingDelay; - this.pingDelayTimeUnit = pingDelayTimeUnit; - } - - public void setPacketSize(int packetSize) { - this.packetSize = packetSize; - } - - public static final void main(String[] args) throws Exception { - - try { - //Start in server mode - if(args == null || args.length == 0) { - } else if(args.length == 1) { - final int port = Integer.parseInt(args[0]); - ServerSocketChannel ssc = ServerSocketChannel.open(); - ssc.configureBlocking(true); - ssc.socket().bind(new InetSocketAddress(port)); - - try { - SocketChannel sc = ssc.accept(); - new PingDaemon(sc); - }catch(Throwable t) { - t.printStackTrace(); - } - } else if(args.length == 2) { - InetAddress ia = InetAddress.getByName(args[0]); - final int port = Integer.parseInt(args[1]); - new PingDaemon(ia, port); + System.out.println(" DT = " + (finishTime - sTime) / (1000D * 1000D) + " ms"); + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception", t); } - }catch(Throwable t) { - t.printStackTrace(); - System.exit(1); - } - - - for(;;) { - try { - Thread.sleep(100000); - }catch(Throwable t1){} } } } diff --git a/src/lia/util/net/copy/transport/SocketReaderTask.java b/src/lia/util/net/copy/transport/SocketReaderTask.java index 94510ae..da2d322 100644 --- a/src/lia/util/net/copy/transport/SocketReaderTask.java +++ b/src/lia/util/net/copy/transport/SocketReaderTask.java @@ -4,6 +4,13 @@ package lia.util.net.copy.transport; +import lia.util.net.common.Config; +import lia.util.net.common.DirectByteBufferPool; +import lia.util.net.common.Utils; +import lia.util.net.copy.FileBlock; +import lia.util.net.copy.FileBlockConsumer; +import lia.util.net.copy.transport.internal.FDTSelectionKey; + import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.concurrent.BlockingQueue; @@ -12,16 +19,9 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.Utils; -import lia.util.net.copy.FileBlock; -import lia.util.net.copy.FileBlockConsumer; -import lia.util.net.copy.transport.internal.FDTSelectionKey; - /** * The one and (not the) only reader task for a channel - * + * * @author ramiro */ public class SocketReaderTask extends SocketTask { @@ -72,7 +72,7 @@ private final boolean checkForData() throws InterruptedException { boolean offered = false; try { - for (;;) { + for (; ; ) { offered = fileBlockConsumer.offer(fileBlock, 5, TimeUnit.SECONDS); if (offered) break; @@ -128,7 +128,7 @@ private boolean readData() throws Exception { } final ByteBuffer bpl = attach.payload(); - for (;;) { + for (; ; ) { if (isClosed() || Thread.currentThread().isInterrupted()) break; @@ -273,7 +273,7 @@ public void run() { } try { - for (;;) { + for (; ; ) { // use a local FDTSelKey for a little speed-up FDTSelectionKey iSel = null; fdtSelectionKeyRef.set(null); @@ -302,7 +302,7 @@ public void run() { try { if (!readData()) { if (!isClosed()) { - if(readyChannelsQueue.offer(fdtSelectionKeyRef.getAndSet(null))) { + if (readyChannelsQueue.offer(fdtSelectionKeyRef.getAndSet(null))) { throw new FDTProcolException(" Unable to add selection key in the selection queue"); } } else { diff --git a/src/lia/util/net/copy/transport/SocketTask.java b/src/lia/util/net/copy/transport/SocketTask.java index 8a32678..97b3da9 100644 --- a/src/lia/util/net/copy/transport/SocketTask.java +++ b/src/lia/util/net/copy/transport/SocketTask.java @@ -3,41 +3,39 @@ */ package lia.util.net.copy.transport; -import java.util.concurrent.BlockingQueue; - import lia.util.net.common.AbstractFDTIOEntity; import lia.util.net.common.Config; import lia.util.net.copy.transport.internal.FDTSelectionKey; +import java.util.concurrent.BlockingQueue; + /** - * * The base class for to fill/drain a socket channel. - * + * * @author ramiro - * */ public abstract class SocketTask extends AbstractFDTIOEntity implements Runnable { - protected static final boolean isBlocking = Config.getInstance().isBlocking(); + protected static final boolean isBlocking = Config.getInstance().isBlocking(); protected final BlockingQueue readyChannelsQueue; - public long getSize() { - return -1; - } - public SocketTask(BlockingQueue readyChannelsQueue) { this.readyChannelsQueue = readyChannelsQueue; } - + + public long getSize() { + return -1; + } + protected void internalClose() throws Exception { try { - for(final FDTSelectionKey selKey: readyChannelsQueue) { + for (final FDTSelectionKey selKey : readyChannelsQueue) { final FDTKeyAttachement attach = selKey.attachment(); - if(attach != null) { + if (attach != null) { attach.recycleBuffers(); } } - }catch(Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } } diff --git a/src/lia/util/net/copy/transport/SocketWriterTask.java b/src/lia/util/net/copy/transport/SocketWriterTask.java index d779e1b..40df092 100644 --- a/src/lia/util/net/copy/transport/SocketWriterTask.java +++ b/src/lia/util/net/copy/transport/SocketWriterTask.java @@ -3,6 +3,14 @@ */ package lia.util.net.copy.transport; +import lia.util.net.common.Config; +import lia.util.net.common.DirectByteBufferPool; +import lia.util.net.common.HeaderBufferPool; +import lia.util.net.common.Utils; +import lia.util.net.copy.FileBlock; +import lia.util.net.copy.FileBlockProducer; +import lia.util.net.copy.transport.internal.FDTSelectionKey; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -12,33 +20,24 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.HeaderBufferPool; -import lia.util.net.common.Utils; -import lia.util.net.copy.FileBlock; -import lia.util.net.copy.FileBlockProducer; -import lia.util.net.copy.transport.internal.FDTSelectionKey; - /** * The one and (not the) only writer over a channel - * + * * @author ramiro */ public class SocketWriterTask extends SocketTask { - /** Logger used by this class */ + /** + * Logger used by this class + */ private static final Logger logger = Logger.getLogger(SocketWriterTask.class.getName()); - - private final AtomicReference fdtSelectionKeyRef = new AtomicReference(null); - // private static final int MSS_SIZE = 16 * 1024 * 1024; private static final int BUFF_LEN_SIZE = Config.NETWORK_BUFF_LEN_SIZE; + private final AtomicReference fdtSelectionKeyRef = new AtomicReference(null); // private static final int BUFF_LEN_SIZE = 16 * 1024 * 1024; // private static final int RETRY_IO_COUNT = Config.getInstance().getRetryIOCount(); - private final TCPSessionWriter master; private final FileBlockProducer fileBlockProducer; @@ -79,7 +78,7 @@ private long writeData() throws IOException, InterruptedException { bufferSize = mss; } - final boolean logFinest = logger.isLoggable(Level.FINEST); + final boolean logFinest = logger.isLoggable(Level.FINEST); if (logFinest) { logger.log(Level.FINEST, "Using MSS: " + bufferSize + " for socket channel: " + sc); } @@ -87,7 +86,7 @@ private long writeData() throws IOException, InterruptedException { final DirectByteBufferPool dbPool = DirectByteBufferPool.getInstance(); final HeaderBufferPool hbPool = HeaderBufferPool.getInstance(); - for (;;) { + for (; ; ) { count = -1; if (!attach.hasBuffers()) { if (isNetTest) { @@ -350,7 +349,7 @@ private long writeData() throws IOException, InterruptedException { private void recycleBuffers() { try { final FDTSelectionKey fdtSelectionKey = fdtSelectionKeyRef.getAndSet(null); - + if (fdtSelectionKey != null) { FDTWriterKeyAttachement attach = (FDTWriterKeyAttachement) fdtSelectionKey.attachment(); if (attach != null) { @@ -397,7 +396,7 @@ public void internalClose() { public void run() { try { - for (;;) { + for (; ; ) { fdtSelectionKeyRef.set(null); FDTSelectionKey iSel = null; while (iSel == null) { diff --git a/src/lia/util/net/copy/transport/SpeedLimitManager.java b/src/lia/util/net/copy/transport/SpeedLimitManager.java index 23aad6b..4adc469 100644 --- a/src/lia/util/net/copy/transport/SpeedLimitManager.java +++ b/src/lia/util/net/copy/transport/SpeedLimitManager.java @@ -4,16 +4,17 @@ */ package lia.util.net.copy.transport; +import lia.util.net.common.Utils; + import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Utils; /** * This class manages all the speed limits for FDTSession-s - * + * * @author ramiro */ public class SpeedLimitManager { @@ -24,6 +25,23 @@ public class SpeedLimitManager { private final ScheduledThreadPoolExecutor executor; + /** + * Creates a new instance of LimitManager + */ + private SpeedLimitManager() { + executor = Utils.getSchedExecService("SpeedLimitManager", 2, Thread.MIN_PRIORITY + 1); + } + + public static final SpeedLimitManager getInstance() { + return _thisInstance; + } + + public ScheduledFuture addLimiter(SpeedLimiter speedLimiter) throws Exception { + final long delay = speedLimiter.getNotifyDelay(); + logger.log(Level.INFO, " Adding SpeedLimiterTask for " + speedLimiter + " delay: " + delay + " ms"); + return executor.scheduleWithFixedDelay(new SpeedLimiterTask(speedLimiter), 0, delay, TimeUnit.MILLISECONDS); + } + private static final class SpeedLimiterTask implements Runnable { final SpeedLimiter speedLimiter; @@ -58,19 +76,4 @@ public void run() { } } } - - /** Creates a new instance of LimitManager */ - private SpeedLimitManager() { - executor = Utils.getSchedExecService("SpeedLimitManager", 2, Thread.MIN_PRIORITY + 1); - } - - public static final SpeedLimitManager getInstance() { - return _thisInstance; - } - - public ScheduledFuture addLimiter(SpeedLimiter speedLimiter) throws Exception { - final long delay = speedLimiter.getNotifyDelay(); - logger.log(Level.INFO, " Adding SpeedLimiterTask for " + speedLimiter + " delay: " + delay + " ms"); - return executor.scheduleWithFixedDelay(new SpeedLimiterTask(speedLimiter), 0, delay, TimeUnit.MILLISECONDS); - } } diff --git a/src/lia/util/net/copy/transport/SpeedLimiter.java b/src/lia/util/net/copy/transport/SpeedLimiter.java index fda03af..8baed43 100644 --- a/src/lia/util/net/copy/transport/SpeedLimiter.java +++ b/src/lia/util/net/copy/transport/SpeedLimiter.java @@ -7,19 +7,20 @@ /** * The interface which all SpeedLimiter-s shoud implement... + * * @author ramiro */ public interface SpeedLimiter { - + /** * returns the rate in Bytes/s */ public long getRateLimit(); - + /** - * * @return - how responsive/aggresive is the SpeedLimiter */ public long getNotifyDelay(); + public void notifyAvailableBytes(long availableBytes); } diff --git a/src/lia/util/net/copy/transport/TCPSessionReader.java b/src/lia/util/net/copy/transport/TCPSessionReader.java index 05dc3fb..3b915ec 100644 --- a/src/lia/util/net/copy/transport/TCPSessionReader.java +++ b/src/lia/util/net/copy/transport/TCPSessionReader.java @@ -3,12 +3,6 @@ */ package lia.util.net.copy.transport; -import java.net.InetAddress; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.util.logging.Level; -import java.util.logging.Logger; - import lia.util.net.common.Config; import lia.util.net.common.Utils; import lia.util.net.copy.FDTSession; @@ -16,14 +10,18 @@ import lia.util.net.copy.transport.internal.FDTSelectionKey; import lia.util.net.copy.transport.internal.SelectionManager; +import java.net.InetAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * - * This class provides the TransportProvider for a FDTSession which expects data from the wire. + * This class provides the TransportProvider for a FDTSession which expects data from the wire. * It is responsible to stop all the sockets and notify it's session if something goes wrong - * It also registers every worker in the SelectionManager with OP_READ interest - * + * It also registers every worker in the SelectionManager with OP_READ interest + * * @author ramiro - * */ public class TCPSessionReader extends TCPTransportProvider { @@ -31,66 +29,66 @@ public class TCPSessionReader extends TCPTransportProvider { private static final SelectionManager selectionManager = SelectionManager.getInstance(); private static final Config config = Config.getInstance(); private FileBlockConsumer fileBlockConsumer; - + public TCPSessionReader(FDTSession fdtSession, FileBlockConsumer fileBlockConsumer) throws Exception { super(fdtSession); this.fileBlockConsumer = fileBlockConsumer; } - - public TCPSessionReader(FDTSession fdtSession, - FileBlockConsumer fileBlockConsumer, - InetAddress endPointAddress, int port, - int numberOfStreams - ) throws Exception { - + + public TCPSessionReader(FDTSession fdtSession, + FileBlockConsumer fileBlockConsumer, + InetAddress endPointAddress, int port, + int numberOfStreams + ) throws Exception { + super(fdtSession, endPointAddress, port, numberOfStreams); this.fileBlockConsumer = fileBlockConsumer; } - - + public void addWorkerStream(SocketChannel sc, boolean sentCookie) throws Exception { - synchronized(this.closeLock) { + synchronized (this.closeLock) { super.addWorkerStream(sc, sentCookie); FDTSelectionKey fsk = null; - if(config.isBlocking()) { + if (config.isBlocking()) { fsk = new FDTSelectionKey(fdtSession.sessionID(), sc, SelectionKey.OP_READ, this, null); fsk.attach(new FDTReaderKeyAttachement(fsk, fdtSession.useFixedBlockSize())); selectionQueue.add(fsk); SocketTask socketTask = new SocketReaderTask(selectionQueue, fileBlockConsumer, this); - - if(addSocketTask(socketTask)) { + + if (addSocketTask(socketTask)) { executor.submit(socketTask); } else { close("Unable to add a new SocketTask. OOM?", null); } - + } else { fsk = selectionManager.register(fdtSession.sessionID(), sc, SelectionKey.OP_READ, this); fsk.attach(new FDTReaderKeyAttachement(fsk, fdtSession.useFixedBlockSize())); - if(!fsk.registerInterest()) { + if (!fsk.registerInterest()) { logger.log(Level.WARNING, " \n\n Smth went terrible wrong ?? \n\n fsk.registerInterest() returned false \n\n"); } } - + channels.put(sc, fsk); } } - + // //TODO - can we recover if downCause != null // - implement a timeout retry ? ... for the moment it just finishes the entire session // - this behavior should be changed when dynamic creation of workers will be added + /** - * @param fdtSelectionKey - * @param downCause + * @param fdtSelectionKey + * @param downCause */ public void workerDown(FDTSelectionKey fdtSelectionKey, Throwable downCause) { //smth gone wrong ... or maybe the session finished already //I do not know if it should take other action ... for the moment the session will go down - + // if(downCause != null) { // logger.log(Level.WARNING, " [ TCPSessionReader ] for fdtSession [ " + fdtSession + " ] got an error on a worker", downCause); // } @@ -100,11 +98,11 @@ public void workerDown(FDTSelectionKey fdtSelectionKey, Throwable downCause) { public void startTransport(boolean sendCookie) throws Exception { super.startTransport(sendCookie); - synchronized(this.closeLock) { - if(!config.isBlocking()) { - for(int i =0; i <= Utils.availableProcessors()*2; i++) { + synchronized (this.closeLock) { + if (!config.isBlocking()) { + for (int i = 0; i <= Utils.availableProcessors() * 2; i++) { SocketTask socketTask = new SocketReaderTask(selectionQueue, fileBlockConsumer, this); - if(addSocketTask(socketTask)) { + if (addSocketTask(socketTask)) { executor.submit(socketTask); } else { close("Unable to add a new SocketTask. OOM?", null); diff --git a/src/lia/util/net/copy/transport/TCPSessionWriter.java b/src/lia/util/net/copy/transport/TCPSessionWriter.java index b61bfc3..ba616eb 100644 --- a/src/lia/util/net/copy/transport/TCPSessionWriter.java +++ b/src/lia/util/net/copy/transport/TCPSessionWriter.java @@ -3,6 +3,12 @@ */ package lia.util.net.copy.transport; +import lia.util.net.common.Config; +import lia.util.net.common.Utils; +import lia.util.net.copy.FDTReaderSession; +import lia.util.net.copy.transport.internal.FDTSelectionKey; +import lia.util.net.copy.transport.internal.SelectionManager; + import java.net.InetAddress; import java.net.NetworkInterface; import java.nio.channels.SelectionKey; @@ -12,20 +18,14 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.Utils; -import lia.util.net.copy.FDTReaderSession; -import lia.util.net.copy.transport.internal.FDTSelectionKey; -import lia.util.net.copy.transport.internal.SelectionManager; - /** * The BIG "limiter" is here :) - * + * * @author ramiro */ public class TCPSessionWriter extends TCPTransportProvider { - + private static final Logger logger = Logger.getLogger("lia.util.net.copy.transport.TCPSessionWriter"); private static final SelectionManager selectionManager = SelectionManager.getInstance(); private static final Config config = Config.getInstance(); @@ -35,8 +35,8 @@ public TCPSessionWriter(FDTReaderSession fdtSession) throws Exception { } public TCPSessionWriter(FDTReaderSession fdtSession, - InetAddress endPointAddress, int port, - int numberOfStreams) throws Exception { + InetAddress endPointAddress, int port, + int numberOfStreams) throws Exception { super(fdtSession, endPointAddress, port, numberOfStreams, new PriorityBlockingQueue(10, new FDTWriterKeyAttachementComparator())); } @@ -45,29 +45,29 @@ public void notifyAvailableBytes(final long available) { try { availableBytes = available; isAvailable.signalAll(); - }finally{ + } finally { speedLimitLock.unlock(); } } - + public long awaitSend(final long bytesNo) throws InterruptedException { long avForWrite = 0; speedLimitLock.lock(); try { - while(avForWrite == 0 && !isClosed()) { - if(availableBytes > 0) { + while (avForWrite == 0 && !isClosed()) { + if (availableBytes > 0) { final long remainingBytes = availableBytes - bytesNo; - - if(remainingBytes >= 0) { + + if (remainingBytes >= 0) { availableBytes = remainingBytes; avForWrite = bytesNo; break; } - + avForWrite = availableBytes; availableBytes = 0; } else { - if(isAvailable.await(2, TimeUnit.SECONDS) && avForWrite > 0) { + if (isAvailable.await(2, TimeUnit.SECONDS) && avForWrite > 0) { break; } } @@ -75,97 +75,98 @@ public long awaitSend(final long bytesNo) throws InterruptedException { } finally { speedLimitLock.unlock(); } - + return avForWrite; } - + private int getMSS(SocketChannel sc) { - + int retMSS = Config.NETWORK_BUFF_LEN_SIZE; - + try { final InetAddress ia = sc.socket().getLocalAddress(); NetworkInterface ni = NetworkInterface.getByInetAddress(ia); int mss = ni.getMTU() - 40; - if(mss > 1000) { + if (mss > 1000) { retMSS = mss; } - }catch(Throwable t) { - if(logger.isLoggable(Level.FINE)){ + } catch (Throwable t) { + if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Cannot determine MTU for socket channel: " + sc); } } - + return retMSS; } - + public void addWorkerStream(SocketChannel sc, boolean sentCookie) throws Exception { - synchronized(this.closeLock) { + synchronized (this.closeLock) { super.addWorkerStream(sc, sentCookie); FDTSelectionKey fsk = null; - - if(config.isBlocking()) { + + if (config.isBlocking()) { fsk = new FDTSelectionKey(fdtSession.sessionID(), sc, SelectionKey.OP_WRITE, this, null); fsk.attach(new FDTWriterKeyAttachement(fsk, fdtSession.useFixedBlockSize(), sentCookie)); - executor.submit(new SocketWriterTask(selectionQueue, (FDTReaderSession)fdtSession, this)); - if(logger.isLoggable(Level.FINEST)) { + executor.submit(new SocketWriterTask(selectionQueue, (FDTReaderSession) fdtSession, this)); + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, " BIO Mode. Adding SocketChannel " + sc + " to the selection queue"); } selectionQueue.add(fsk); } else { - if(logger.isLoggable(Level.FINEST)) { + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, " NBIO Mode. Adding SocketChannel " + sc + " to the SelectionManager! "); } fsk = selectionManager.register(fdtSession.sessionID(), sc, SelectionKey.OP_WRITE, this); fsk.attach(new FDTWriterKeyAttachement(fsk, fdtSession.useFixedBlockSize(), sentCookie)); - if(!fsk.registerInterest()) { + if (!fsk.registerInterest()) { logger.log(Level.WARNING, " \n\n Smth went terrible wrong ?? \n\n fsk.registerInterest() returned false \n\n"); } } - + final int mss = getMSS(sc); - if(logger.isLoggable(Level.FINER)) { + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, " Setting MSS for: " + sc + " to: " + mss); } fsk.setMSS(mss); - + channels.put(sc, fsk); } } - + public void startTransport(final boolean sendCookie) throws Exception { super.startTransport(sendCookie); - if(!config.isBlocking()) { - for(int i =0; i <= Utils.availableProcessors()*2; i++) { - executor.submit(new SocketWriterTask(selectionQueue, (FDTReaderSession)fdtSession, this)); + if (!config.isBlocking()) { + for (int i = 0; i <= Utils.availableProcessors() * 2; i++) { + executor.submit(new SocketWriterTask(selectionQueue, (FDTReaderSession) fdtSession, this)); } } } - + // //TODO - can we recover if downCause != null // - implement a timeout retry ? ... for the moment it just finishes the entire session // - this behaviour should be changed when dynamic creation of workers will be added + /** - * @param fdtSelectionKey + * @param fdtSelectionKey */ public void workerDown(FDTSelectionKey fdtSelectionKey, Throwable downCause) { //smth gone wrong ... or maybe the session finished already //I do not know if it should take other action ... for the moment the session will go down - if(downCause != null) { + if (downCause != null) { logger.log(Level.WARNING, " [ TCPSessionReader ] for fdtSession [ " + fdtSession + " ] got an error on a worker", downCause); } close("Worker down", downCause); - - if(fdtSession != null) { + + if (fdtSession != null) { try { - ((FDTReaderSession)fdtSession).transportWorkerDown(); - }catch(Throwable ignore){ + ((FDTReaderSession) fdtSession).transportWorkerDown(); + } catch (Throwable ignore) { //really don't care } } } - + } diff --git a/src/lia/util/net/copy/transport/TCPTransportProvider.java b/src/lia/util/net/copy/transport/TCPTransportProvider.java index d9b6404..4314739 100644 --- a/src/lia/util/net/copy/transport/TCPTransportProvider.java +++ b/src/lia/util/net/copy/transport/TCPTransportProvider.java @@ -3,6 +3,16 @@ */ package lia.util.net.copy.transport; +import lia.util.net.common.AbstractFDTIOEntity; +import lia.util.net.common.Config; +import lia.util.net.common.DirectByteBufferPool; +import lia.util.net.common.Utils; +import lia.util.net.copy.FDTSession; +import lia.util.net.copy.monitoring.NetSessionMonitoringTask; +import lia.util.net.copy.transport.internal.FDTSelectionKey; +import lia.util.net.copy.transport.internal.SelectionHandler; +import lia.util.net.copy.transport.internal.SelectionManager; + import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -11,72 +21,38 @@ import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.AbstractFDTIOEntity; -import lia.util.net.common.Config; -import lia.util.net.common.DirectByteBufferPool; -import lia.util.net.common.Utils; -import lia.util.net.copy.FDTSession; -import lia.util.net.copy.monitoring.NetSessionMonitoringTask; -import lia.util.net.copy.transport.internal.FDTSelectionKey; -import lia.util.net.copy.transport.internal.SelectionHandler; -import lia.util.net.copy.transport.internal.SelectionManager; /** * The base class for all read/write over the wire, and also has the "SpeedLimiter" incorporated, or not :) - * + * * @author ramiro */ public abstract class TCPTransportProvider extends AbstractFDTIOEntity implements SelectionHandler, SpeedLimiter { + protected static final SelectionManager selectionManager = SelectionManager.getInstance(); private static final Logger logger = Logger.getLogger(TCPTransportProvider.class.getName()); - private static final Config config = Config.getInstance(); - - protected static final SelectionManager selectionManager = SelectionManager.getInstance(); - protected final Lock speedLimitLock = new ReentrantLock(true); protected final Condition isAvailable = speedLimitLock.newCondition(); - - protected long availableBytes; - // GuardedBy - this.closeLock protected final HashMap channels = new HashMap(); - protected final FDTSession fdtSession; - protected final ExecutorService executor; - protected final ArrayList socketTasks = new ArrayList(); - protected final BlockingQueue selectionQueue; - + public NetSessionMonitoringTask monitoringTask; + protected long availableBytes; protected InetAddress endPointAddress; - protected int port; - protected int numberOfStreams; - - public NetSessionMonitoringTask monitoringTask; - ScheduledFuture monitoringTaskFuture; ScheduledFuture limiterTask; @@ -105,33 +81,6 @@ public TCPTransportProvider(FDTSession fdtSession, InetAddress endPointAddress, this.numberOfStreams = numberOfStreams; } - public final boolean useFixedBlockSize() { - return fdtSession.useFixedBlockSize(); - } - - public final boolean localLoop() { - return fdtSession.localLoop(); - } - - public final boolean isNetTest() { - return fdtSession.isNetTest(); - } - - public long getSize() { - return -1; - } - - public long getNotifyDelay() { - return fdtSession.getRateLimitDelay(); - } - - public void notifyAvailableBytes(long available) { - } - - public final long getRateLimit() { - return fdtSession.getRateLimit(); - } - private static final List tryToConnect(InetSocketAddress addr, int numberOfStreams, ByteBuffer connectCookie, final boolean sendCookie) throws Exception { if (addr == null) { @@ -180,14 +129,14 @@ private static final List tryToConnect(InetSocketAddress addr, in tmpChannels.add(sc); final Socket s = sc.socket(); - + if (windowSize > 0) { s.setSendBufferSize(windowSize); } final String sdpConfFlag = System.getProperty("com.sun.sdp.conf"); final boolean bSDP = (sdpConfFlag != null && !sdpConfFlag.isEmpty()); - - if(!bSDP) { + + if (!bSDP) { try { s.setKeepAlive(true); } catch (Throwable t) { @@ -201,7 +150,7 @@ private static final List tryToConnect(InetSocketAddress addr, in logger.log(Level.WARNING, "[ FDTServer ] [ AcceptableTask ] Cannot set traffic class for " + sc + "[ IPTOS_RELIABILITY (0x04) | IPTOS_THROUGHPUT (0x08) | IPTOS_LOWDELAY (0x10) ] Will ignore the error. Contact your sys admin.", t); } } - + if (!sc.isBlocking()) { sc.register(tmpSelector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE); } else { @@ -235,7 +184,7 @@ private static final List tryToConnect(InetSocketAddress addr, in } Set selectedKeys = tmpSelector.selectedKeys(); - for (Iterator it = selectedKeys.iterator(); it.hasNext();) { + for (Iterator it = selectedKeys.iterator(); it.hasNext(); ) { SelectionKey ssk = it.next(); it.remove(); SocketChannel sc = (SocketChannel) ssk.channel(); @@ -303,6 +252,33 @@ private static final List tryToConnect(InetSocketAddress addr, in return tmpChannels; } + public final boolean useFixedBlockSize() { + return fdtSession.useFixedBlockSize(); + } + + public final boolean localLoop() { + return fdtSession.localLoop(); + } + + public final boolean isNetTest() { + return fdtSession.isNetTest(); + } + + public long getSize() { + return -1; + } + + public long getNotifyDelay() { + return fdtSession.getRateLimitDelay(); + } + + public void notifyAvailableBytes(long available) { + } + + public final long getRateLimit() { + return fdtSession.getRateLimit(); + } + public int getNumberOfStreams() { synchronized (this.closeLock) { return channels.size(); diff --git a/src/lia/util/net/copy/transport/gui/FileHandler.java b/src/lia/util/net/copy/transport/gui/FileHandler.java index 71d5e52..c0b84e7 100644 --- a/src/lia/util/net/copy/transport/gui/FileHandler.java +++ b/src/lia/util/net/copy/transport/gui/FileHandler.java @@ -9,26 +9,35 @@ import java.io.Serializable; /** - * * @author Ciprian Dobre * The serializable object representing the properties of a file... - * */ public class FileHandler implements Serializable { /** * serialVersionUID */ private static final long serialVersionUID = 1988671591829311032L; - /** The name of the file or folder */ + /** + * The name of the file or folder + */ private final String name; - /** The last time it was modified */ + /** + * The last time it was modified + */ private final long modif; - /** The length of the file, should be -1 if folder */ + /** + * The length of the file, should be -1 if folder + */ private final long size; - /** Read flag is set ? */ + /** + * Read flag is set ? + */ private final boolean read; - /** Write flag is set ? */ + /** + * Write flag is set ? + */ private final boolean write; + public FileHandler(String name, long modif, long size, boolean read, boolean write) { this.name = name; this.modif = modif; @@ -36,9 +45,24 @@ public FileHandler(String name, long modif, long size, boolean read, boolean wri this.read = read; this.write = write; } - public final String getName() { return name; } - public final long getModif() { return modif; } - public final long getSize() { return size; } - public final boolean canRead() { return read; } - public final boolean canWrite() { return write; } + + public final String getName() { + return name; + } + + public final long getModif() { + return modif; + } + + public final long getSize() { + return size; + } + + public final boolean canRead() { + return read; + } + + public final boolean canWrite() { + return write; + } } \ No newline at end of file diff --git a/src/lia/util/net/copy/transport/gui/GUIControlChannel.java b/src/lia/util/net/copy/transport/gui/GUIControlChannel.java index 29f6101..6af0e63 100644 --- a/src/lia/util/net/copy/transport/gui/GUIControlChannel.java +++ b/src/lia/util/net/copy/transport/gui/GUIControlChannel.java @@ -3,50 +3,41 @@ */ package lia.util.net.copy.transport.gui; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import lia.util.net.common.AbstractFDTCloseable; +import lia.util.net.common.Config; +import lia.util.net.copy.transport.CtrlMsg; +import lia.util.net.copy.transport.FDTProcolException; + +import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.concurrent.ConcurrentLinkedQueue; -import lia.util.net.common.AbstractFDTCloseable; -import lia.util.net.common.Config; -import lia.util.net.copy.transport.CtrlMsg; -import lia.util.net.copy.transport.FDTProcolException; - /** * A special control channel used to transport only GUI messages - on the client side of the connection. + * * @author Ciprian Dobre */ public class GUIControlChannel extends AbstractFDTCloseable implements Runnable { + public static final int CONNECT_TIMEOUT = 20 * 1000; private static final CtrlMsg versionMsg = new CtrlMsg(CtrlMsg.PROTOCOL_VERSION, Config.FDT_FULL_VERSION + "-" + Config.FDT_RELEASE_DATE); - - private GUIControlChannelNotifier notifier; - private Socket controlSocket; public final InetAddress remoteAddress; public final int remotePort; public final int localPort; - + private GUIControlChannelNotifier notifier; + private Socket controlSocket; private ObjectOutputStream oos = null; private ObjectInputStream ois = null; - - public static final int CONNECT_TIMEOUT = 20 * 1000; - private ConcurrentLinkedQueue qToSend = new ConcurrentLinkedQueue(); /** - * * Try to connect to a remote FDT instance - * + * * @param address * @param port - * @param sessionID * @param notifier * @throws Exception */ @@ -55,60 +46,58 @@ public GUIControlChannel(String address, int port, GUIControlChannelNotifier not } /** - * * Try to connect to a remote FDT instance - * - * @param address + * + * @param inetAddress * @param port - * @param fdtSessionID * @param notifier * @throws Exception */ public GUIControlChannel(InetAddress inetAddress, int port, GUIControlChannelNotifier notifier) throws Exception { try { this.notifier = notifier; - + controlSocket = new Socket(); - controlSocket.connect(new InetSocketAddress(inetAddress, port), CONNECT_TIMEOUT ); - + controlSocket.connect(new InetSocketAddress(inetAddress, port), CONNECT_TIMEOUT); + this.remoteAddress = inetAddress; this.remotePort = port; this.localPort = controlSocket.getLocalPort(); - + controlSocket.setTcpNoDelay(true); - + //only the first octet will be interpreted by the AcceptTask at the other end controlSocket.getOutputStream().write(new byte[]{3}); - + //from now on only CtrlMsg will be sent initStreams(); controlSocket.setSoTimeout(1000); // - } catch(Throwable t) { + } catch (Throwable t) { close("Cannot instantiate ControlChannel", t); throw new Exception(t); } } - + public String toString() { - return (controlSocket == null)?"null":controlSocket.toString(); + return (controlSocket == null) ? "null" : controlSocket.toString(); } private void initStreams() throws Exception { oos = new ObjectOutputStream(new BufferedOutputStream(controlSocket.getOutputStream())); //send the version - + sendMsgImpl(versionMsg); ois = new ObjectInputStream(new BufferedInputStream(controlSocket.getInputStream())); - + //wait for remote version - CtrlMsg ctrlMsg = (CtrlMsg)ois.readObject(); - if(ctrlMsg.tag != CtrlMsg.PROTOCOL_VERSION) { + CtrlMsg ctrlMsg = (CtrlMsg) ois.readObject(); + if (ctrlMsg.tag != CtrlMsg.PROTOCOL_VERSION) { throw new FDTProcolException("Unexpected remote control message. Expected PROTOCOL_VERSION tag [ " + CtrlMsg.PROTOCOL_VERSION + " ] Received tag: " + ctrlMsg.tag); } - + Object o = ois.readObject(); // read the working dir notifier.notifyCtrlMsg(this, o); o = ois.readObject(); // read the user home @@ -120,30 +109,30 @@ private void initStreams() throws Exception { o = ois.readObject(); // read the current roots notifier.notifyCtrlMsg(this, o); } - + private void cleanup() { - if(ois != null) { + if (ois != null) { try { ois.close(); - }catch(Throwable t){ + } catch (Throwable t) { //ignore } ois = null; } - if(oos != null) { + if (oos != null) { try { oos.close(); - }catch(Throwable t){ + } catch (Throwable t) { //ignore } oos = null; } - if(controlSocket != null) { + if (controlSocket != null) { try { controlSocket.close(); - }catch(Throwable t){ + } catch (Throwable t) { //ignore } controlSocket = null; @@ -151,53 +140,53 @@ private void cleanup() { } public void sendCtrlMessage(Object ctrlMsg) throws IOException, FDTProcolException { - - if(isClosed() && controlSocket != null && !controlSocket.isClosed()) { + + if (isClosed() && controlSocket != null && !controlSocket.isClosed()) { throw new FDTProcolException("Control channel already closed"); } - + qToSend.add(ctrlMsg); } - + public boolean isClosed() { - return super.isClosed() || controlSocket == null || controlSocket.isClosed(); + return super.isClosed() || controlSocket == null || controlSocket.isClosed(); } private void sendAllMsgs() throws Exception { - for(;;) { + for (; ; ) { Object ctrlMsg = qToSend.poll(); - if(ctrlMsg == null) break; + if (ctrlMsg == null) break; sendMsgImpl(ctrlMsg); } } - + private void sendMsgImpl(Object o) throws Exception { try { oos.writeObject(o); oos.reset();//DO NOT CACHE! oos.flush(); - } catch(Throwable t) { + } catch (Throwable t) { close("Exception sending control data", t); - throw new IOException(" Cannot send ctrl message ( " + t.getCause() + " ) "); + throw new IOException(" Cannot send ctrl message ( " + t.getCause() + " ) "); } } - + public void run() { try { - while(!isClosed()) { + while (!isClosed()) { try { sendAllMsgs(); Object o = ois.readObject(); - if(o == null) continue; + if (o == null) continue; notifier.notifyCtrlMsg(this, o); - } catch(SocketTimeoutException ste) { + } catch (SocketTimeoutException ste) { //ignore this??? or shall I close it() ? - } catch(FDTProcolException fdte) { + } catch (FDTProcolException fdte) { close("FDTProtocolException", fdte); } } - - } catch(Throwable t) { + + } catch (Throwable t) { close(null, t); } } @@ -205,15 +194,17 @@ public void run() { protected void internalClose() { try { cleanup(); - }catch(Throwable ignore){} - + } catch (Throwable ignore) { + } + try { - if(notifier != null) { + if (notifier != null) { notifier.notifyCtrlSessionDown(this, downCause()); } - }catch(Throwable ignore){} + } catch (Throwable ignore) { + } } - + } // end of class GUIControlChannel diff --git a/src/lia/util/net/copy/transport/gui/GUIControlChannelNotifier.java b/src/lia/util/net/copy/transport/gui/GUIControlChannelNotifier.java index 5ca9ed5..4b77261 100644 --- a/src/lia/util/net/copy/transport/gui/GUIControlChannelNotifier.java +++ b/src/lia/util/net/copy/transport/gui/GUIControlChannelNotifier.java @@ -4,11 +4,13 @@ /** * An interface to be used for signaling between session and gui + * * @author Ciprian Dobre */ public interface GUIControlChannelNotifier { - + public void notifyCtrlMsg(GUIControlChannel controlChannel, Object ctrlMessage) throws FDTProcolException; + public void notifyCtrlSessionDown(GUIControlChannel controlChannel, Throwable cause) throws FDTProcolException; - + } diff --git a/src/lia/util/net/copy/transport/gui/GUIMessage.java b/src/lia/util/net/copy/transport/gui/GUIMessage.java index ecd0572..590fb96 100644 --- a/src/lia/util/net/copy/transport/gui/GUIMessage.java +++ b/src/lia/util/net/copy/transport/gui/GUIMessage.java @@ -9,36 +9,48 @@ import java.io.Serializable; /** - * * @author Ciprian Dobre - * */ public class GUIMessage implements Serializable { /** * serialVersionUID */ private static final long serialVersionUID = 1988671591829311032L; - /** The tag of the message */ + /** + * The tag of the message + */ private final int mID; - /** The message */ + /** + * The message + */ private final Object msg; - - /** Possible exception */ + + /** + * Possible exception + */ private final Exception e; - + public GUIMessage(int mID, Object msg) { this.mID = mID; this.msg = msg; this.e = null; } - + public GUIMessage(int mID, Object msg, Exception e) { - this.mID = mID; - this.msg = msg; - this.e = e; + this.mID = mID; + this.msg = msg; + this.e = e; + } + + public final int getMID() { + return mID; + } + + public final Object getMsg() { + return msg; + } + + public final Exception getException() { + return e; } - - public final int getMID() { return mID; } - public final Object getMsg() { return msg; } - public final Exception getException() { return e; } } \ No newline at end of file diff --git a/src/lia/util/net/copy/transport/gui/ServerSessionManager.java b/src/lia/util/net/copy/transport/gui/ServerSessionManager.java index 3f1ba29..dade374 100644 --- a/src/lia/util/net/copy/transport/gui/ServerSessionManager.java +++ b/src/lia/util/net/copy/transport/gui/ServerSessionManager.java @@ -3,83 +3,70 @@ */ package lia.util.net.copy.transport.gui; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import lia.util.net.common.AbstractFDTCloseable; +import lia.util.net.common.Config; +import lia.util.net.copy.transport.ControlChannel; +import lia.util.net.copy.transport.CtrlMsg; +import lia.util.net.copy.transport.FDTProcolException; + +import javax.swing.filechooser.*; +import java.io.*; import java.net.InetAddress; import java.net.Socket; import java.net.SocketTimeoutException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; -import java.util.Vector; +import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.filechooser.FileSystemView; - -import lia.util.net.common.AbstractFDTCloseable; -import lia.util.net.common.Config; -import lia.util.net.copy.transport.ControlChannel; -import lia.util.net.copy.transport.CtrlMsg; -import lia.util.net.copy.transport.FDTProcolException; - /** - * * The manager that handler the local FS on the server side of the communication, it should be interrogated in order * to fill the methods and data of RemoteSessionManager - * + * * @author Ciprian Dobre */ public class ServerSessionManager extends AbstractFDTCloseable implements Runnable { private static final CtrlMsg versionMsg = new CtrlMsg(CtrlMsg.PROTOCOL_VERSION, Config.FDT_FULL_VERSION + "-" + Config.FDT_RELEASE_DATE); - - private static FileSystemView local = FileSystemView.getFileSystemView(); - - // helper mapping between display names and real File folders used for root folders - private final HashMap roots = new HashMap(); - - private File currentFile; - - /** The current working directory */ - public String currentDir; - - private boolean canWrite; - - /** The name of the operating system */ - public String osName; - - /** The file separator */ - public String fileSeparator; - - /** The default directory of the user */ - public String userDir; - - protected final Vector currentFiles = new Vector(); - - private Socket controlSocket; + private static final Logger logger = Logger.getLogger(ControlChannel.class.getName()); + private static FileSystemView local = FileSystemView.getFileSystemView(); public final InetAddress remoteAddress; public final int remotePort; public final int localPort; + protected final Vector currentFiles = new Vector(); + // helper mapping between display names and real File folders used for root folders + private final HashMap roots = new HashMap(); + private final HashMap h = new HashMap(); + /** + * The current working directory + */ + public String currentDir; + /** + * The name of the operating system + */ + public String osName; + /** + * The file separator + */ + public String fileSeparator; + /** + * The default directory of the user + */ + public String userDir; + private File currentFile; + private boolean canWrite; + private Socket controlSocket; private ObjectOutputStream oos = null; private ObjectInputStream ois = null; - private static final Logger logger = Logger.getLogger(ControlChannel.class.getName()); private ConcurrentLinkedQueue qToSend = new ConcurrentLinkedQueue(); - public ServerSessionManager(Socket s) throws Exception { - currentFile = local.getHomeDirectory(); - this.currentDir = currentFile.getAbsolutePath(); - osName = System.getProperty("os.name"); - userDir = System.getProperty("user.home"); - fileSeparator = System.getProperty("file.separator"); - update(); + public ServerSessionManager(Socket s) throws Exception { + currentFile = local.getHomeDirectory(); + this.currentDir = currentFile.getAbsolutePath(); + osName = System.getProperty("os.name"); + userDir = System.getProperty("user.home"); + fileSeparator = System.getProperty("file.separator"); + update(); try { this.controlSocket = s; this.remoteAddress = s.getInetAddress(); @@ -87,332 +74,348 @@ public ServerSessionManager(Socket s) throws Exception { this.localPort = s.getLocalPort(); initStreams(); controlSocket.setSoTimeout(1000); - } catch(Throwable t) { + } catch (Throwable t) { close("Cannot instantiate ControlChannel", t); throw new Exception(t); } - } - - public void setAbsoluteDir(String dir) { - if (dir == null) return; - dir = getRoot(dir); - if (roots.containsKey(dir)) { - currentFile = roots.get(dir); - currentDir = currentFile.getAbsolutePath(); - update(); - return; - } - try { - File f = new File(dir); - if (!f.exists() || !f.isDirectory() || !f.canRead()) return; - currentFile = f; - currentDir = currentFile.getAbsolutePath(); - update(); - } catch (Exception e) { } - } - - private final String getRoot(String dir) { - if (roots.containsKey(dir)) return dir; - for (Map.Entry entry : roots.entrySet()) { - if (entry.getValue().getAbsolutePath().equals(dir)) return entry.getKey(); - } - return dir; - } - - public void setRelativeDir(String dir) { - File f = local.getChild(currentFile, dir); - if (f==null || !f.exists() || !f.isDirectory()) return; // error, cannot change - currentFile = f; - currentDir = f.getAbsolutePath(); - update(); - } - - public void setUpDir() { - File f = local.getParentDirectory(currentFile); - if (f == null || !f.exists() || !f.isDirectory()) return; // error, cannot change - currentFile = f; - currentDir = f.getAbsolutePath(); - update(); - } - - public String[] getRoots() { - roots.clear(); // clear the previously discovered roots - // auxiliary hash used - final HashSet h = new HashSet(); - // start by using the FileSystemView... - File roots[] = local.getRoots(); - if (roots != null) { - for (int i=0; i it = this.roots.keySet().iterator(); it.hasNext() && i entry : roots.entrySet()) { + if (entry.getValue().getAbsolutePath().equals(dir)) return entry.getKey(); + } + return dir; + } + + public void setRelativeDir(String dir) { + File f = local.getChild(currentFile, dir); + if (f == null || !f.exists() || !f.isDirectory()) return; // error, cannot change + currentFile = f; + currentDir = f.getAbsolutePath(); + update(); + } + + public void setUpDir() { + File f = local.getParentDirectory(currentFile); + if (f == null || !f.exists() || !f.isDirectory()) return; // error, cannot change + currentFile = f; + currentDir = f.getAbsolutePath(); + update(); + } + + public String[] getRoots() { + roots.clear(); // clear the previously discovered roots + // auxiliary hash used + final HashSet h = new HashSet(); + // start by using the FileSystemView... + File roots[] = local.getRoots(); + if (roots != null) { + for (int i = 0; i < roots.length; i++) { + String displayName = local.getSystemDisplayName(roots[i]); + if (h.contains(displayName)) continue; // do not add it twice + if (displayName.length() == 0) + continue; // skip empty drives (it applies to cd drives and floppy drives without media + h.add(displayName); + this.roots.put(displayName, roots[i]); + } + } + // then use the File object.... + roots = File.listRoots(); + if (roots != null) { + for (int i = 0; i < roots.length; i++) { + String displayName = local.getSystemDisplayName(roots[i]); + if (h.contains(displayName)) continue; // do not add it twice + if (displayName.length() == 0) + continue; // skip empty drives (it applies to cd drives and floppy drives without media + h.add(displayName); + this.roots.put(displayName, roots[i]); + } + } + // also if linux put the path to the home directory... + if (System.getProperty("os.name").toLowerCase(Locale.US).contains("linux")) { + String p = System.getProperty("user.home"); + File f = new File(p); + if (f.exists()) { + File pa = null; + while ((pa = f.getParentFile()) != null && pa.exists()) { + this.roots.put(f.getAbsolutePath(), f); + f = pa; + } + } + } + final String[] keys = new String[this.roots.size()]; + int i = 0; + for (Iterator it = this.roots.keySet().iterator(); it.hasNext() && i < keys.length; i++) { + keys[i] = it.next(); + } + return keys; + } + + public String getShortRootName(String rootFolder) { + if (rootFolder == null) return null; + rootFolder = getRoot(rootFolder); + if (roots.containsKey(rootFolder)) return roots.get(rootFolder).getAbsolutePath(); + return null; + } + + public boolean isRoot() { + if (local.isDrive(currentFile)) { + return true; + } + File f = local.getParentDirectory(currentFile); + return f == null; + } + + protected void update() { + currentFiles.clear(); + // list the current files + File l[] = local.getFiles(currentFile, false); + if (l != null) { + for (int i = 0; i < l.length; i++) { + String fn = local.getSystemDisplayName(l[i]); + if (fn.length() == 0) continue; + if (l[i].isDirectory()) { + // check to see if we are not looking at some links... + try { + File tmpf = local.getChild(currentFile, fn); + if (tmpf == null || !local.isTraversable(tmpf)) continue; // alarm, link detected here + local.getFiles(tmpf, false); + } catch (Throwable t) { + continue; + } + // otherwise is ok to add it... + currentFiles.add(new FileHandler(fn, l[i].lastModified(), -1L, l[i].canRead(), l[i].canWrite())); + } else if (l[i].isFile()) { + currentFiles.add(new FileHandler(fn, l[i].lastModified(), l[i].length(), l[i].canRead(), l[i].canWrite())); + } + } + } + try { + int i = 0; + while (true) { + File f = new File(currentDir + System.getProperty("file.separator") + "fdt" + i); + if (f.exists()) continue; + f.createNewFile(); + canWrite = f.exists(); + if (canWrite) + f.delete(); + break; + } + } catch (Throwable t) { + canWrite = false; + } + } + + /** + * Message comming from the other end of the connection... + */ + public void process(Object o) throws Exception { + if (!(o instanceof CtrlMsg)) return; + CtrlMsg msg = (CtrlMsg) o; + if (msg.tag != CtrlMsg.GUI_MSG) return; // only this type of message can be processed here + GUIMessage m = (GUIMessage) msg.message; + switch (m.getMID()) { + case 0: // request for current directory... + { + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(0, new Object[]{getWorkingDirectory(), canWrite})); + sendCtrlMessage(c); + break; + } + case 1: // request for user home + { + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(1, getUserHome())); + sendCtrlMessage(c); + break; + } + case 2: // request for os name + { + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(2, getOSName())); + sendCtrlMessage(c); + break; + } + case 3: // request for current files + { + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList())); + sendCtrlMessage(c); + break; + } + case 4: // request for the current roots + { + CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(4, getRoots())); + sendCtrlMessage(c); + c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(9, formShortNames())); + sendCtrlMessage(c); + break; + } // case 5: // is root request // { // CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(5, Boolean.valueOf(isRoot()))); // sendCtrlMessage(c); // break; // } - case 6: // set absolute dir - { - String dir = (String)m.getMsg(); - setAbsoluteDir(dir); - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(5, Boolean.valueOf(isRoot()))); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(12, freeSpace())); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(0, new Object[] { getWorkingDirectory(), canWrite })); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList())); - sendCtrlMessage(c); - break; - } - case 7: // set relative dir - { - String dir = (String)m.getMsg(); - setRelativeDir(dir); - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(5, Boolean.valueOf(isRoot()))); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(12, freeSpace())); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(0, new Object[] { getWorkingDirectory(), canWrite })); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList())); - sendCtrlMessage(c); - break; - } - case 8: // set up dir - { - setUpDir(); - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(5, Boolean.valueOf(isRoot()))); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(12, freeSpace())); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(0, new Object[] { getWorkingDirectory(), canWrite })); - sendCtrlMessage(c); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList())); - sendCtrlMessage(c); - break; - } - case 10: // remove files - { - String files[] = (String[])m.getMsg(); - removeFiles(files); - CtrlMsg c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList())); - sendCtrlMessage(c); - break; - } - case 11: // create dir - { - String name = (String)m.getMsg(); - CtrlMsg c = null; - try { - createDir(name); - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList())); - } catch (Exception e) { - c = new CtrlMsg(CtrlMsg.GUI_MSG, new GUIMessage(3, getFileList(), e)); - } - sendCtrlMessage(c); - break; - } - } - } - - /** Returns the current directory of the session.. (default user home) */ - public String getWorkingDirectory() { - return currentDir; - } - - /** Returns the directory home of the user */ - public String getUserHome() { - return userDir; - } - - /** Returns the name of the operating system */ - public String getOSName() { - return osName; - } - - public String getFileSeparator() { - return fileSeparator; - } - - /** Returns the list of current files and folder in the current directory of the session */ - public Vector getFileList() { - return currentFiles; - } - - public void removeFiles(String[] files) { - if (files == null) return; - for (int i=0; i getFileList() { + return currentFiles; + } + + public void removeFiles(String[] files) { + if (files == null) return; + for (int i = 0; i < files.length; i++) { + try { + File f = new File(files[i]); + if (f.isDirectory()) { + File ff[] = f.listFiles(); + if (ff != null) { + String str[] = new String[ff.length]; + for (int j = 0; j < str.length; j++) { + str[j] = ff[j].getAbsolutePath(); + } + removeFiles(str); + } + } + f.delete(); + } catch (Throwable t) { + } + } + update(); + } + + public void createDir(String name) throws Exception { + if (name == null) return; + File f = new File(name); + f.mkdirs(); + update(); + } private void initStreams() throws Exception { oos = new ObjectOutputStream(new BufferedOutputStream(controlSocket.getOutputStream())); //send the version - + sendMsgImpl(versionMsg); ois = new ObjectInputStream(new BufferedInputStream(controlSocket.getInputStream())); //wait for remote version - CtrlMsg ctrlMsg = (CtrlMsg)ois.readObject(); - if(ctrlMsg.tag != CtrlMsg.PROTOCOL_VERSION) { + CtrlMsg ctrlMsg = (CtrlMsg) ois.readObject(); + if (ctrlMsg.tag != CtrlMsg.PROTOCOL_VERSION) { throw new FDTProcolException("Unexpected remote control message. Expected PROTOCOL_VERSION tag [ " + CtrlMsg.PROTOCOL_VERSION + " ] Received tag: " + ctrlMsg.tag); } //if I was able to reach this point ... every other CtrlMsg will be notified // send the working directory.... - GUIMessage m = new GUIMessage(0, new Object[] { getWorkingDirectory(), canWrite }); + GUIMessage m = new GUIMessage(0, new Object[]{getWorkingDirectory(), canWrite}); sendMsgImpl(m); // send the user home m = new GUIMessage(1, getUserHome()); @@ -427,7 +430,7 @@ private void initStreams() throws Exception { m = new GUIMessage(5, Boolean.valueOf(isRoot())); sendMsgImpl(m); // send free space - m = new GUIMessage(12, freeSpace()); + m = new GUIMessage(12, freeSpace()); sendMsgImpl(m); // send the list of current files m = new GUIMessage(3, getFileList()); @@ -442,73 +445,71 @@ private void initStreams() throws Exception { m = new GUIMessage(11, "Init"); sendMsgImpl(m); } - - private final HashMap h = new HashMap(); - + private final HashMap formShortNames() { - h.clear(); - String roots[] = getRoots(); - if (roots == null) return h; - for (int i=0; i 1024l) { - space = space / 1024l; - if (space > 1024l) { - space = space / 1024l; - if (space > 1024l) { - space = space / 1024l; - if (space > 1024l) { - space = space / 1024l; - return space + " TB"; - } else - return space + " GB"; - } else - return space + " MB"; - } else - return space + "KB"; - } else { - return space+" B"; - } - } - + if (currentFile == null) return null; + try { + long space = currentFile.getFreeSpace(); + return parseSize(space); + } catch (Throwable t) { + } + return null; + } + + private final String parseSize(long space) { + if (space > 1024l) { + space = space / 1024l; + if (space > 1024l) { + space = space / 1024l; + if (space > 1024l) { + space = space / 1024l; + if (space > 1024l) { + space = space / 1024l; + return space + " TB"; + } else + return space + " GB"; + } else + return space + " MB"; + } else + return space + "KB"; + } else { + return space + " B"; + } + } + private void cleanup() { - if(ois != null) { + if (ois != null) { try { ois.close(); - }catch(Throwable t){ + } catch (Throwable t) { //ignore } ois = null; } - if(oos != null) { + if (oos != null) { try { oos.close(); - }catch(Throwable t){ + } catch (Throwable t) { //ignore } oos = null; } - if(controlSocket != null) { + if (controlSocket != null) { try { controlSocket.close(); - }catch(Throwable t){ + } catch (Throwable t) { //ignore } controlSocket = null; @@ -516,52 +517,52 @@ private void cleanup() { } public void sendCtrlMessage(Object ctrlMsg) throws IOException, FDTProcolException { - if(isClosed()) { + if (isClosed()) { throw new FDTProcolException("Control channel already closed"); } - - if(logger.isLoggable(Level.FINER)) { + + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "CtrlChannel Queuing for send: " + ctrlMsg.toString()); } qToSend.add(ctrlMsg); } private void sendAllMsgs() throws Exception { - for(;;) { + for (; ; ) { Object ctrlMsg = qToSend.poll(); - if(ctrlMsg == null) break; + if (ctrlMsg == null) break; sendMsgImpl(ctrlMsg); } } - + private void sendMsgImpl(Object o) throws Exception { try { oos.writeObject(o); oos.reset();//DO NOT CACHE! oos.flush(); - } catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, "Got exception sending ctrl message", t); close("Exception sending control data", t); - throw new IOException(" Cannot send ctrl message ( " + t.getCause() + " ) "); + throw new IOException(" Cannot send ctrl message ( " + t.getCause() + " ) "); } } - + public void run() { try { - while(!isClosed()) { + while (!isClosed()) { try { sendAllMsgs(); Object o = ois.readObject(); - if(o == null) continue; + if (o == null) continue; process(o); - } catch(SocketTimeoutException ste) { + } catch (SocketTimeoutException ste) { //ignore this??? or shall I close it() ? - } catch(FDTProcolException fdte) { + } catch (FDTProcolException fdte) { close("FDTProtocolException", fdte); } } - - } catch(Throwable t) { + + } catch (Throwable t) { close(null, t); } close(downMessage(), downCause()); @@ -570,7 +571,8 @@ public void run() { protected void internalClose() { try { cleanup(); - }catch(Throwable ignore){} + } catch (Throwable ignore) { + } } diff --git a/src/lia/util/net/copy/transport/internal/FDTSelectionKey.java b/src/lia/util/net/copy/transport/internal/FDTSelectionKey.java index abfa560..e043266 100644 --- a/src/lia/util/net/copy/transport/internal/FDTSelectionKey.java +++ b/src/lia/util/net/copy/transport/internal/FDTSelectionKey.java @@ -3,20 +3,20 @@ */ package lia.util.net.copy.transport.internal; +import lia.util.net.common.Utils; +import lia.util.net.copy.transport.FDTKeyAttachement; +import lia.util.net.copy.transport.internal.SelectionManager.SelectionTask; + import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import lia.util.net.common.Utils; -import lia.util.net.copy.transport.FDTKeyAttachement; -import lia.util.net.copy.transport.internal.SelectionManager.SelectionTask; - /** * This class is used in conjunction with SelectionManager to handle the NIO event readiness It is a wrapper over * {@link SelectionKey} with - * + * * @author ramiro */ public class FDTSelectionKey { @@ -28,9 +28,7 @@ public class FDTSelectionKey { protected final SelectionTask selectionTask; protected final SocketChannel channel; - - volatile SelectionKey selectionKey; - + protected final UUID fdtSessionID; final SelectionHandler handler; final int interests; @@ -40,14 +38,10 @@ public class FDTSelectionKey { final AtomicBoolean registered; final AtomicBoolean renewed; - - protected final UUID fdtSessionID; - public int opCount; - - private volatile FDTKeyAttachement attachment; - + volatile SelectionKey selectionKey; int MSS = -1; + private volatile FDTKeyAttachement attachment; FDTSelectionKey(UUID fdtSessionID, SocketChannel channel, int interests, SelectionHandler handler, FDTKeyAttachement attachement, Selector selector, SelectionTask selectionTask) { this.channel = channel; @@ -84,9 +78,9 @@ public boolean registerInterest() { /** * Should be called to renew the this interest for I/O readiness - * + * * @return true, if the renewal was successful ( if the key is already registered if will remain register and this - * function will return false) + * function will return false) */ public boolean renewInterest() { @@ -101,7 +95,7 @@ public boolean renewInterest() { /** * Cancels the I/O readiness interests. It can be called multiple times. - * + * * @return true, only the first time when it is called, and false for any other subsequent calls */ public boolean cancel() { @@ -126,13 +120,14 @@ public boolean cancel() { selectionKey.cancel(); } catch (Throwable t) { } - + try { - if(selectionKey.selector() != null) { + if (selectionKey.selector() != null) { //just for cleanup - otherwise stalled sockets renewInterest(); } - }catch(Throwable t) {} + } catch (Throwable t) { + } } final FDTKeyAttachement attach = attachment(); @@ -157,12 +152,12 @@ public SocketChannel channel() { /** * Same functionality provided by NIO's SelectionKey - * + * * @param attachement */ - public final FDTKeyAttachement attach(FDTKeyAttachement o) { + public final FDTKeyAttachement attach(FDTKeyAttachement attachement) { FDTKeyAttachement ret = this.attachment; - this.attachment = o; + this.attachment = attachement; return ret; } @@ -172,8 +167,7 @@ public UUID fdtSessionID() { /** * Same functionality provided by NIO's SelectionKey - * - * @param attachement + * */ public final FDTKeyAttachement attachment() { return attachment; diff --git a/src/lia/util/net/copy/transport/internal/SelectionHandler.java b/src/lia/util/net/copy/transport/internal/SelectionHandler.java index 2ffa716..94afc30 100644 --- a/src/lia/util/net/copy/transport/internal/SelectionHandler.java +++ b/src/lia/util/net/copy/transport/internal/SelectionHandler.java @@ -7,12 +7,13 @@ /** * Every class interested in I/O Events should implement this. * It is used by the SelectionManager to notify I/O readiness - * + * * @author ramiro */ public interface SelectionHandler { - + public void handleSelection(FDTSelectionKey fdtSelectionKey); + public void canceled(FDTSelectionKey fdtSelectionKey); - + } diff --git a/src/lia/util/net/copy/transport/internal/SelectionManager.java b/src/lia/util/net/copy/transport/internal/SelectionManager.java index 183659a..95288a4 100644 --- a/src/lia/util/net/copy/transport/internal/SelectionManager.java +++ b/src/lia/util/net/copy/transport/internal/SelectionManager.java @@ -3,37 +3,195 @@ */ package lia.util.net.copy.transport.internal; +import lia.util.net.common.Config; +import lia.util.net.common.Utils; +import lia.util.net.copy.transport.FDTKeyAttachement; + import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Queue; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.common.Config; -import lia.util.net.common.Utils; -import lia.util.net.copy.transport.FDTKeyAttachement; - /** * Wrapper class for NIO Selector-s and SocketChannel-s, hopefully optimizes the selection process for the entire app. * Every select() runs in its own Task. The synchronization should be "loose" enough ( a sync per Task ), and probably * the contention of selector.wakeup() also. Having queues for FDTKey should scale at high load-s - * + * * @author ramiro */ public class SelectionManager { private static final Logger logger = Logger.getLogger(SelectionManager.class.getName()); + // the one and only. Now, go outside and play + private static final SelectionManager _thisInstance; + + static { + SelectionManager tmpSMgr = null; + + try { + tmpSMgr = new SelectionManager(); + int i = 0; + for (Map.Entry entry : tmpSMgr.selTasksMap.entrySet()) { + Thread t = new Thread(entry.getValue(), " [ SelectionManager ] Selection task ( " + i++ + " )"); + t.setDaemon(true); + t.start(); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Got exception initializing SelectionManager. Cannot continue. Will stop", t); + throw new RuntimeException("Cannot instantiate SelectionManager. Do you want to continue ( N/N )? ", t); + } + + _thisInstance = tmpSMgr; + } + + final Map selTasksMap; + + private final BlockingQueue selectorsQueue; + + private SelectionManager() throws IOException { + selTasksMap = new HashMap(); + int sNo = Config.getInstance().getNumberOfSelectors(); + + selectorsQueue = new ArrayBlockingQueue(sNo); + for (int i = 0; i < sNo; i++) { + Selector sel = Selector.open(); + selectorsQueue.add(sel); + SelectionTask st = new SelectionTask(sel); + selTasksMap.put(sel, st); + } + } + + public static final SelectionManager getInstance() { + return _thisInstance; + } + + void renewInterest(FDTSelectionKey fdtSelectionKey) { + final SelectionTask st = fdtSelectionKey.selectionTask; + boolean bShouldWakeup = false; + + // final ReentrantLock lock = st.lock; + // lock.lock(); + // try { + // } finally { + // lock.unlock(); + // } + synchronized (st) { + if (st.renewQueue.isEmpty() && st.newQueue.isEmpty()) { + bShouldWakeup = true; + } + + st.renewQueue.add(fdtSelectionKey); + } + + if (bShouldWakeup) { + st.selector.wakeup(); + } + } + + /** + * This should not be used from FDT .... + * + * @param channel + * @param interests + * @param selectionHandler + * @return + * @throws InterruptedException + * @throws IOException + */ + public FDTSelectionKey register(final SocketChannel channel, final int interests, final SelectionHandler selectionHandler) throws InterruptedException, IOException { + return register(null, channel, interests, selectionHandler); + } + + /** + * This method register a channel with specific interests. The readiness will be notified to + * SelectionHandler parameter If this key is canceled during the selection process ( e.g stream closes + * ) the same handler will be notified + * + * @param fdtsessionID + * @param channel + * @param interests + * @param selectionHandler + * @return + * @throws InterruptedException + * @throws IOException + */ + public FDTSelectionKey register(final UUID fdtsessionID, final SocketChannel channel, final int interests, final SelectionHandler selectionHandler) throws InterruptedException { + return register(fdtsessionID, channel, interests, selectionHandler, null); + } + + // TODO ... extra checks to see if a channel is already registered + public FDTSelectionKey register(final UUID fdtsessionID, final SocketChannel channel, final int interests, final SelectionHandler selectionHandler, final FDTKeyAttachement attach) throws InterruptedException { + + if (channel == null) { + throw new NullPointerException("SocketChannel cannot be null"); + } + + if (selectionHandler == null) { + throw new NullPointerException("SelectionHanfler cannot be null"); + } + + final Selector sel = getAndRotateSelector(); + final SelectionTask sTask = selTasksMap.get(sel); + + FDTSelectionKey fdtSelectionKey = new FDTSelectionKey(fdtsessionID, channel, interests, selectionHandler, attach, sel, sTask); + + return fdtSelectionKey; + } + + public void stopIt() { + try { + for (Map.Entry entry : selTasksMap.entrySet()) { + entry.getValue().stopIt(); + entry.getValue().selector.wakeup(); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + + void initialKeyRegister(FDTSelectionKey fdtSelectionKey) { + + final Selector sel = fdtSelectionKey.selector; + final SelectionTask st = fdtSelectionKey.selectionTask; + + boolean bShouldWakeup = false; + + // something gone wrong if either the Selector or the SelectionTask are + // null + + // final ReentrantLock lock = st.lock; + + // lock.lock(); + // try { + // } finally { + // lock.unlock(); + // } + + synchronized (st) { + if (st.renewQueue.isEmpty() && st.newQueue.isEmpty()) { + bShouldWakeup = true; + } + + st.newQueue.add(fdtSelectionKey); + } + + if (bShouldWakeup) { + sel.wakeup(); + } + } + + // just serve them in round robin stile + private Selector getAndRotateSelector() throws InterruptedException { + final Selector sel = selectorsQueue.take(); + selectorsQueue.put(sel); + return sel; + } // should be one for every Selector - handles the selection process final static class SelectionTask implements Runnable { @@ -197,7 +355,7 @@ public void run() { private void cancelAllKeys() { SelectionKey sk = null; FDTSelectionKey fsk = null; - for (Iterator it = selector.keys().iterator(); it.hasNext();) { + for (Iterator it = selector.keys().iterator(); it.hasNext(); ) { try { sk = it.next(); @@ -220,169 +378,4 @@ public void stopIt() { } } - // the one and only. Now, go outside and play - private static final SelectionManager _thisInstance; - - final Map selTasksMap; - - private final BlockingQueue selectorsQueue; - - static { - SelectionManager tmpSMgr = null; - - try { - tmpSMgr = new SelectionManager(); - int i = 0; - for (Map.Entry entry : tmpSMgr.selTasksMap.entrySet()) { - Thread t = new Thread(entry.getValue(), " [ SelectionManager ] Selection task ( " + i++ + " )"); - t.setDaemon(true); - t.start(); - } - } catch (Throwable t) { - logger.log(Level.WARNING, "Got exception initializing SelectionManager. Cannot continue. Will stop", t); - throw new RuntimeException("Cannot instantiate SelectionManager. Do you want to continue ( N/N )? ", t); - } - - _thisInstance = tmpSMgr; - } - - private SelectionManager() throws IOException { - selTasksMap = new HashMap(); - int sNo = Config.getInstance().getNumberOfSelectors(); - - selectorsQueue = new ArrayBlockingQueue(sNo); - for (int i = 0; i < sNo; i++) { - Selector sel = Selector.open(); - selectorsQueue.add(sel); - SelectionTask st = new SelectionTask(sel); - selTasksMap.put(sel, st); - } - } - - public static final SelectionManager getInstance() { - return _thisInstance; - } - - void renewInterest(FDTSelectionKey fdtSelectionKey) { - final SelectionTask st = fdtSelectionKey.selectionTask; - boolean bShouldWakeup = false; - - // final ReentrantLock lock = st.lock; - // lock.lock(); - // try { - // } finally { - // lock.unlock(); - // } - synchronized (st) { - if (st.renewQueue.isEmpty() && st.newQueue.isEmpty()) { - bShouldWakeup = true; - } - - st.renewQueue.add(fdtSelectionKey); - } - - if (bShouldWakeup) { - st.selector.wakeup(); - } - } - - /** - * This should not be used from FDT .... - * - * @param channel - * @param interests - * @param selectionHandler - * @return - * @throws InterruptedException - * @throws IOException - */ - public FDTSelectionKey register(final SocketChannel channel, final int interests, final SelectionHandler selectionHandler) throws InterruptedException, IOException { - return register(null, channel, interests, selectionHandler); - } - - /** - * This method register a channel with specific interests. The readiness will be notified to - * SelectionHandler parameter If this key is canceled during the selection process ( e.g stream closes - * ) the same handler will be notified - * - * @param fdtsessionID - * @param channel - * @param interests - * @param selectionHandler - * @return - * @throws InterruptedException - * @throws IOException - */ - public FDTSelectionKey register(final UUID fdtsessionID, final SocketChannel channel, final int interests, final SelectionHandler selectionHandler) throws InterruptedException { - return register(fdtsessionID, channel, interests, selectionHandler, null); - } - - // TODO ... extra checks to see if a channel is already registered - public FDTSelectionKey register(final UUID fdtsessionID, final SocketChannel channel, final int interests, final SelectionHandler selectionHandler, final FDTKeyAttachement attach) throws InterruptedException { - - if (channel == null) { - throw new NullPointerException("SocketChannel cannot be null"); - } - - if (selectionHandler == null) { - throw new NullPointerException("SelectionHanfler cannot be null"); - } - - final Selector sel = getAndRotateSelector(); - final SelectionTask sTask = selTasksMap.get(sel); - - FDTSelectionKey fdtSelectionKey = new FDTSelectionKey(fdtsessionID, channel, interests, selectionHandler, attach, sel, sTask); - - return fdtSelectionKey; - } - - public void stopIt() { - try { - for (Map.Entry entry : selTasksMap.entrySet()) { - entry.getValue().stopIt(); - entry.getValue().selector.wakeup(); - } - } catch (Throwable t) { - t.printStackTrace(); - } - } - - void initialKeyRegister(FDTSelectionKey fdtSelectionKey) { - - final Selector sel = fdtSelectionKey.selector; - final SelectionTask st = fdtSelectionKey.selectionTask; - - boolean bShouldWakeup = false; - - // something gone wrong if either the Selector or the SelectionTask are - // null - - // final ReentrantLock lock = st.lock; - - // lock.lock(); - // try { - // } finally { - // lock.unlock(); - // } - - synchronized (st) { - if (st.renewQueue.isEmpty() && st.newQueue.isEmpty()) { - bShouldWakeup = true; - } - - st.newQueue.add(fdtSelectionKey); - } - - if (bShouldWakeup) { - sel.wakeup(); - } - } - - // just serve them in round robin stile - private Selector getAndRotateSelector() throws InterruptedException { - final Selector sel = selectorsQueue.take(); - selectorsQueue.put(sel); - return sel; - } - } diff --git a/src/lia/util/net/jiperf/ByteBufferPool.java b/src/lia/util/net/jiperf/ByteBufferPool.java index e55d930..94f1d9c 100644 --- a/src/lia/util/net/jiperf/ByteBufferPool.java +++ b/src/lia/util/net/jiperf/ByteBufferPool.java @@ -2,6 +2,7 @@ * $Id$ */ package lia.util.net.jiperf; + import java.nio.ByteBuffer; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; @@ -9,88 +10,86 @@ import java.util.logging.Logger; /** - * - * This will be kept for history :). + * This will be kept for history :). * The entire package lia.util.net.jiperf is the very first version of FDT. It * started as an Iperf-like test for Java. - * + * * @author ramiro */ public class ByteBufferPool { - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger(ByteBufferPool.class.getName()); - //TODO - dynamicaly set this size public static final int BUFFER_SIZE = 32 * 1024 * 1024;//32 MBytes - - //TODO - the size will have to be set based on the capacity() of the + //TODO - the size will have to be set based on the capacity() of the //current buffers in the pool //For the momnet it will be the size of the "list" of the buffers - should be a long instead of an integer public static final int POOL_SIZE = 10; - + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger(ByteBufferPool.class.getName()); //the list of ByteBuffer-s public static ByteBufferPool _theInstance; - + //used for double checked locking private static volatile boolean initialized = false; - + ArrayBlockingQueue thePool; - + private ByteBufferPool() { thePool = new ArrayBlockingQueue(POOL_SIZE); - - int i =0; - for(i =0; i < POOL_SIZE; i++) { + + int i = 0; + for (i = 0; i < POOL_SIZE; i++) { try { if (!thePool.offer(ByteBuffer.allocateDirect(BUFFER_SIZE))) { logger.log(Level.WARNING, " Queue full ??? Should not happen ..."); } - }catch(OutOfMemoryError oom) { + } catch (OutOfMemoryError oom) { logger.log(Level.WARNING, " Please try to increase -XX:MaxDirectMemorySize=256m ", oom); break; - }catch(Throwable t) { + } catch (Throwable t) { logger.log(Level.WARNING, " Got general exception trying to allocate the mem", t); break; } } - + logger.log(Level.INFO, " Succesfully alocated " + i + " buffers"); } - + public static final ByteBufferPool getInstance() { - + //double checked locking - if(!initialized) { - synchronized(ByteBufferPool.class) { - if(!initialized) { + if (!initialized) { + synchronized (ByteBufferPool.class) { + if (!initialized) { _theInstance = new ByteBufferPool(); } } } - + return _theInstance; } - + public ByteBuffer get() { ByteBuffer retBuff = null; - + while (retBuff == null) { try { retBuff = thePool.poll(10, TimeUnit.SECONDS); - }catch(InterruptedException ie) { + } catch (InterruptedException ie) { logger.log(Level.WARNING, " The thread was interrupted trying to get a buffer from the pool", ie); } - if(retBuff == null) { + if (retBuff == null) { logger.log(Level.WARNING, " Timeot reached ... unable to get a buffer. You should increase the pool size"); } } - + return retBuff; } - + public void put(ByteBuffer buff) { - + //TODO - extra checks here buff.clear(); thePool.offer(buff); diff --git a/src/lia/util/net/jiperf/JIperf.java b/src/lia/util/net/jiperf/JIperf.java index be98c24..7084ff3 100644 --- a/src/lia/util/net/jiperf/JIperf.java +++ b/src/lia/util/net/jiperf/JIperf.java @@ -4,128 +4,124 @@ package lia.util.net.jiperf; import java.util.HashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; /** - * - * This will be kept for history :). + * This will be kept for history :). * The entire package lia.util.net.jiperf is the very first version of FDT. It * started as an Iperf-like test for Java. - * + * * @author ramiro */ public class JIperf { - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger(JIperf.class.getName()); - - /** - * The executor used to perform I/O tasks - */ - private static final ExecutorService executor; - - static { - ThreadPoolExecutor texecutor = new ThreadPoolExecutor(5, 20, 2 * 60, TimeUnit.SECONDS, new SynchronousQueue(), new ThreadFactory() { - AtomicLong l = new AtomicLong(0); - - public Thread newThread(Runnable r) { - return new Thread(r, " JIperf Worker Task " + l.getAndIncrement()); - } - }); - texecutor.setRejectedExecutionHandler(new RejectedExecutionHandler() { - public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { - try { - // slow down a little bit - final long SLEEP_TIME = Math.round(Math.random() * 1000D + 1); - try { - Thread.sleep(SLEEP_TIME); - } catch (Throwable ignore) { - } - System.err.println("\n\n [ RejectedExecutionHandler ] slept for " + SLEEP_TIME); - // resubmit the task! - executor.execute(r); - } catch (Throwable t) { - t.printStackTrace(); - } - } - }); - - // it will be added in 1.6 - // texecutor.allowCoreThreadTimeOut(true); - texecutor.prestartAllCoreThreads(); - executor = texecutor; - } - - public static final ExecutorService getExecutor() { - return executor; - } - - public static final void shutdownExecutor() { + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger(JIperf.class.getName()); + + /** + * The executor used to perform I/O tasks + */ + private static final ExecutorService executor; + + static { + ThreadPoolExecutor texecutor = new ThreadPoolExecutor(5, 20, 2 * 60, TimeUnit.SECONDS, new SynchronousQueue(), new ThreadFactory() { + AtomicLong l = new AtomicLong(0); + + public Thread newThread(Runnable r) { + return new Thread(r, " JIperf Worker Task " + l.getAndIncrement()); + } + }); + texecutor.setRejectedExecutionHandler(new RejectedExecutionHandler() { + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + try { + // slow down a little bit + final long SLEEP_TIME = Math.round(Math.random() * 1000D + 1); + try { + Thread.sleep(SLEEP_TIME); + } catch (Throwable ignore) { + } + System.err.println("\n\n [ RejectedExecutionHandler ] slept for " + SLEEP_TIME); + // resubmit the task! + executor.execute(r); + } catch (Throwable t) { + t.printStackTrace(); + } + } + }); + + // it will be added in 1.6 + // texecutor.allowCoreThreadTimeOut(true); + texecutor.prestartAllCoreThreads(); + executor = texecutor; + } + + public static final ExecutorService getExecutor() { + return executor; + } + + public static final void shutdownExecutor() { executor.shutdown(); try { - if(!executor.awaitTermination(10L,TimeUnit.SECONDS)) + if (!executor.awaitTermination(10L, TimeUnit.SECONDS)) executor.shutdownNow(); } catch (Exception e) { //nothing to do } - } - - private static HashMap parseArguments(final String args[]) { - if (args == null || args.length == 0) - return null; - HashMap rHM = new HashMap(); - - for (int i = 0; i < args.length - 1; i++) { - if (args[i].startsWith("-")) { - if (args[i + 1].startsWith("-")) { - rHM.put(args[i], ""); - } else { - rHM.put(args[i], args[i + 1]); - i++; - } - } - } - if (args[args.length-1].startsWith("-")) - rHM.put(args[args.length-1], ""); - - return rHM; - } - - public static void printHelp() { - System.err.println("Usage:"); - System.err.println("\tServer"); - System.err.println("\t\tstandalone: java JIperf -s -p portNumer -P numberOfThreads -w windowSize"); - System.err.println("\t\tUse a SSH control connection: java JIperf -s -ssh"); - System.err.println("\tClient"); - System.err.println("\t\tstandalone: java JIperf -c host -p portNumer -P numberOfThreads -w windowSize"); - System.err.println("\t\tremotely start a jiperf server: java JIperf -c host -ssh [-u user] [-E command] -p portNumer -P numberOfThreads -w windowSize"); - - } - - public static void main(String[] args) { - try { - HashMap argsMap = parseArguments(args); - if (argsMap.containsKey("-c")) { - JIperfClient client = new JIperfClient(argsMap); - client.flood(); - } else if (argsMap.containsKey("-s")) { - JIperfServer server = new JIperfServer(argsMap); - server.doWork(); - - } else { - printHelp(); - System.exit(0); - } - } catch (Throwable t) { - t.printStackTrace(); - System.exit(1); - } - } + } + + private static HashMap parseArguments(final String args[]) { + if (args == null || args.length == 0) + return null; + HashMap rHM = new HashMap(); + + for (int i = 0; i < args.length - 1; i++) { + if (args[i].startsWith("-")) { + if (args[i + 1].startsWith("-")) { + rHM.put(args[i], ""); + } else { + rHM.put(args[i], args[i + 1]); + i++; + } + } + } + if (args[args.length - 1].startsWith("-")) + rHM.put(args[args.length - 1], ""); + + return rHM; + } + + public static void printHelp() { + System.err.println("Usage:"); + System.err.println("\tServer"); + System.err.println("\t\tstandalone: java JIperf -s -p portNumer -P numberOfThreads -w windowSize"); + System.err.println("\t\tUse a SSH control connection: java JIperf -s -ssh"); + System.err.println("\tClient"); + System.err.println("\t\tstandalone: java JIperf -c host -p portNumer -P numberOfThreads -w windowSize"); + System.err.println("\t\tremotely start a jiperf server: java JIperf -c host -ssh [-u user] [-E command] -p portNumer -P numberOfThreads -w windowSize"); + + } + + public static void main(String[] args) { + try { + HashMap argsMap = parseArguments(args); + if (argsMap.containsKey("-c")) { + JIperfClient client = new JIperfClient(argsMap); + client.flood(); + } else if (argsMap.containsKey("-s")) { + JIperfServer server = new JIperfServer(argsMap); + server.doWork(); + + } else { + printHelp(); + System.exit(0); + } + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } } diff --git a/src/lia/util/net/jiperf/JIperfClient.java b/src/lia/util/net/jiperf/JIperfClient.java index 821e91c..dd9740f 100644 --- a/src/lia/util/net/jiperf/JIperfClient.java +++ b/src/lia/util/net/jiperf/JIperfClient.java @@ -3,6 +3,8 @@ */ package lia.util.net.jiperf; +import lia.util.net.jiperf.control.ControlStream; + import java.io.File; import java.io.FileInputStream; import java.net.InetSocketAddress; @@ -18,219 +20,218 @@ import java.util.logging.Level; import java.util.logging.Logger; -import lia.util.net.jiperf.control.ControlStream; - /** - * - * This will be kept for history :). + * This will be kept for history :). * The entire package lia.util.net.jiperf is the very first version of FDT. It * started as an Iperf-like test for Java. - * + * * @author ramiro */ public class JIperfClient { - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger(JIperfClient.class.getName()); - - Selector sel; - - int serverPort; + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger(JIperfClient.class.getName()); - int sockNum; + Selector sel; - String serverHost; + int serverPort; - Executor executor; + int sockNum; - ByteBufferPool buffPool; + String serverHost; - // this should be the "staging" queue ... should be a "self-adjusting" buffer - ArrayBlockingQueue queueToSend; + Executor executor; - //SSH control stream - private ControlStream control; + ByteBufferPool buffPool; - class FillingTask implements Runnable { + // this should be the "staging" queue ... should be a "self-adjusting" buffer + ArrayBlockingQueue queueToSend; - FileChannel readChannel; + //SSH control stream + private ControlStream control; - FillingTask() throws Exception { - File dev_zero = new File("/dev/zero"); - readChannel = new FileInputStream(dev_zero).getChannel(); - } + public JIperfClient(final HashMap config) throws Exception { + serverPort = Integer.parseInt(config.get("-p")); + serverHost = config.get("-c"); - public void run() { - for (;;) { - try { - ByteBuffer buff = buffPool.get(); + try { + sockNum = Integer.parseInt(config.get("-P")); + } catch (Throwable t) { + sockNum = 1; + } - readChannel.read(buff);// TODO - should check if it read buff.size() - buff.flip(); - queueToSend.put(buff); - - } catch (Throwable t) { - logger.log(Level.WARNING, "Filling task got exc", t); - try { - Thread.sleep(50); - } catch (Throwable t1) { - } - } - } - } - } - - class WriterTask implements Runnable { - - SelectionKey sk; - - ByteBuffer buff; + /** --SSH mode-- */ + /* we start the remote jiperf server */ + if (config.containsKey("-ssh")) { + String user; + if (config.containsKey("-u")) + user = config.get("-u"); + else + user = System.getProperty("user.name"); + String command; + if (config.containsKey("-E")) + command = config.get("-E"); + else //TODO, maybe set the default the default command to a more generic path + command = "java -XX:MaxDirectMemorySize=512m -cp ~/JIPERF/TEST_JAVA_IO_PERF/JPERF_NIO/bin lia.util.net.jiperf.JIperf -ssh -s"; + System.out.println(" [Client] Using SSH mode: connecting to " + user + "@" + serverHost + " start command:" + command); + try { + control = new ControlStream(); + control.startServer(serverHost, user, command); + control.waitAck(); + //if NAT is in place, the user may specify the gateway IP to be allowed in JIPerf server + String myIP = null; + if (config.containsKey("-F")) + myIP = config.get("-F"); + control.sendInitCommands(myIP, serverPort, sockNum, -1); + control.waitAck(); + } catch (Exception e) { + System.out.println(" [Client] ERROR: " + e); + System.exit(1); + } + } + /** --SSH mode-- */ + + // TODO - the size should also dynamically ajust .... depends very much on the performance + // of the "filling" threads ;) + queueToSend = new ArrayBlockingQueue(ByteBufferPool.POOL_SIZE + 1); + buffPool = ByteBufferPool.getInstance(); + + if (sockNum < 1) + sockNum = 1; + init(); + executor = JIperf.getExecutor(); + + } + + public void init() throws Exception { + + sel = Selector.open(); + + InetSocketAddress addr = new InetSocketAddress(serverHost, serverPort); + for (int i = 0; i < sockNum; i++) { + SocketChannel sc = SocketChannel.open(); + + sc.configureBlocking(false); + + System.out.println("initiating connection"); + + sc.connect(addr); + + // TODO ... for the moment there is a 1-1 mapping between "filling threads" and number of sockets ... + Thread t = new Thread(new FillingTask()); + t.setDaemon(true); + t.start(); + + while (!sc.finishConnect()) { + // TODO - do something useful + try { + Thread.sleep(100); + } catch (Exception ex) { + } + ; + } + + System.out.println("connection established"); + sc.register(sel, SelectionKey.OP_WRITE); + } + + } + + public void flood() throws Exception { + for (; ; ) { + while (sel.select() > 0) + ; + + Iterator it = sel.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey sk = (SelectionKey) it.next(); + + if (sk.isWritable()) { + sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE); + executor.execute(new WriterTask(sk)); + } + + it.remove(); + } + } + + } + + class FillingTask implements Runnable { + + FileChannel readChannel; + + FillingTask() throws Exception { + File dev_zero = new File("/dev/zero"); + readChannel = new FileInputStream(dev_zero).getChannel(); + } + + public void run() { + for (; ; ) { + try { + ByteBuffer buff = buffPool.get(); + + readChannel.read(buff);// TODO - should check if it read buff.size() + buff.flip(); + queueToSend.put(buff); + + } catch (Throwable t) { + logger.log(Level.WARNING, "Filling task got exc", t); + try { + Thread.sleep(50); + } catch (Throwable t1) { + } + } + } + } + } + + class WriterTask implements Runnable { - WriterTask(SelectionKey sk) { - this.sk = sk; - // take a free buffer from the pool - } + SelectionKey sk; - private void writeData() throws Exception { - buff = queueToSend.take(); - SocketChannel sc = (SocketChannel) sk.channel(); - int count = -1; - while ((count = sc.write(buff)) > 0) - ; + ByteBuffer buff; - // TODO - here we should check for buff.remainig() !!!! - if (count < 0) { - sc.close(); - } else { - sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE); - } + WriterTask(SelectionKey sk) { + this.sk = sk; + // take a free buffer from the pool + } - sel.wakeup(); + private void writeData() throws Exception { + buff = queueToSend.take(); + SocketChannel sc = (SocketChannel) sk.channel(); + int count = -1; + while ((count = sc.write(buff)) > 0) + ; - }// readData() + // TODO - here we should check for buff.remainig() !!!! + if (count < 0) { + sc.close(); + } else { + sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE); + } - public void run() { - if (sk == null) - return; - try { - writeData(); - /*try { disable flooding + sel.wakeup(); + + }// readData() + + public void run() { + if (sk == null) + return; + try { + writeData(); + /*try { disable flooding Thread.sleep(1000); } catch (Throwable t1) { }*/ - } catch (Throwable t) { - t.printStackTrace(); - } finally { - // *ALWAYS* return the buffer to the pool whatever happens - buffPool.put(buff); - } - } - }// WriterTask class - - public JIperfClient(final HashMap config) throws Exception { - serverPort = Integer.parseInt(config.get("-p")); - serverHost = config.get("-c"); - - try { - sockNum = Integer.parseInt(config.get("-P")); - } catch (Throwable t) { - sockNum = 1; - } - - /** --SSH mode-- */ - /* we start the remote jiperf server */ - if (config.containsKey("-ssh")) { - String user; - if (config.containsKey("-u")) - user = config.get("-u"); - else - user = System.getProperty("user.name"); - String command; - if (config.containsKey("-E")) - command = config.get("-E"); - else //TODO, maybe set the default the default command to a more generic path - command = "java -XX:MaxDirectMemorySize=512m -cp ~/JIPERF/TEST_JAVA_IO_PERF/JPERF_NIO/bin lia.util.net.jiperf.JIperf -ssh -s"; - System.out.println(" [Client] Using SSH mode: connecting to "+user+"@"+serverHost + " start command:"+command ); - try{ - control = new ControlStream(); - control.startServer(serverHost, user, command); - control.waitAck(); - //if NAT is in place, the user may specify the gateway IP to be allowed in JIPerf server - String myIP=null; - if (config.containsKey("-F")) - myIP = config.get("-F"); - control.sendInitCommands(myIP,serverPort,sockNum,-1); - control.waitAck(); - }catch (Exception e) { - System.out.println(" [Client] ERROR: "+e); - System.exit(1); - } - } - /** --SSH mode-- */ - - // TODO - the size should also dynamically ajust .... depends very much on the performance - // of the "filling" threads ;) - queueToSend = new ArrayBlockingQueue(ByteBufferPool.POOL_SIZE + 1); - buffPool = ByteBufferPool.getInstance(); - - if (sockNum < 1) - sockNum = 1; - init(); - executor = JIperf.getExecutor(); - - } - - public void init() throws Exception { - - sel = Selector.open(); - - InetSocketAddress addr = new InetSocketAddress(serverHost, serverPort); - for (int i = 0; i < sockNum; i++) { - SocketChannel sc = SocketChannel.open(); - - sc.configureBlocking(false); - - System.out.println("initiating connection"); - - sc.connect(addr); - - // TODO ... for the moment there is a 1-1 mapping between "filling threads" and number of sockets ... - Thread t = new Thread(new FillingTask()); - t.setDaemon(true); - t.start(); - - while (!sc.finishConnect()) { - // TODO - do something useful - try { - Thread.sleep(100); - } catch (Exception ex) { - } - ; - } - - System.out.println("connection established"); - sc.register(sel, SelectionKey.OP_WRITE); - } - - } - - public void flood() throws Exception { - for (;;) { - while (sel.select() > 0) - ; - - Iterator it = sel.selectedKeys().iterator(); - while (it.hasNext()) { - SelectionKey sk = (SelectionKey) it.next(); - - if (sk.isWritable()) { - sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE); - executor.execute(new WriterTask(sk)); - } - - it.remove(); - } - } - - } + } catch (Throwable t) { + t.printStackTrace(); + } finally { + // *ALWAYS* return the buffer to the pool whatever happens + buffPool.put(buff); + } + } + }// WriterTask class } diff --git a/src/lia/util/net/jiperf/JIperfServer.java b/src/lia/util/net/jiperf/JIperfServer.java index 594f1a6..015e198 100644 --- a/src/lia/util/net/jiperf/JIperfServer.java +++ b/src/lia/util/net/jiperf/JIperfServer.java @@ -16,179 +16,182 @@ import java.util.logging.Logger; /** - * - * This will be kept for history :). + * This will be kept for history :). * The entire package lia.util.net.jiperf is the very first version of FDT. It * started as an Iperf-like test for Java. - * + * * @author ramiro */ public class JIperfServer { - /** Logger used by this class */ - private static final transient Logger logger = Logger.getLogger(JIperfServer.class.getName()); - - ServerSocketChannel ssc; - - ServerSocket ss; - - Selector sel; - - int port; - - ByteBufferPool buffPool; - - ExecutorService executor; - - /** -- SSH Mode fields -- */ - boolean sshMode = false; - - /** - * the Client/Gateway IP that this server restricts connections from if not set, the connections will not be checked against this - */ - String allowedIP = null; - - /** - * the number of connection that the server accept from the {@linkplain allowedIP} - */ - int connectionNo; - - /** - * window size on client side (server should consider this when accepting connections?) - */ - int windowSize; - - class ReaderTask implements Runnable { - - SelectionKey sk; - - ByteBuffer buff; - - ReaderTask(SelectionKey sk) { - this.sk = sk; - // take a free buffer from the pool - buff = buffPool.get(); - } - - private void readData() throws Exception { - buff.clear(); - SocketChannel sc = (SocketChannel) sk.channel(); - int count = -1; - while ((count = sc.read(buff)) > 0) { - // TODO - in the future pass this to a "listener" which will do something useful with this buffer - buff.clear(); - } - - if (count < 0) { - sc.close(); - } else { - sk.interestOps(sk.interestOps() | SelectionKey.OP_READ); - } - - sel.wakeup(); - - }// readData() - - public void run() { - if (sk == null) - return; - try { - readData(); - } catch (Throwable t) { - t.printStackTrace(); - } finally { - // *ALWAYS* return the buffer to the pool whatever happens - buffPool.put(buff); - } - } - }// ReaderTask class - - private void init() throws Exception { - buffPool = ByteBufferPool.getInstance(); - executor = JIperf.getExecutor(); - - ssc = ServerSocketChannel.open(); - ssc.configureBlocking(false); - - ss = ssc.socket(); - ss.bind(new InetSocketAddress(port)); - - sel = Selector.open(); - ssc.register(sel, SelectionKey.OP_ACCEPT); - } - - private void initSSH() throws Exception { - // stdin,stdout are tunneled through SSH and used as control in/out streams - java.io.BufferedReader stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); - try { - System.out.println("ACK1"); - allowedIP = stdin.readLine(); - port = Integer.parseInt(stdin.readLine()); - connectionNo = Integer.parseInt(stdin.readLine()); - windowSize = Integer.parseInt(stdin.readLine()); - System.err.println("Conection parameters received: IP: " + allowedIP + " PORT: " + port + " STREAMS: " + connectionNo + " WSIZE: " + windowSize); - init(); - System.out.println("ACK2"); - } catch (Throwable t) { - System.err.println("Invalid connection parameters" + t.getMessage()); - ssc.close(); - ss.close(); - System.exit(1); - } - /** --SSH mode-- */ - } - - public JIperfServer(final HashMap config) throws Exception { - /** --SSH mode-- */ - /* we start the remote jiperf server */ - if (config.containsKey("-ssh")) { - sshMode = true; - initSSH(); - } else { - port = Integer.parseInt(config.get("-p")); - init(); - } - } - - public void doWork() throws Exception { - for (;;) { - //TODO, stop the server (this loop and the executor) if there are no more connected sockets - while (sel.select() > 0) - ; - Iterator it = sel.selectedKeys().iterator(); - while (it.hasNext()) { - SelectionKey sk = (SelectionKey) it.next(); - - if (sk.isAcceptable()) { - ServerSocketChannel ssc = (ServerSocketChannel) sk.channel(); - SocketChannel sc = ssc.accept(); - if (!sshMode) {// standalone mode - sc.configureBlocking(false); - sc.register(sel, SelectionKey.OP_READ); - } else {// SSH mode - if (allowedIP != null && !allowedIP.equals(sc.socket().getInetAddress().getHostAddress())) { - // just the IP passed on secured SSH control connection is allowed to connect - System.err.println(" [" + allowedIP + "] does not match " + sc.socket().getInetAddress().getHostAddress()); - sc.close(); - } else {// allowed connection - sc.configureBlocking(false); - sc.register(sel, SelectionKey.OP_READ); - if (--connectionNo == 0) { - // stop listening for other connection - this.ssc.keyFor(sel).cancel(); - this.ssc.close(); - } - } - } - } else if (sk.isReadable()) { - sk.interestOps(sk.interestOps() & ~SelectionKey.OP_READ); - executor.execute(new ReaderTask(sk)); - } - - it.remove(); - } - } - - - } + /** + * Logger used by this class + */ + private static final transient Logger logger = Logger.getLogger(JIperfServer.class.getName()); + + ServerSocketChannel ssc; + + ServerSocket ss; + + Selector sel; + + int port; + + ByteBufferPool buffPool; + + ExecutorService executor; + + /** + * -- SSH Mode fields -- + */ + boolean sshMode = false; + + /** + * the Client/Gateway IP that this server restricts connections from if not set, the connections will not be checked against this + */ + String allowedIP = null; + + /** + * the number of connection that the server accept from the allowedIP + */ + int connectionNo; + + /** + * window size on client side (server should consider this when accepting connections?) + */ + int windowSize; + + public JIperfServer(final HashMap config) throws Exception { + /** --SSH mode-- */ + /* we start the remote jiperf server */ + if (config.containsKey("-ssh")) { + sshMode = true; + initSSH(); + } else { + port = Integer.parseInt(config.get("-p")); + init(); + } + } + + private void init() throws Exception { + buffPool = ByteBufferPool.getInstance(); + executor = JIperf.getExecutor(); + + ssc = ServerSocketChannel.open(); + ssc.configureBlocking(false); + + ss = ssc.socket(); + ss.bind(new InetSocketAddress(port)); + + sel = Selector.open(); + ssc.register(sel, SelectionKey.OP_ACCEPT); + } + + private void initSSH() throws Exception { + // stdin,stdout are tunneled through SSH and used as control in/out streams + java.io.BufferedReader stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); + try { + System.out.println("ACK1"); + allowedIP = stdin.readLine(); + port = Integer.parseInt(stdin.readLine()); + connectionNo = Integer.parseInt(stdin.readLine()); + windowSize = Integer.parseInt(stdin.readLine()); + System.err.println("Conection parameters received: IP: " + allowedIP + " PORT: " + port + " STREAMS: " + connectionNo + " WSIZE: " + windowSize); + init(); + System.out.println("ACK2"); + } catch (Throwable t) { + System.err.println("Invalid connection parameters" + t.getMessage()); + ssc.close(); + ss.close(); + System.exit(1); + } + /** --SSH mode-- */ + } + + public void doWork() throws Exception { + for (; ; ) { + //TODO, stop the server (this loop and the executor) if there are no more connected sockets + while (sel.select() > 0) + ; + Iterator it = sel.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey sk = (SelectionKey) it.next(); + + if (sk.isAcceptable()) { + ServerSocketChannel ssc = (ServerSocketChannel) sk.channel(); + SocketChannel sc = ssc.accept(); + if (!sshMode) {// standalone mode + sc.configureBlocking(false); + sc.register(sel, SelectionKey.OP_READ); + } else {// SSH mode + if (allowedIP != null && !allowedIP.equals(sc.socket().getInetAddress().getHostAddress())) { + // just the IP passed on secured SSH control connection is allowed to connect + System.err.println(" [" + allowedIP + "] does not match " + sc.socket().getInetAddress().getHostAddress()); + sc.close(); + } else {// allowed connection + sc.configureBlocking(false); + sc.register(sel, SelectionKey.OP_READ); + if (--connectionNo == 0) { + // stop listening for other connection + this.ssc.keyFor(sel).cancel(); + this.ssc.close(); + } + } + } + } else if (sk.isReadable()) { + sk.interestOps(sk.interestOps() & ~SelectionKey.OP_READ); + executor.execute(new ReaderTask(sk)); + } + + it.remove(); + } + } + + + } + + class ReaderTask implements Runnable { + + SelectionKey sk; + + ByteBuffer buff; + + ReaderTask(SelectionKey sk) { + this.sk = sk; + // take a free buffer from the pool + buff = buffPool.get(); + } + + private void readData() throws Exception { + buff.clear(); + SocketChannel sc = (SocketChannel) sk.channel(); + int count = -1; + while ((count = sc.read(buff)) > 0) { + // TODO - in the future pass this to a "listener" which will do something useful with this buffer + buff.clear(); + } + + if (count < 0) { + sc.close(); + } else { + sk.interestOps(sk.interestOps() | SelectionKey.OP_READ); + } + + sel.wakeup(); + + }// readData() + + public void run() { + if (sk == null) + return; + try { + readData(); + } catch (Throwable t) { + t.printStackTrace(); + } finally { + // *ALWAYS* return the buffer to the pool whatever happens + buffPool.put(buff); + } + } + }// ReaderTask class } diff --git a/src/lia/util/net/jiperf/control/ControlStream.java b/src/lia/util/net/jiperf/control/ControlStream.java index f5d2d6b..70ea34b 100644 --- a/src/lia/util/net/jiperf/control/ControlStream.java +++ b/src/lia/util/net/jiperf/control/ControlStream.java @@ -3,101 +3,98 @@ */ package lia.util.net.jiperf.control; -import java.io.BufferedReader; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; - import lia.util.net.common.LocalHost; +import java.io.*; + /** - * - * This will be kept for history :). + * This will be kept for history :). * The entire package lia.util.net.jiperf is the very first version of FDT. It * started as an Iperf-like test for Java. - * + * * @author ramiro */ public class ControlStream implements StreamConsumer { - // consume stderr lines + // consume stderr lines + + public BufferedReader stdout; + + public PrintWriter stdin; + + public Process proc; - public BufferedReader stdout; + public Thread stderr; - public PrintWriter stdin; + public static boolean isStdinOpen() { + return FileDescriptor.in.valid(); + } - public Process proc; + public static void main(String[] args) throws IOException { + // Execute the command using the specified environment + System.out.println(LocalHost.getPublicIP4()); + ControlStream c = new ControlStream(); + System.out.println(args[1]); + c.startServer(args[0], args[1], args[2]); + c.stdin.println("LINE"); + System.out.println(" WAIT"); + // Parse the stdout of the command + String line; + StringBuffer buff = new StringBuffer(); + while ((line = c.stdout.readLine()) != null) { + buff.append(line).append('\n'); + } + String sStdout = buff.toString(); + // Set the exit code + c.awaitTermination(); + int exitCode = c.proc.exitValue(); + System.out.println("Stdout (" + exitCode + "): " + sStdout); + } - public Thread stderr; + public void consumeLine(String line) { + System.err.println(" [Server] DEBUG:" + line); + // System.exit(1); + } - public void consumeLine(String line) { - System.err.println(" [Server] DEBUG:" + line); - // System.exit(1); - } + public void startServer(String host, String username, String command) throws IOException { + this.proc = Runtime.getRuntime().exec("ssh -l " + username + " " + host + " " + command); + this.stdout = new BufferedReader(new InputStreamReader(proc.getInputStream())); + StreamPumper errorPumper = new StreamPumper(proc.getErrorStream(), null, this); + this.stderr = new Thread(errorPumper); + this.stderr.start(); + this.stdin = new PrintWriter(proc.getOutputStream(), true); + } - public void startServer(String host, String username, String command) throws IOException { - this.proc = Runtime.getRuntime().exec("ssh -l " + username + " " + host + " " + command); - this.stdout = new BufferedReader(new InputStreamReader(proc.getInputStream())); - StreamPumper errorPumper = new StreamPumper(proc.getErrorStream(), null, this); - this.stderr = new Thread(errorPumper); - this.stderr.start(); - this.stdin = new PrintWriter(proc.getOutputStream(), true); - } + public void sendInitCommands(String myIp, int port, int threadsNumber, int windowSize) { + if (myIp == null || myIp.length() == 0) + myIp = LocalHost.getPublicIP4(); + this.stdin.println(myIp); + this.stdin.println(port); + this.stdin.println(threadsNumber); + this.stdin.println(windowSize); + } - public void sendInitCommands(String myIp, int port, int threadsNumber, int windowSize) { - if (myIp == null || myIp.length() == 0) - myIp=LocalHost.getPublicIP4(); - this.stdin.println(myIp); - this.stdin.println(port); - this.stdin.println(threadsNumber); - this.stdin.println(windowSize); - } - // read the ACK from server (blocking the thread) - public void waitAck() throws IOException { - String sAck = this.stdout.readLine(); - if (sAck == null) { - throw new IOException("Invalid ack message"); - } - } - - public void destroy(){ - proc.destroy(); - } + // read the ACK from server (blocking the thread) + public void waitAck() throws IOException { + String sAck = this.stdout.readLine(); + if (sAck == null) { + throw new IOException("Invalid ack message"); + } + } - public void awaitTermination() { - try { - proc.waitFor(); - // cleanup - stderr.join(); - proc.getInputStream().close(); - proc.getOutputStream().close(); - proc.getErrorStream().close(); - } catch (Exception e) { - System.err.println("Thread was interrupted while executing command \"" + "\"." + e); - } - } + public void destroy() { + proc.destroy(); + } - public static boolean isStdinOpen(){ - return FileDescriptor.in.valid(); - } - public static void main(String[] args) throws IOException { - // Execute the command using the specified environment - System.out.println(LocalHost.getPublicIP4()); - ControlStream c = new ControlStream(); - System.out.println(args[1]); - c.startServer(args[0], args[1], args[2]); - c.stdin.println("LINE"); - System.out.println(" WAIT"); - // Parse the stdout of the command - String line; - StringBuffer buff = new StringBuffer(); - while ((line = c.stdout.readLine()) != null) { - buff.append(line).append('\n'); - } - String sStdout = buff.toString(); - // Set the exit code - c.awaitTermination(); - int exitCode = c.proc.exitValue(); - System.out.println("Stdout (" + exitCode + "): " + sStdout); - } + public void awaitTermination() { + try { + proc.waitFor(); + // cleanup + stderr.join(); + proc.getInputStream().close(); + proc.getOutputStream().close(); + proc.getErrorStream().close(); + } catch (Exception e) { + System.err.println("Thread was interrupted while executing command \"" + "\"." + e); + } + } } \ No newline at end of file diff --git a/src/lia/util/net/jiperf/control/StreamConsumer.java b/src/lia/util/net/jiperf/control/StreamConsumer.java index dc09617..e0c3610 100644 --- a/src/lia/util/net/jiperf/control/StreamConsumer.java +++ b/src/lia/util/net/jiperf/control/StreamConsumer.java @@ -4,11 +4,10 @@ package lia.util.net.jiperf.control; /** - * - * This will be kept for history :). + * This will be kept for history :). * The entire package lia.util.net.jiperf is the very first version of FDT. It * started as an Iperf-like test for Java. - * + * * @author ramiro */ public interface StreamConsumer { diff --git a/src/lia/util/net/jiperf/control/StreamPumper.java b/src/lia/util/net/jiperf/control/StreamPumper.java index 53583a4..80f70df 100644 --- a/src/lia/util/net/jiperf/control/StreamPumper.java +++ b/src/lia/util/net/jiperf/control/StreamPumper.java @@ -1,28 +1,23 @@ -/* - * $Id$ - */ -package lia.util.net.jiperf.control; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintWriter; - -/** - * - * This will be kept for history :). - * The entire package lia.util.net.jiperf is the very first version of FDT. It - * started as an Iperf-like test for Java. - * - * @author ramiro - */ +/* + * $Id$ + */ +package lia.util.net.jiperf.control; + +import java.io.*; + +/** + * This will be kept for history :). + * The entire package lia.util.net.jiperf is the very first version of FDT. It + * started as an Iperf-like test for Java. + * + * @author ramiro + */ public class StreamPumper implements Runnable { + private static final int SIZE = 1024; private BufferedReader in; private StreamConsumer consumer = null; private PrintWriter out = new PrintWriter(System.out); - private static final int SIZE = 1024; public StreamPumper(InputStream in, PrintWriter writer) { this(in); @@ -52,7 +47,7 @@ public void run() { consumeLine(s); if (out != null) { out.println(s); - out.println(""); + out.println(""); out.flush(); } diff --git a/src/lia/util/net/jiperf/test/FDTNetPerf.java b/src/lia/util/net/jiperf/test/FDTNetPerf.java index 3091ec9..e08e0cc 100644 --- a/src/lia/util/net/jiperf/test/FDTNetPerf.java +++ b/src/lia/util/net/jiperf/test/FDTNetPerf.java @@ -3,6 +3,9 @@ */ package lia.util.net.jiperf.test; +import lia.util.net.common.Config; +import lia.util.net.common.Utils; + import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -16,166 +19,161 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import lia.util.net.common.Config; -import lia.util.net.common.Utils; - /** - * * The simplest JIperf blocking Client/Server ... used only for testing - * + * * @author ramiro - * */ public class FDTNetPerf { + private static final ExecutorService execThPool = Utils.getStandardExecService("ExecService", 3, 100, Thread.MAX_PRIORITY); + private static final ScheduledThreadPoolExecutor monitorThPool = Utils.getSchedExecService("MonitorService", 1, Thread.MIN_PRIORITY); private static int port = 54320; private static AtomicLong totalBytes = new AtomicLong(0); - private static final ExecutorService execThPool = Utils.getStandardExecService("ExecService", 3, 100, Thread.MAX_PRIORITY); - private static final ScheduledThreadPoolExecutor monitorThPool = Utils.getSchedExecService("MonitorService", 1, Thread.MIN_PRIORITY); - private static int byteBufferSize = 512 * 1024; private static int buffCount = 1; - + private FDTNetPerf(Map argsMap) throws Exception { monitorThPool.scheduleWithFixedDelay(new FDTNetPerfMonitorTask(), 1, 2, TimeUnit.SECONDS); Object host = argsMap.get("-c"); - if(host != null) { + if (host != null) { execThPool.execute(new FDTNetPerfClient(host.toString(), port)); } else { execThPool.execute(new FDTNetPerfServer()); } } - + + /** + * @param args + */ + public static void main(String[] args) { + Map argsMap = Utils.parseArguments(args, Config.SINGLE_CMDLINE_ARGS); + + byteBufferSize = Utils.getIntValue(argsMap, "-bs", byteBufferSize); + buffCount = Utils.getIntValue(argsMap, "-bn", buffCount); + port = Utils.getIntValue(argsMap, "-p", port); + + try { + new FDTNetPerf(argsMap); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + + for (; ; ) { + try { + Thread.sleep(10000000); + } catch (Throwable ignore) { + } + } + + } + private static class FDTNetPerfServer implements Runnable { - + private ServerSocketChannel ssc; - + FDTNetPerfServer() throws Exception { ssc = ServerSocketChannel.open(); ssc.configureBlocking(true); ssc.socket().bind(new InetSocketAddress(port)); } - + public void run() { try { SocketChannel sc = ssc.accept(); sc.configureBlocking(true); execThPool.execute(new FDTNetPerfClient(sc)); - }catch(Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } } } - + private static class FDTNetPerfClient implements Runnable { SocketChannel sc; -// private ByteBuffer buff = ByteBuffer.allocateDirect(8 * 1024 * 1024); + // private ByteBuffer buff = ByteBuffer.allocateDirect(8 * 1024 * 1024); // private ByteBuffer buff = ByteBuffer.allocateDirect(512 * 1024); private ByteBuffer[] buffs; - + private boolean shouldWrite; - + private FDTNetPerfClient() throws Exception { int bCount = 0; ArrayList buffsList = new ArrayList(buffCount); try { - for(int i=0; i 0) { + if (bCount > 0) { buffs = buffsList.toArray(new ByteBuffer[buffsList.size()]); - System.out.println("buffs.size() = " + (buffs.length * byteBufferSize)/1024 + " KB"); + System.out.println("buffs.size() = " + (buffs.length * byteBufferSize) / 1024 + " KB"); } else { throw new Exception("Cannot instantiate the buff pool"); } } - + FDTNetPerfClient(String host, int port) throws Exception { this(); - this.sc = SocketChannel.open(); + this.sc = SocketChannel.open(); this.sc.configureBlocking(true); this.sc.socket().connect(new InetSocketAddress(InetAddress.getByName(host), port)); shouldWrite = true; } - + FDTNetPerfClient(SocketChannel sc) throws Exception { this(); this.sc = sc; shouldWrite = false; } - + public void run() { try { - for(;;) { - if(shouldWrite) { - for(ByteBuffer buff: buffs) { + for (; ; ) { + if (shouldWrite) { + for (ByteBuffer buff : buffs) { buff.position(0); buff.limit(buff.capacity()); } totalBytes.addAndGet(sc.write(buffs)); } else { - for(ByteBuffer buff: buffs) { + for (ByteBuffer buff : buffs) { buff.clear(); } totalBytes.addAndGet(sc.read(buffs)); } } - } catch(Throwable t) { + } catch (Throwable t) { t.printStackTrace(); } } } - + private static class FDTNetPerfMonitorTask implements Runnable { - + private long lastCount; private long lastRun; - + public void run() { - + final long currentCount = totalBytes.get(); final long now = System.currentTimeMillis(); - - if(lastRun > 0) { + + if (lastRun > 0) { long diff = (currentCount - lastCount); - double speed = diff*8D/(now - lastRun); - System.out.println(new Date() + " CurentSpeed: " + speed/1000 + " Mb/s"); + double speed = diff * 8D / (now - lastRun); + System.out.println(new Date() + " CurentSpeed: " + speed / 1000 + " Mb/s"); } - + lastRun = now; lastCount = currentCount; } } - - /** - * @param args - */ - public static void main(String[] args) { - Map argsMap = Utils.parseArguments(args, Config.SINGLE_CMDLINE_ARGS); - - byteBufferSize = Utils.getIntValue(argsMap, "-bs", byteBufferSize); - buffCount = Utils.getIntValue(argsMap, "-bn", buffCount); - port = Utils.getIntValue(argsMap, "-p", port); - - try { - new FDTNetPerf(argsMap); - }catch(Throwable t) { - t.printStackTrace(); - System.exit(1); - } - - for(;;) { - try { - Thread.sleep(10000000); - }catch(Throwable ignore) {} - } - - } } diff --git a/src/org/apache/commons/cli/AlreadySelectedException.java b/src/org/apache/commons/cli/AlreadySelectedException.java index 6dcd3cb..7236220 100644 --- a/src/org/apache/commons/cli/AlreadySelectedException.java +++ b/src/org/apache/commons/cli/AlreadySelectedException.java @@ -60,7 +60,7 @@ */ package org.apache.commons.cli; -/** +/** *

    Thrown when more than one option in an option group * has been provided.

    * @@ -69,13 +69,13 @@ */ public class AlreadySelectedException extends ParseException { - /** - *

    Construct a new AlreadySelectedException + /** + *

    Construct a new AlreadySelectedException * with the specified detail message.

    * * @param message the detail message */ - public AlreadySelectedException( String message ) { - super( message ); + public AlreadySelectedException(String message) { + super(message); } } diff --git a/src/org/apache/commons/cli/BasicParser.java b/src/org/apache/commons/cli/BasicParser.java index 8936dfd..c6c9cfa 100644 --- a/src/org/apache/commons/cli/BasicParser.java +++ b/src/org/apache/commons/cli/BasicParser.java @@ -62,7 +62,7 @@ /** * The class BasicParser provides a very simple implementation of - * the {@link Parser#flatten(Options,String[],boolean) flatten} method. + * the {@link Parser#flatten(Options, String[], boolean) flatten} method. * * @author John Keyes (john at integralsource.com) * @see Parser @@ -71,21 +71,20 @@ public class BasicParser extends Parser { /** *

    A simple implementation of {@link Parser}'s abstract - * {@link Parser#flatten(Options,String[],boolean) flatten} method.

    - * + * {@link Parser#flatten(Options, String[], boolean) flatten} method.

    + *

    *

    Note: options and stopAtNonOption * are not used in this flatten method.

    * - * @param options The command line {@link Options} - * @param arguments The command line arguments to be parsed + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed * @param stopAtNonOption Specifies whether to stop flattening - * when an non option is found. + * when an non option is found. * @return The arguments String array. */ - protected String[] flatten( Options options, - String[] arguments, - boolean stopAtNonOption ) - { + protected String[] flatten(Options options, + String[] arguments, + boolean stopAtNonOption) { // just echo the arguments return arguments; } diff --git a/src/org/apache/commons/cli/CommandLine.java b/src/org/apache/commons/cli/CommandLine.java index bf8634c..9a3c092 100644 --- a/src/org/apache/commons/cli/CommandLine.java +++ b/src/org/apache/commons/cli/CommandLine.java @@ -60,21 +60,16 @@ */ package org.apache.commons.cli; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** +import java.util.*; + +/** *

    Represents list of arguments parsed against * a {@link Options} descriptor.

    - * + *

    *

    It allows querying of a boolean {@link #hasOption(String opt)}, * in addition to retrieving the {@link #getOptionValue(String opt)} * for options requiring arguments.

    - * + *

    *

    Additionally, any left-over or unrecognized arguments, * are available for further processing.

    * @@ -83,17 +78,25 @@ * @author John Keyes (john at integralsource.com) */ public class CommandLine { - - /** the unrecognised options/arguments */ - private List args = new LinkedList(); - /** the processed options */ + /** + * the unrecognised options/arguments + */ + private List args = new LinkedList(); + + /** + * the processed options + */ private Map options = new HashMap(); - /** Map of unique options for ease to get complete list of options */ + /** + * Map of unique options for ease to get complete list of options + */ private Map hashcodeMap = new HashMap(); - /** the processed options */ + /** + * the processed options + */ private Option[] optionsArray; /** @@ -101,25 +104,25 @@ public class CommandLine { */ CommandLine() { } - - /** + + /** *

    Query to see if an option has been set.

    * * @param opt Short name of the option * @return true if set, false if not */ public boolean hasOption(String opt) { - return options.containsKey( opt ); + return options.containsKey(opt); } - /** + /** *

    Query to see if an option has been set.

    * * @param opt character name of the option * @return true if set, false if not */ - public boolean hasOption( char opt ) { - return hasOption( String.valueOf( opt ) ); + public boolean hasOption(char opt) { + return hasOption(String.valueOf(opt)); } /** @@ -128,10 +131,10 @@ public boolean hasOption( char opt ) { * @param opt the name of the option * @return the type of this Option */ - public Object getOptionObject( String opt ) { - String res = getOptionValue( opt ); - - Object type = ((Option)((List)options.get(opt)).iterator().next()).getType(); + public Object getOptionObject(String opt) { + String res = getOptionValue(opt); + + Object type = ((Option) ((List) options.get(opt)).iterator().next()).getType(); return res == null ? null : TypeHandler.createValue(res, type); } @@ -141,103 +144,103 @@ public Object getOptionObject( String opt ) { * @param opt the name of the option * @return the type of opt */ - public Object getOptionObject( char opt ) { - return getOptionObject( String.valueOf( opt ) ); + public Object getOptionObject(char opt) { + return getOptionObject(String.valueOf(opt)); } - /** + /** *

    Retrieve the argument, if any, of this option.

    * * @param opt the name of the option * @return Value of the argument if option is set, and has an argument, * otherwise null. */ - public String getOptionValue( String opt ) { + public String getOptionValue(String opt) { String[] values = getOptionValues(opt); return (values == null) ? null : values[0]; } - /** + /** *

    Retrieve the argument, if any, of this option.

    * * @param opt the character name of the option * @return Value of the argument if option is set, and has an argument, * otherwise null. */ - public String getOptionValue( char opt ) { - return getOptionValue( String.valueOf( opt ) ); + public String getOptionValue(char opt) { + return getOptionValue(String.valueOf(opt)); } - /** + /** *

    Retrieves the array of values, if any, of an option.

    * * @param opt string name of the option * @return Values of the argument if option is set, and has an argument, * otherwise null. */ - public String[] getOptionValues( String opt ) { + public String[] getOptionValues(String opt) { List values = new java.util.ArrayList(); - if( options.containsKey( opt ) ) { - List opts = (List)options.get( opt ); + if (options.containsKey(opt)) { + List opts = (List) options.get(opt); Iterator iter = opts.iterator(); - while( iter.hasNext() ) { - Option optt = (Option)iter.next(); - values.addAll( optt.getValuesList() ); + while (iter.hasNext()) { + Option optt = (Option) iter.next(); + values.addAll(optt.getValuesList()); } } - return (values.size() == 0) ? null : (String[])values.toArray(new String[]{}); + return (values.size() == 0) ? null : (String[]) values.toArray(new String[]{}); } - /** + /** *

    Retrieves the array of values, if any, of an option.

    * * @param opt character name of the option * @return Values of the argument if option is set, and has an argument, * otherwise null. */ - public String[] getOptionValues( char opt ) { - return getOptionValues( String.valueOf( opt ) ); + public String[] getOptionValues(char opt) { + return getOptionValues(String.valueOf(opt)); } - - /** + + /** *

    Retrieve the argument, if any, of an option.

    * - * @param opt name of the option + * @param opt name of the option * @param defaultValue is the default value to be returned if the option is not specified * @return Value of the argument if option is set, and has an argument, * otherwise defaultValue. */ - public String getOptionValue( String opt, String defaultValue ) { - String answer = getOptionValue( opt ); - return ( answer != null ) ? answer : defaultValue; + public String getOptionValue(String opt, String defaultValue) { + String answer = getOptionValue(opt); + return (answer != null) ? answer : defaultValue; } - - /** + + /** *

    Retrieve the argument, if any, of an option.

    * - * @param opt character name of the option + * @param opt character name of the option * @param defaultValue is the default value to be returned if the option is not specified * @return Value of the argument if option is set, and has an argument, * otherwise defaultValue. */ - public String getOptionValue( char opt, String defaultValue ) { - return getOptionValue( String.valueOf( opt ), defaultValue ); + public String getOptionValue(char opt, String defaultValue) { + return getOptionValue(String.valueOf(opt), defaultValue); } - /** + /** *

    Retrieve any left-over non-recognized options and arguments

    * * @return remaining items passed in but not parsed as an array */ public String[] getArgs() { - String[] answer = new String[ args.size() ]; - args.toArray( answer ); + String[] answer = new String[args.size()]; + args.toArray(answer); return answer; } - - /** + + /** *

    Retrieve any left-over non-recognized options and arguments

    * * @return remaining items passed in but not parsed as a List. @@ -245,8 +248,8 @@ public String[] getArgs() { public List getArgList() { return args; } - - /** + + /** * jkeyes * - commented out until it is implemented properly *

    Dump state, suitable for debugging.

    @@ -273,39 +276,38 @@ public String toString() { * @param arg the unrecognised option/argument. */ void addArg(String arg) { - args.add( arg ); + args.add(arg); } - + /** - *

    Add an option to the command line. The values of + *

    Add an option to the command line. The values of * the option are stored.

    * * @param opt the processed option */ - void addOption( Option opt ) { - hashcodeMap.put( new Integer( opt.hashCode() ), opt ); + void addOption(Option opt) { + hashcodeMap.put(new Integer(opt.hashCode()), opt); String key = opt.getOpt(); - if( " ".equals(key) ) { + if (" ".equals(key)) { key = opt.getLongOpt(); } - if( options.get( key ) != null ) { - ((java.util.List)options.get( key )).add( opt ); - } - else { - options.put( key, new java.util.ArrayList() ); - ((java.util.List)options.get( key ) ).add( opt ); + if (options.get(key) != null) { + ((java.util.List) options.get(key)).add(opt); + } else { + options.put(key, new java.util.ArrayList()); + ((java.util.List) options.get(key)).add(opt); } } /** *

    Returns an iterator over the Option members of CommandLine.

    * - * @return an Iterator over the processed {@link Option} + * @return an Iterator over the processed {@link Option} * members of this {@link CommandLine} */ - public Iterator iterator( ) { + public Iterator iterator() { return hashcodeMap.values().iterator(); } @@ -314,14 +316,14 @@ public Iterator iterator( ) { * * @return an array of the processed {@link Option}s. */ - public Option[] getOptions( ) { + public Option[] getOptions() { Collection processed = hashcodeMap.values(); // reinitialise array - optionsArray = new Option[ processed.size() ]; + optionsArray = new Option[processed.size()]; // return the array - return (Option[]) processed.toArray( optionsArray ); + return (Option[]) processed.toArray(optionsArray); } } diff --git a/src/org/apache/commons/cli/CommandLineParser.java b/src/org/apache/commons/cli/CommandLineParser.java index e73de22..e2aaba0 100644 --- a/src/org/apache/commons/cli/CommandLineParser.java +++ b/src/org/apache/commons/cli/CommandLineParser.java @@ -61,37 +61,37 @@ package org.apache.commons.cli; /** - * A class that implements the CommandLineParser interface + * A class that implements the CommandLineParser interface * can parse a String array according to the {@link Options} specified * and return a {@link CommandLine}. * * @author John Keyes (john at integralsource.com) */ public interface CommandLineParser { - + /** * Parse the arguments according to the specified options. * - * @param options the specified Options + * @param options the specified Options * @param arguments the command line arguments * @return the list of atomic option and value tokens * @throws ParseException if there are any problems encountered - * while parsing the command line tokens. + * while parsing the command line tokens. */ - public CommandLine parse( Options options, String[] arguments ) - throws ParseException; + public CommandLine parse(Options options, String[] arguments) + throws ParseException; /** * Parse the arguments according to the specified options. * - * @param options the specified Options - * @param arguments the command line arguments + * @param options the specified Options + * @param arguments the command line arguments * @param stopAtNonOption specifies whether to continue parsing the - * arguments if a non option is encountered. + * arguments if a non option is encountered. * @return the list of atomic option and value tokens * @throws ParseException if there are any problems encountered - * while parsing the command line tokens. + * while parsing the command line tokens. */ - public CommandLine parse( Options options, String[] arguments, boolean stopAtNonOption ) - throws ParseException; + public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) + throws ParseException; } \ No newline at end of file diff --git a/src/org/apache/commons/cli/GnuParser.java b/src/org/apache/commons/cli/GnuParser.java index 18a42e1..9f537dc 100644 --- a/src/org/apache/commons/cli/GnuParser.java +++ b/src/org/apache/commons/cli/GnuParser.java @@ -63,16 +63,18 @@ import java.util.ArrayList; /** - * The class GnuParser provides an implementation of the - * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * The class GnuParser provides an implementation of the + * {@link Parser#flatten(Options, String[], boolean) flatten} method. * * @author John Keyes (john at integralsource.com) - * @see Parser * @version $Revision: 1.10 $ + * @see Parser */ public class GnuParser extends Parser { - /** holder for flattened tokens */ + /** + * holder for flattened tokens + */ private ArrayList tokens = new ArrayList(); /** @@ -86,99 +88,85 @@ private void init() { /** *

    This flatten method does so using the following rules: *

      - *
    1. If an {@link Option} exists for the first character of - * the arguments entry AND an {@link Option} - * does not exist for the whole argument then - * add the first character as an option to the processed tokens - * list e.g. "-D" and add the rest of the entry to the also.
    2. - *
    3. Otherwise just add the token to the processed tokens list. - *
    4. + *
    5. If an {@link Option} exists for the first character of + * the arguments entry AND an {@link Option} + * does not exist for the whole argument then + * add the first character as an option to the processed tokens + * list e.g. "-D" and add the rest of the entry to the also.
    6. + *
    7. Otherwise just add the token to the processed tokens list. + *
    8. *
    *

    */ - protected String[] flatten( Options options, - String[] arguments, - boolean stopAtNonOption ) - { + protected String[] flatten(Options options, + String[] arguments, + boolean stopAtNonOption) { init(); boolean eatTheRest = false; Option currentOption = null; - for( int i = 0; i < arguments.length; i++ ) { - if( "--".equals( arguments[i] ) ) { + for (int i = 0; i < arguments.length; i++) { + if ("--".equals(arguments[i])) { eatTheRest = true; - tokens.add( "--" ); - } - else if ( "-".equals( arguments[i] ) ) { - tokens.add( "-" ); - } - else if( arguments[i].startsWith( "-" ) ) { - Option option = options.getOption( arguments[i] ); + tokens.add("--"); + } else if ("-".equals(arguments[i])) { + tokens.add("-"); + } else if (arguments[i].startsWith("-")) { + Option option = options.getOption(arguments[i]); // this is not an Option - if( option == null ) { + if (option == null) { // handle special properties Option - Option specialOption = options.getOption( arguments[i].substring(0,2) ); - if( specialOption != null ) { - tokens.add( arguments[i].substring(0,2) ); - tokens.add( arguments[i].substring(2) ); - } - else if( stopAtNonOption ) { + Option specialOption = options.getOption(arguments[i].substring(0, 2)); + if (specialOption != null) { + tokens.add(arguments[i].substring(0, 2)); + tokens.add(arguments[i].substring(2)); + } else if (stopAtNonOption) { eatTheRest = true; - tokens.add( arguments[i] ); - } - else { - tokens.add( arguments[i] ); + tokens.add(arguments[i]); + } else { + tokens.add(arguments[i]); } - } - else { + } else { currentOption = option; // special option - Option specialOption = options.getOption( arguments[i].substring(0,2) ); - if( specialOption != null && option == null ) { - tokens.add( arguments[i].substring(0,2) ); - tokens.add( arguments[i].substring(2) ); - } - else if( currentOption != null && currentOption.hasArg() ) { - if( currentOption.hasArg() ) { - tokens.add( arguments[i] ); - currentOption= null; - } - else if ( currentOption.hasArgs() ) { - tokens.add( arguments[i] ); - } - else if ( stopAtNonOption ) { + Option specialOption = options.getOption(arguments[i].substring(0, 2)); + if (specialOption != null && option == null) { + tokens.add(arguments[i].substring(0, 2)); + tokens.add(arguments[i].substring(2)); + } else if (currentOption != null && currentOption.hasArg()) { + if (currentOption.hasArg()) { + tokens.add(arguments[i]); + currentOption = null; + } else if (currentOption.hasArgs()) { + tokens.add(arguments[i]); + } else if (stopAtNonOption) { eatTheRest = true; - tokens.add( "--" ); - tokens.add( arguments[i] ); + tokens.add("--"); + tokens.add(arguments[i]); + } else { + tokens.add(arguments[i]); } - else { - tokens.add( arguments[i] ); - } - } - else if (currentOption != null ) { - tokens.add( arguments[i] ); - } - else if ( stopAtNonOption ) { + } else if (currentOption != null) { + tokens.add(arguments[i]); + } else if (stopAtNonOption) { eatTheRest = true; - tokens.add( "--" ); - tokens.add( arguments[i] ); - } - else { - tokens.add( arguments[i] ); + tokens.add("--"); + tokens.add(arguments[i]); + } else { + tokens.add(arguments[i]); } } - } - else { - tokens.add( arguments[i] ); + } else { + tokens.add(arguments[i]); } - if( eatTheRest ) { - for( i++; i < arguments.length; i++ ) { - tokens.add( arguments[i] ); + if (eatTheRest) { + for (i++; i < arguments.length; i++) { + tokens.add(arguments[i]); } } } - return (String[])tokens.toArray( new String[] {} ); + return (String[]) tokens.toArray(new String[]{}); } } \ No newline at end of file diff --git a/src/org/apache/commons/cli/HelpFormatter.java b/src/org/apache/commons/cli/HelpFormatter.java index 1cffbf5..10906cc 100644 --- a/src/org/apache/commons/cli/HelpFormatter.java +++ b/src/org/apache/commons/cli/HelpFormatter.java @@ -62,124 +62,111 @@ package org.apache.commons.cli; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -/** +import java.util.*; + +/** * A formatter of help messages for the current command line options * * @author Slawek Zachcial * @author John Keyes (john at integralsource.com) **/ -public class HelpFormatter -{ - // --------------------------------------------------------------- Constants - - public static final int DEFAULT_WIDTH = 74; - public static final int DEFAULT_LEFT_PAD = 1; - public static final int DEFAULT_DESC_PAD = 3; - public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; - public static final String DEFAULT_OPT_PREFIX = "-"; - public static final String DEFAULT_LONG_OPT_PREFIX = "--"; - public static final String DEFAULT_ARG_NAME = "arg"; - - // ------------------------------------------------------------------ Static - - // -------------------------------------------------------------- Attributes - - public int defaultWidth; - public int defaultLeftPad; - public int defaultDescPad; - public String defaultSyntaxPrefix; - public String defaultNewLine; - public String defaultOptPrefix; - public String defaultLongOptPrefix; - public String defaultArgName; - - // ------------------------------------------------------------ Constructors - public HelpFormatter() - { - defaultWidth = DEFAULT_WIDTH; - defaultLeftPad = DEFAULT_LEFT_PAD; - defaultDescPad = DEFAULT_DESC_PAD; - defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; - defaultNewLine = System.getProperty("line.separator"); - defaultOptPrefix = DEFAULT_OPT_PREFIX; - defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; - defaultArgName = DEFAULT_ARG_NAME; - } - - // ------------------------------------------------------------------ Public - - public void printHelp( String cmdLineSyntax, - Options options ) - { - printHelp( defaultWidth, cmdLineSyntax, null, options, null, false ); - } - - public void printHelp( String cmdLineSyntax, +public class HelpFormatter { + // --------------------------------------------------------------- Constants + + public static final int DEFAULT_WIDTH = 74; + public static final int DEFAULT_LEFT_PAD = 1; + public static final int DEFAULT_DESC_PAD = 3; + public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; + public static final String DEFAULT_OPT_PREFIX = "-"; + public static final String DEFAULT_LONG_OPT_PREFIX = "--"; + public static final String DEFAULT_ARG_NAME = "arg"; + + // ------------------------------------------------------------------ Static + + // -------------------------------------------------------------- Attributes + + public int defaultWidth; + public int defaultLeftPad; + public int defaultDescPad; + public String defaultSyntaxPrefix; + public String defaultNewLine; + public String defaultOptPrefix; + public String defaultLongOptPrefix; + public String defaultArgName; + + // ------------------------------------------------------------ Constructors + public HelpFormatter() { + defaultWidth = DEFAULT_WIDTH; + defaultLeftPad = DEFAULT_LEFT_PAD; + defaultDescPad = DEFAULT_DESC_PAD; + defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; + defaultNewLine = System.getProperty("line.separator"); + defaultOptPrefix = DEFAULT_OPT_PREFIX; + defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; + defaultArgName = DEFAULT_ARG_NAME; + } + + // ------------------------------------------------------------------ Public + + public void printHelp(String cmdLineSyntax, + Options options) { + printHelp(defaultWidth, cmdLineSyntax, null, options, null, false); + } + + public void printHelp(String cmdLineSyntax, Options options, - boolean autoUsage ) - { - printHelp( defaultWidth, cmdLineSyntax, null, options, null, autoUsage ); - } + boolean autoUsage) { + printHelp(defaultWidth, cmdLineSyntax, null, options, null, autoUsage); + } - public void printHelp( String cmdLineSyntax, + public void printHelp(String cmdLineSyntax, String header, Options options, - String footer ) - { - printHelp( cmdLineSyntax, header, options, footer, false ); - } + String footer) { + printHelp(cmdLineSyntax, header, options, footer, false); + } - public void printHelp( String cmdLineSyntax, + public void printHelp(String cmdLineSyntax, String header, Options options, String footer, - boolean autoUsage ) - { - printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage ); - } - - public void printHelp( int width, + boolean autoUsage) { + printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage); + } + + public void printHelp(int width, String cmdLineSyntax, String header, Options options, - String footer ) - { - printHelp( width, cmdLineSyntax, header, options, footer, false ); - } + String footer) { + printHelp(width, cmdLineSyntax, header, options, footer, false); + } - public void printHelp( int width, + public void printHelp(int width, String cmdLineSyntax, String header, Options options, String footer, - boolean autoUsage ) - { - PrintWriter pw = new PrintWriter(System.out); - printHelp( pw, width, cmdLineSyntax, header, - options, defaultLeftPad, defaultDescPad, footer, autoUsage ); - pw.flush(); - } - public void printHelp( PrintWriter pw, + boolean autoUsage) { + PrintWriter pw = new PrintWriter(System.out); + printHelp(pw, width, cmdLineSyntax, header, + options, defaultLeftPad, defaultDescPad, footer, autoUsage); + pw.flush(); + } + + public void printHelp(PrintWriter pw, int width, String cmdLineSyntax, String header, Options options, int leftPad, int descPad, - String footer ) - throws IllegalArgumentException - { - printHelp( pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false ); - } + String footer) + throws IllegalArgumentException { + printHelp(pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false); + } - public void printHelp( PrintWriter pw, + public void printHelp(PrintWriter pw, int width, String cmdLineSyntax, String header, @@ -187,351 +174,304 @@ public void printHelp( PrintWriter pw, int leftPad, int descPad, String footer, - boolean autoUsage ) - throws IllegalArgumentException - { - if ( cmdLineSyntax == null || cmdLineSyntax.length() == 0 ) - { - throw new IllegalArgumentException("cmdLineSyntax not provided"); - } - - if ( autoUsage ) { - printUsage( pw, width, cmdLineSyntax, options ); - } - else { - printUsage( pw, width, cmdLineSyntax ); - } - - if ( header != null && header.trim().length() > 0 ) - { - printWrapped( pw, width, header ); - } - printOptions( pw, width, options, leftPad, descPad ); - if ( footer != null && footer.trim().length() > 0 ) - { - printWrapped( pw, width, footer ); - } - } - - /** - *

    Prints the usage statement for the specified application.

    - * - * @param pw The PrintWriter to print the usage statement - * @param width ?? - * @param appName The application name - * @param options The command line Options - * - */ - public void printUsage( PrintWriter pw, int width, String app, Options options ) - { - // initialise the string buffer - StringBuffer buff = new StringBuffer( defaultSyntaxPrefix ).append( app ).append( " " ); - - // create a list for processed option groups - ArrayList list = new ArrayList(); - - // temp variable - Option option; - - // iterate over the options - for ( Iterator i = options.getOptions().iterator(); i.hasNext(); ) - { - // get the next Option - option = (Option) i.next(); - - // check if the option is part of an OptionGroup - OptionGroup group = options.getOptionGroup( option ); - - // if the option is part of a group and the group has not already - // been processed - if( group != null && !list.contains(group)) { - - // add the group to the processed list - list.add( group ); - - // get the names of the options from the OptionGroup - Collection names = group.getNames(); - - buff.append( "[" ); - - // for each option in the OptionGroup - for( Iterator iter = names.iterator(); iter.hasNext(); ) { - buff.append( iter.next() ); - if( iter.hasNext() ) { - buff.append( " | " ); - } - } - buff.append( "]" ); - } - // if the Option is not part of an OptionGroup - else { - // if the Option is not a required option - if( !option.isRequired() ) { - buff.append( "[" ); - } - - if( !" ".equals( option.getOpt() ) ) { - buff.append( "-" ).append( option.getOpt() ); - } - else { - buff.append( "--" ).append( option.getLongOpt() ); - } - - if( option.hasArg() ){ - buff.append( " " ); - } - - // if the Option has a value - if( option.hasArg() ) { - buff.append( option.getArgName() ); - } - - // if the Option is not a required option - if( !option.isRequired() ) { - buff.append( "]" ); - } - buff.append( " " ); - } - } - - // call printWrapped - printWrapped( pw, width, buff.toString().indexOf(' ')+1, - buff.toString() ); - } - - public void printUsage( PrintWriter pw, int width, String cmdLineSyntax ) - { - int argPos = cmdLineSyntax.indexOf(' ') + 1; - printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, - defaultSyntaxPrefix + cmdLineSyntax); - } - - public void printOptions( PrintWriter pw, int width, Options options, int leftPad, int descPad ) - { - StringBuffer sb = new StringBuffer(); - renderOptions(sb, width, options, leftPad, descPad); - pw.println(sb.toString()); - } - - public void printWrapped( PrintWriter pw, int width, String text ) - { - printWrapped(pw, width, 0, text); - } - - public void printWrapped( PrintWriter pw, int width, int nextLineTabStop, String text ) - { - StringBuffer sb = new StringBuffer(text.length()); - renderWrappedText(sb, width, nextLineTabStop, text); - pw.println(sb.toString()); - } - - // --------------------------------------------------------------- Protected - - protected StringBuffer renderOptions( StringBuffer sb, + boolean autoUsage) + throws IllegalArgumentException { + if (cmdLineSyntax == null || cmdLineSyntax.length() == 0) { + throw new IllegalArgumentException("cmdLineSyntax not provided"); + } + + if (autoUsage) { + printUsage(pw, width, cmdLineSyntax, options); + } else { + printUsage(pw, width, cmdLineSyntax); + } + + if (header != null && header.trim().length() > 0) { + printWrapped(pw, width, header); + } + printOptions(pw, width, options, leftPad, descPad); + if (footer != null && footer.trim().length() > 0) { + printWrapped(pw, width, footer); + } + } + + /** + *

    Prints the usage statement for the specified application.

    + * + * @param pw The PrintWriter to print the usage statement + * @param width ?? + * @param app The application name + * @param options The command line Options + */ + public void printUsage(PrintWriter pw, int width, String app, Options options) { + // initialise the string buffer + StringBuffer buff = new StringBuffer(defaultSyntaxPrefix).append(app).append(" "); + + // create a list for processed option groups + ArrayList list = new ArrayList(); + + // temp variable + Option option; + + // iterate over the options + for (Iterator i = options.getOptions().iterator(); i.hasNext(); ) { + // get the next Option + option = (Option) i.next(); + + // check if the option is part of an OptionGroup + OptionGroup group = options.getOptionGroup(option); + + // if the option is part of a group and the group has not already + // been processed + if (group != null && !list.contains(group)) { + + // add the group to the processed list + list.add(group); + + // get the names of the options from the OptionGroup + Collection names = group.getNames(); + + buff.append("["); + + // for each option in the OptionGroup + for (Iterator iter = names.iterator(); iter.hasNext(); ) { + buff.append(iter.next()); + if (iter.hasNext()) { + buff.append(" | "); + } + } + buff.append("]"); + } + // if the Option is not part of an OptionGroup + else { + // if the Option is not a required option + if (!option.isRequired()) { + buff.append("["); + } + + if (!" ".equals(option.getOpt())) { + buff.append("-").append(option.getOpt()); + } else { + buff.append("--").append(option.getLongOpt()); + } + + if (option.hasArg()) { + buff.append(" "); + } + + // if the Option has a value + if (option.hasArg()) { + buff.append(option.getArgName()); + } + + // if the Option is not a required option + if (!option.isRequired()) { + buff.append("]"); + } + buff.append(" "); + } + } + + // call printWrapped + printWrapped(pw, width, buff.toString().indexOf(' ') + 1, + buff.toString()); + } + + public void printUsage(PrintWriter pw, int width, String cmdLineSyntax) { + int argPos = cmdLineSyntax.indexOf(' ') + 1; + printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, + defaultSyntaxPrefix + cmdLineSyntax); + } + + public void printOptions(PrintWriter pw, int width, Options options, int leftPad, int descPad) { + StringBuffer sb = new StringBuffer(); + renderOptions(sb, width, options, leftPad, descPad); + pw.println(sb.toString()); + } + + public void printWrapped(PrintWriter pw, int width, String text) { + printWrapped(pw, width, 0, text); + } + + public void printWrapped(PrintWriter pw, int width, int nextLineTabStop, String text) { + StringBuffer sb = new StringBuffer(text.length()); + renderWrappedText(sb, width, nextLineTabStop, text); + pw.println(sb.toString()); + } + + // --------------------------------------------------------------- Protected + + protected StringBuffer renderOptions(StringBuffer sb, int width, Options options, int leftPad, - int descPad ) - { - final String lpad = createPadding(leftPad); - final String dpad = createPadding(descPad); - - //first create list containing only -a,--aaa where -a is opt and --aaa is - //long opt; in parallel look for the longest opt string - //this list will be then used to sort options ascending - int max = 0; - StringBuffer optBuf; - List prefixList = new ArrayList(); - Option option; - List optList = options.helpOptions(); - Collections.sort( optList, new StringBufferComparator() ); - for ( Iterator i = optList.iterator(); i.hasNext(); ) - { - option = (Option) i.next(); - optBuf = new StringBuffer(8); - - if (option.getOpt().equals(" ")) - { - optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt()); - } - else - { - optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt()); - if ( option.hasLongOpt() ) - { - optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt()); - } - - } - - if( option.hasArg() ) { - if( option.hasArgName() ) { - optBuf.append(" <").append( option.getArgName() ).append( '>' ); - } - else { - optBuf.append(' '); - } - } - - prefixList.add(optBuf); - max = optBuf.length() > max ? optBuf.length() : max; - } - int x = 0; - for ( Iterator i = optList.iterator(); i.hasNext(); ) - { - option = (Option) i.next(); - optBuf = new StringBuffer( prefixList.get( x++ ).toString() ); - - if ( optBuf.length() < max ) - { - optBuf.append(createPadding(max - optBuf.length())); - } - optBuf.append( dpad ); - - int nextLineTabStop = max + descPad; - renderWrappedText(sb, width, nextLineTabStop, - optBuf.append(option.getDescription()).toString()); - if ( i.hasNext() ) - { - sb.append(defaultNewLine); - } - } - - return sb; - } - - protected StringBuffer renderWrappedText( StringBuffer sb, + int descPad) { + final String lpad = createPadding(leftPad); + final String dpad = createPadding(descPad); + + //first create list containing only -a,--aaa where -a is opt and --aaa is + //long opt; in parallel look for the longest opt string + //this list will be then used to sort options ascending + int max = 0; + StringBuffer optBuf; + List prefixList = new ArrayList(); + Option option; + List optList = options.helpOptions(); + Collections.sort(optList, new StringBufferComparator()); + for (Iterator i = optList.iterator(); i.hasNext(); ) { + option = (Option) i.next(); + optBuf = new StringBuffer(8); + + if (option.getOpt().equals(" ")) { + optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt()); + } else { + optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt()); + if (option.hasLongOpt()) { + optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt()); + } + + } + + if (option.hasArg()) { + if (option.hasArgName()) { + optBuf.append(" <").append(option.getArgName()).append('>'); + } else { + optBuf.append(' '); + } + } + + prefixList.add(optBuf); + max = optBuf.length() > max ? optBuf.length() : max; + } + int x = 0; + for (Iterator i = optList.iterator(); i.hasNext(); ) { + option = (Option) i.next(); + optBuf = new StringBuffer(prefixList.get(x++).toString()); + + if (optBuf.length() < max) { + optBuf.append(createPadding(max - optBuf.length())); + } + optBuf.append(dpad); + + int nextLineTabStop = max + descPad; + renderWrappedText(sb, width, nextLineTabStop, + optBuf.append(option.getDescription()).toString()); + if (i.hasNext()) { + sb.append(defaultNewLine); + } + } + + return sb; + } + + protected StringBuffer renderWrappedText(StringBuffer sb, int width, int nextLineTabStop, - String text ) - { - int pos = findWrapPos( text, width, 0); - if ( pos == -1 ) - { - sb.append(rtrim(text)); - return sb; - } - else - { - sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); - } - - //all following lines must be padded with nextLineTabStop space characters - final String padding = createPadding(nextLineTabStop); - - while ( true ) - { - text = padding + text.substring(pos).trim(); - pos = findWrapPos( text, width, nextLineTabStop ); - if ( pos == -1 ) - { - sb.append(text); + String text) { + int pos = findWrapPos(text, width, 0); + if (pos == -1) { + sb.append(rtrim(text)); return sb; - } - - sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); - } - - } - - /** - * Finds the next text wrap position after startPos for the text - * in sb with the column width width. - * The wrap point is the last postion before startPos+width having a whitespace - * character (space, \n, \r). - * - * @param sb text to be analyzed - * @param width width of the wrapped text - * @param startPos position from which to start the lookup whitespace character - * @return postion on which the text must be wrapped or -1 if the wrap position is at the end - * of the text - */ - protected int findWrapPos( String text, int width, int startPos ) - { - int pos = -1; - // the line ends before the max wrap pos or a new line char found - if ( ((pos = text.indexOf('\n', startPos)) != -1 && pos <= width) || - ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width) ) - { - return pos; - } - else if ( (startPos + width) >= text.length() ) - { - return -1; - } - - //look for the last whitespace character before startPos+width - pos = startPos + width; - char c; - while ( pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) - { - --pos; - } - //if we found it - just return - if ( pos > startPos ) - { - return pos; - } - else - { - //must look for the first whitespace chearacter after startPos + width - pos = startPos + width; - while ( pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) - { - ++pos; - } - return pos == text.length() ? -1 : pos; - } - } - - protected String createPadding(int len) - { - StringBuffer sb = new StringBuffer(len); - for ( int i = 0; i < len; ++i ) - { - sb.append(' '); - } - return sb.toString(); - } - - protected String rtrim( String s ) - { - if ( s == null || s.length() == 0 ) - { - return s; - } - - int pos = s.length(); - while ( pos >= 0 && Character.isWhitespace(s.charAt(pos-1)) ) - { - --pos; - } - return s.substring(0, pos); - } - - // ------------------------------------------------------- Package protected - - // ----------------------------------------------------------------- Private - - // ----------------------------------------------------------- Inner classes + } else { + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + //all following lines must be padded with nextLineTabStop space characters + final String padding = createPadding(nextLineTabStop); + + while (true) { + text = padding + text.substring(pos).trim(); + pos = findWrapPos(text, width, nextLineTabStop); + if (pos == -1) { + sb.append(text); + return sb; + } + + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + } + + /** + * Finds the next text wrap position after startPos for the text + * in sb with the column width width. + * The wrap point is the last postion before startPos+width having a whitespace + * character (space, \n, \r). + * + * @param text text to be analyzed + * @param width width of the wrapped text + * @param startPos position from which to start the lookup whitespace character + * @return postion on which the text must be wrapped or -1 if the wrap position is at the end + * of the text + */ + protected int findWrapPos(String text, int width, int startPos) { + int pos = -1; + // the line ends before the max wrap pos or a new line char found + if (((pos = text.indexOf('\n', startPos)) != -1 && pos <= width) || + ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width)) { + return pos; + } else if ((startPos + width) >= text.length()) { + return -1; + } + + //look for the last whitespace character before startPos+width + pos = startPos + width; + char c; + while (pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r') { + --pos; + } + //if we found it - just return + if (pos > startPos) { + return pos; + } else { + //must look for the first whitespace chearacter after startPos + width + pos = startPos + width; + while (pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r') { + ++pos; + } + return pos == text.length() ? -1 : pos; + } + } + + protected String createPadding(int len) { + StringBuffer sb = new StringBuffer(len); + for (int i = 0; i < len; ++i) { + sb.append(' '); + } + return sb.toString(); + } + + protected String rtrim(String s) { + if (s == null || s.length() == 0) { + return s; + } + + int pos = s.length(); + while (pos >= 0 && Character.isWhitespace(s.charAt(pos - 1))) { + --pos; + } + return s.substring(0, pos); + } + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes private static class StringBufferComparator - implements Comparator - { - public int compare( Object o1, Object o2 ) - { + implements Comparator { + public int compare(Object o1, Object o2) { String str1 = stripPrefix(o1.toString()); String str2 = stripPrefix(o2.toString()); return (str1.compareTo(str2)); } - private String stripPrefix(String strOption) - { + private String stripPrefix(String strOption) { // Strip any leading '-' characters int iStartIndex = strOption.lastIndexOf('-'); - if (iStartIndex == -1) - { - iStartIndex = 0; + if (iStartIndex == -1) { + iStartIndex = 0; } return strOption.substring(iStartIndex); diff --git a/src/org/apache/commons/cli/MissingArgumentException.java b/src/org/apache/commons/cli/MissingArgumentException.java index 23b23bd..0461eb2 100644 --- a/src/org/apache/commons/cli/MissingArgumentException.java +++ b/src/org/apache/commons/cli/MissingArgumentException.java @@ -61,7 +61,7 @@ package org.apache.commons.cli; -/** +/** *

    Thrown when an option requiring an argument * is not provided with an argument.

    * @@ -69,14 +69,14 @@ * @see ParseException */ public class MissingArgumentException extends ParseException { - - /** - *

    Construct a new MissingArgumentException + + /** + *

    Construct a new MissingArgumentException * with the specified detail message.

    * * @param message the detail message */ - public MissingArgumentException( String message ) { - super( message ); + public MissingArgumentException(String message) { + super(message); } } diff --git a/src/org/apache/commons/cli/MissingOptionException.java b/src/org/apache/commons/cli/MissingOptionException.java index 7a8157b..429a664 100644 --- a/src/org/apache/commons/cli/MissingOptionException.java +++ b/src/org/apache/commons/cli/MissingOptionException.java @@ -61,21 +61,21 @@ package org.apache.commons.cli; -/** +/** *

    Thrown when a required option has not been provided.

    * * @author John Keyes ( john at integralsource.com ) * @see ParseException */ public class MissingOptionException extends ParseException { - - /** - *

    Construct a new MissingSelectedException + + /** + *

    Construct a new MissingSelectedException * with the specified detail message.

    * * @param message the detail message */ - public MissingOptionException( String message ) { - super( message ); + public MissingOptionException(String message) { + super(message); } } diff --git a/src/org/apache/commons/cli/Option.java b/src/org/apache/commons/cli/Option.java index 3868227..6a4e80f 100644 --- a/src/org/apache/commons/cli/Option.java +++ b/src/org/apache/commons/cli/Option.java @@ -73,106 +73,179 @@ import java.util.ArrayList; -/**

    Describes a single command-line option. It maintains +/** + *

    Describes a single command-line option. It maintains * information regarding the short-name of the option, the long-name, * if any exists, a flag indicating if an argument is required for * this option, and a self-documenting description of the option.

    - * + *

    *

    An Option is not created independantly, but is create through * an instance of {@link Options}.

    * - * @see org.apache.commons.cli.Options - * @see org.apache.commons.cli.CommandLine - * * @author bob mcwhirter (bob @ werken.com) * @author James Strachan * @version $Revision: 1.6 $ + * @see org.apache.commons.cli.Options + * @see org.apache.commons.cli.CommandLine */ public class Option implements Cloneable { - /** constant that specifies the number of argument values has not been specified */ + /** + * constant that specifies the number of argument values has not been specified + */ public final static int UNINITIALIZED = -1; - - /** constant that specifies the number of argument values is infinite */ + + /** + * constant that specifies the number of argument values is infinite + */ public final static int UNLIMITED_VALUES = -2; - - /** opt the single character representation of the option */ + + /** + * opt the single character representation of the option + */ private String opt; - /** longOpt is the long representation of the option */ + /** + * longOpt is the long representation of the option + */ private String longOpt; - /** hasArg specifies whether this option has an associated argument */ + /** + * hasArg specifies whether this option has an associated argument + */ private boolean hasArg; - /** argName specifies the name of the argument for this option */ + /** + * argName specifies the name of the argument for this option + */ private String argName; - /** description of the option */ + /** + * description of the option + */ private String description; - /** required specifies whether this option is required to be present */ + /** + * required specifies whether this option is required to be present + */ private boolean required; - /** specifies whether the argument value of this Option is optional */ + /** + * specifies whether the argument value of this Option is optional + */ private boolean optionalArg; - /** - * numberOfArgs specifies the number of argument values this option - * can have + /** + * numberOfArgs specifies the number of argument values this option + * can have */ - private int numberOfArgs = UNINITIALIZED; + private int numberOfArgs = UNINITIALIZED; - /** the type of this Option */ + /** + * the type of this Option + */ private Object type; - /** the list of argument values **/ + /** + * the list of argument values + **/ private ArrayList values = new ArrayList(); - - /** option char (only valid for single character options) */ + + /** + * option char (only valid for single character options) + */ private char id; - /** the character that is the value separator */ + /** + * the character that is the value separator + */ private char valuesep; + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param description describes the function of the option + */ + public Option(String opt, String description) + throws IllegalArgumentException { + this(opt, null, false, description); + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option(String opt, boolean hasArg, String description) + throws IllegalArgumentException { + this(opt, null, hasArg, description); + } + + /** + *

    Creates an Option using the specified parameters.

    + * + * @param opt short representation of the option + * @param longOpt the long representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option(String opt, String longOpt, boolean hasArg, String description) + throws IllegalArgumentException { + // ensure that the option is valid + validateOption(opt); + + this.opt = opt; + this.longOpt = longOpt; + + // if hasArg is set then the number of arguments is 1 + if (hasArg) { + this.numberOfArgs = 1; + } + + this.hasArg = hasArg; + this.description = description; + } + /** *

    Validates whether opt is a permissable Option * shortOpt. The rules that specify if the opt * is valid are:

    *
      - *
    • opt is not NULL
    • - *
    • a single character opt that is either - * ' '(special case), '?', '@' or a letter
    • - *
    • a multi character opt that only contains - * letters.
    • + *
    • opt is not NULL
    • + *
    • a single character opt that is either + * ' '(special case), '?', '@' or a letter
    • + *
    • a multi character opt that only contains + * letters.
    • *
    * * @param opt The option string to validate * @throws IllegalArgumentException if the Option is not valid. */ - private void validateOption( String opt ) - throws IllegalArgumentException - { + private void validateOption(String opt) + throws IllegalArgumentException { // check that opt is not NULL - if( opt == null ) { - throw new IllegalArgumentException( "opt is null" ); + if (opt == null) { + throw new IllegalArgumentException("opt is null"); } // handle the single character opt - else if( opt.length() == 1 ) { - char ch = opt.charAt( 0 ); - if ( !isValidOpt( ch ) ) { - throw new IllegalArgumentException( "illegal option value '" - + ch + "'" ); + else if (opt.length() == 1) { + char ch = opt.charAt(0); + if (!isValidOpt(ch)) { + throw new IllegalArgumentException("illegal option value '" + + ch + "'"); } id = ch; } // handle the multi character opt else { char[] chars = opt.toCharArray(); - for( int i = 0; i < chars.length; i++ ) { - if( !isValidChar( chars[i] ) ) { - throw new IllegalArgumentException( "opt contains illegal character value '" + chars[i] + "'" ); + for (int i = 0; i < chars.length; i++) { + if (!isValidChar(chars[i])) { + throw new IllegalArgumentException("opt contains illegal character value '" + chars[i] + "'"); } } } @@ -184,8 +257,8 @@ else if( opt.length() == 1 ) { * @param c the option to validate * @return true if c is a letter, ' ', '?' or '@', otherwise false. */ - private boolean isValidOpt( char c ) { - return ( isValidChar( c ) || c == ' ' || c == '?' || c == '@' ); + private boolean isValidOpt(char c) { + return (isValidChar(c) || c == ' ' || c == '?' || c == '@'); } /** @@ -194,8 +267,8 @@ private boolean isValidOpt( char c ) { * @param c the character to validate * @return true if c is a letter. */ - private boolean isValidChar( char c ) { - return Character.isJavaIdentifierPart( c ); + private boolean isValidChar(char c) { + return Character.isJavaIdentifierPart(c); } /** @@ -205,64 +278,13 @@ private boolean isValidChar( char c ) { * * @return the id of this Option */ - public int getId( ) { + public int getId() { return id; } /** - * Creates an Option using the specified parameters. - * - * @param opt short representation of the option - * @param hasArg specifies whether the Option takes an argument or not - * @param description describes the function of the option - */ - public Option( String opt, String description ) - throws IllegalArgumentException - { - this( opt, null, false, description ); - } - - /** - * Creates an Option using the specified parameters. - * - * @param opt short representation of the option - * @param hasArg specifies whether the Option takes an argument or not - * @param description describes the function of the option - */ - public Option( String opt, boolean hasArg, String description ) - throws IllegalArgumentException - { - this( opt, null, hasArg, description ); - } - - /** - *

    Creates an Option using the specified parameters.

    - * - * @param opt short representation of the option - * @param longOpt the long representation of the option - * @param hasArg specifies whether the Option takes an argument or not - * @param description describes the function of the option - */ - public Option( String opt, String longOpt, boolean hasArg, String description ) - throws IllegalArgumentException - { - // ensure that the option is valid - validateOption( opt ); - - this.opt = opt; - this.longOpt = longOpt; - - // if hasArg is set then the number of arguments is 1 - if( hasArg ) { - this.numberOfArgs = 1; - } - - this.hasArg = hasArg; - this.description = description; - } - - /**

    Retrieve the name of this Option.

    - * + *

    Retrieve the name of this Option.

    + *

    *

    It is this String which can be used with * {@link CommandLine#hasOption(String opt)} and * {@link CommandLine#getOptionValue(String opt)} to check @@ -276,7 +298,7 @@ public String getOpt() { /** *

    Retrieve the type of this Option.

    - * + * * @return The type of this option */ public Object getType() { @@ -288,11 +310,11 @@ public Object getType() { * * @param type the type of this Option */ - public void setType( Object type ) { + public void setType(Object type) { this.type = type; } - - /** + + /** *

    Retrieve the long name of this Option.

    * * @return Long name of this option, or null, if there is no long name @@ -306,7 +328,7 @@ public String getLongOpt() { * * @param longOpt the long name of this Option */ - public void setLongOpt( String longOpt ) { + public void setLongOpt(String longOpt) { this.longOpt = longOpt; } @@ -314,36 +336,39 @@ public void setLongOpt( String longOpt ) { *

    Sets whether this Option can have an optional argument.

    * * @param optionalArg specifies whether the Option can have - * an optional argument. + * an optional argument. */ - public void setOptionalArg( boolean optionalArg ) { + public void setOptionalArg(boolean optionalArg) { this.optionalArg = optionalArg; } /** * @return whether this Option can have an optional argument */ - public boolean hasOptionalArg( ) { + public boolean hasOptionalArg() { return this.optionalArg; } - - /**

    Query to see if this Option has a long name

    + + /** + *

    Query to see if this Option has a long name

    * * @return boolean flag indicating existence of a long name */ public boolean hasLongOpt() { - return ( this.longOpt != null ); + return (this.longOpt != null); } - - /**

    Query to see if this Option requires an argument

    + + /** + *

    Query to see if this Option requires an argument

    * * @return boolean flag indicating if an argument is required */ public boolean hasArg() { return this.numberOfArgs > 0 || numberOfArgs == UNLIMITED_VALUES; } - - /**

    Retrieve the self-documenting description of this Option

    + + /** + *

    Retrieve the self-documenting description of this Option

    * * @return The string description of this option */ @@ -351,126 +376,126 @@ public String getDescription() { return this.description; } - /** - *

    Query to see if this Option requires an argument

    - * - * @return boolean flag indicating if an argument is required - */ - public boolean isRequired() { - return this.required; - } - - /** - *

    Sets whether this Option is mandatory.

    - * - * @param required specifies whether this Option is mandatory - */ - public void setRequired( boolean required ) { - this.required = required; - } - - /** - *

    Sets the display name for the argument value.

    - * - * @param argName the display name for the argument value. - */ - public void setArgName( String argName ) { - this.argName = argName; - } - - /** - *

    Gets the display name for the argument value.

    - * - * @return the display name for the argument value. - */ - public String getArgName() { - return this.argName; - } - - /** - *

    Returns whether the display name for the argument value - * has been set.

    - * - * @return if the display name for the argument value has been - * set. - */ - public boolean hasArgName() { - return (this.argName != null && this.argName.length() > 0 ); - } - - /** - *

    Query to see if this Option can take many values

    - * - * @return boolean flag indicating if multiple values are allowed - */ - public boolean hasArgs() { - return ( this.numberOfArgs > 1 || this.numberOfArgs == UNLIMITED_VALUES ); - } - - /** - *

    Sets the number of argument values this Option can take.

    - * - * @param num the number of argument values - */ - public void setArgs( int num ) { - this.numberOfArgs = num; - } - - /** - *

    Sets the value separator. For example if the argument value - * was a Java property, the value separator would be '='.

    - * - * @param sep The value separator. - */ - public void setValueSeparator( char sep ) { - this.valuesep = sep; - } - - /** - *

    Returns the value separator character.

    - * - * @return the value separator character. - */ - public char getValueSeparator() { - return this.valuesep; - } - - /** - *

    Returns the number of argument values this Option can take.

    - * - * @return num the number of argument values - */ - public int getArgs( ) { - return this.numberOfArgs; - } - - /** + /** + *

    Query to see if this Option requires an argument

    + * + * @return boolean flag indicating if an argument is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

    Sets whether this Option is mandatory.

    + * + * @param required specifies whether this Option is mandatory + */ + public void setRequired(boolean required) { + this.required = required; + } + + /** + *

    Gets the display name for the argument value.

    + * + * @return the display name for the argument value. + */ + public String getArgName() { + return this.argName; + } + + /** + *

    Sets the display name for the argument value.

    + * + * @param argName the display name for the argument value. + */ + public void setArgName(String argName) { + this.argName = argName; + } + + /** + *

    Returns whether the display name for the argument value + * has been set.

    + * + * @return if the display name for the argument value has been + * set. + */ + public boolean hasArgName() { + return (this.argName != null && this.argName.length() > 0); + } + + /** + *

    Query to see if this Option can take many values

    + * + * @return boolean flag indicating if multiple values are allowed + */ + public boolean hasArgs() { + return (this.numberOfArgs > 1 || this.numberOfArgs == UNLIMITED_VALUES); + } + + /** + *

    Returns the value separator character.

    + * + * @return the value separator character. + */ + public char getValueSeparator() { + return this.valuesep; + } + + /** + *

    Sets the value separator. For example if the argument value + * was a Java property, the value separator would be '='.

    + * + * @param sep The value separator. + */ + public void setValueSeparator(char sep) { + this.valuesep = sep; + } + + /** + *

    Returns the number of argument values this Option can take.

    + * + * @return num the number of argument values + */ + public int getArgs() { + return this.numberOfArgs; + } + + /** + *

    Sets the number of argument values this Option can take.

    + * + * @param num the number of argument values + */ + public void setArgs(int num) { + this.numberOfArgs = num; + } + + /** *

    Dump state, suitable for debugging.

    * * @return Stringified form of this object */ public String toString() { StringBuffer buf = new StringBuffer().append("[ option: "); - - buf.append( this.opt ); - - if ( this.longOpt != null ) { + + buf.append(this.opt); + + if (this.longOpt != null) { buf.append(" ") - .append(this.longOpt); + .append(this.longOpt); } - + buf.append(" "); - - if ( hasArg ) { - buf.append( "+ARG" ); + + if (hasArg) { + buf.append("+ARG"); } - + buf.append(" :: ") - .append( this.description ); - - if ( this.type != null ) { + .append(this.description); + + if (this.type != null) { buf.append(" :: ") - .append( this.type ); + .append(this.type); } buf.append(" ]"); @@ -479,76 +504,75 @@ public String toString() { /** *

    Adds the specified value to this Option.

    - * + * * @param value is a/the value of this Option */ - public boolean addValue( String value ) { + public boolean addValue(String value) { - switch( numberOfArgs ) { + switch (numberOfArgs) { case UNINITIALIZED: return false; case UNLIMITED_VALUES: - if( getValueSeparator() > 0 ) { + if (getValueSeparator() > 0) { int index = 0; - while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { - this.values.add( value.substring( 0, index ) ); - value = value.substring( index+1 ); + while ((index = value.indexOf(getValueSeparator())) != -1) { + this.values.add(value.substring(0, index)); + value = value.substring(index + 1); } } - this.values.add( value ); + this.values.add(value); return true; default: - if( getValueSeparator() > 0 ) { + if (getValueSeparator() > 0) { int index = 0; - while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { - if( values.size() > numberOfArgs-1 ) { + while ((index = value.indexOf(getValueSeparator())) != -1) { + if (values.size() > numberOfArgs - 1) { return false; } - this.values.add( value.substring( 0, index ) ); - value = value.substring( index+1 ); + this.values.add(value.substring(0, index)); + value = value.substring(index + 1); } } - if( values.size() > numberOfArgs-1 ) { + if (values.size() > numberOfArgs - 1) { return false; } - this.values.add( value ); + this.values.add(value); return true; } } /** - * @return the value/first value of this Option or + * @return the value/first value of this Option or * null if there are no values. */ public String getValue() { - return this.values.size()==0 ? null : (String)this.values.get( 0 ); + return this.values.size() == 0 ? null : (String) this.values.get(0); } /** - * @return the specified value of this Option or + * @return the specified value of this Option or * null if there are no values. */ - public String getValue( int index ) - throws IndexOutOfBoundsException - { - return ( this.values.size()==0 ) ? null : (String)this.values.get( index ); + public String getValue(int index) + throws IndexOutOfBoundsException { + return (this.values.size() == 0) ? null : (String) this.values.get(index); } /** - * @return the value/first value of this Option or the + * @return the value/first value of this Option or the * defaultValue if there are no values. */ - public String getValue( String defaultValue ) { - String value = getValue( ); - return ( value != null ) ? value : defaultValue; + public String getValue(String defaultValue) { + String value = getValue(); + return (value != null) ? value : defaultValue; } /** - * @return the values of this Option as a String array + * @return the values of this Option as a String array * or null if there are no values */ public String[] getValues() { - return this.values.size()==0 ? null : (String[])this.values.toArray(new String[]{}); + return this.values.size() == 0 ? null : (String[]) this.values.toArray(new String[]{}); } /** @@ -563,13 +587,13 @@ public java.util.List getValuesList() { * @return a copy of this Option */ public Object clone() { - Option option = new Option( getOpt(), getDescription() ); - option.setArgs( getArgs() ); - option.setOptionalArg( hasOptionalArg() ); - option.setRequired( isRequired() ); - option.setLongOpt( getLongOpt() ); - option.setType( getType() ); - option.setValueSeparator( getValueSeparator() ); + Option option = new Option(getOpt(), getDescription()); + option.setArgs(getArgs()); + option.setOptionalArg(hasOptionalArg()); + option.setRequired(isRequired()); + option.setLongOpt(getLongOpt()); + option.setType(getType()); + option.setValueSeparator(getValueSeparator()); return option; } } diff --git a/src/org/apache/commons/cli/OptionBuilder.java b/src/org/apache/commons/cli/OptionBuilder.java index d8d8a58..b220062 100644 --- a/src/org/apache/commons/cli/OptionBuilder.java +++ b/src/org/apache/commons/cli/OptionBuilder.java @@ -64,7 +64,7 @@ /** *

    OptionBuilder allows the user to create Options using descriptive * methods.

    - *

    Details on the Builder pattern can be found at + *

    Details on the Builder pattern can be found at * http://c2.com/cgi-bin/wiki?BuilderPattern.

    * * @author John Keyes ( john at integralsource.com ) @@ -72,24 +72,42 @@ */ public class OptionBuilder { - /** long option */ + /** + * long option + */ private static String longopt; - /** option description */ + /** + * option description + */ private static String description; - /** argument name */ + /** + * argument name + */ private static String argName; - /** is required? */ + /** + * is required? + */ private static boolean required; - /** the number of arguments */ + /** + * the number of arguments + */ private static int numberOfArgs = Option.UNINITIALIZED; - /** option type */ + /** + * option type + */ private static Object type; - /** option can have an optional argument value */ + /** + * option can have an optional argument value + */ private static boolean optionalArg; - /** value separator for argument value */ + /** + * value separator for argument value + */ private static char valuesep; - /** option builder instance */ + /** + * option builder instance + */ private static OptionBuilder instance = new OptionBuilder(); // private constructor @@ -118,7 +136,7 @@ private static void reset() { * @param longopt the long option value * @return the OptionBuilder instance */ - public static OptionBuilder withLongOpt( String longopt ) { + public static OptionBuilder withLongOpt(String longopt) { instance.longopt = longopt; return instance; } @@ -128,7 +146,7 @@ public static OptionBuilder withLongOpt( String longopt ) { * * @return the OptionBuilder instance */ - public static OptionBuilder hasArg( ) { + public static OptionBuilder hasArg() { instance.numberOfArgs = 1; return instance; } @@ -140,19 +158,19 @@ public static OptionBuilder hasArg( ) { * @param hasArg if true then the Option has an argument value * @return the OptionBuilder instance */ - public static OptionBuilder hasArg( boolean hasArg ) { - instance.numberOfArgs = ( hasArg == true ) ? 1 : Option.UNINITIALIZED; + public static OptionBuilder hasArg(boolean hasArg) { + instance.numberOfArgs = (hasArg == true) ? 1 : Option.UNINITIALIZED; return instance; } /** - *

    The next Option created will have the specified argument value + *

    The next Option created will have the specified argument value * name.

    * * @param name the name for the argument value * @return the OptionBuilder instance */ - public static OptionBuilder withArgName( String name ) { + public static OptionBuilder withArgName(String name) { instance.argName = name; return instance; } @@ -162,7 +180,7 @@ public static OptionBuilder withArgName( String name ) { * * @return the OptionBuilder instance */ - public static OptionBuilder isRequired( ) { + public static OptionBuilder isRequired() { instance.required = true; return instance; } @@ -170,7 +188,7 @@ public static OptionBuilder isRequired( ) { /** *

    The next Option created uses sep as a means to * separate argument values.

    - * + *

    * Example: *

          * Option opt = OptionBuilder.withValueSeparator( ':' )
    @@ -183,7 +201,7 @@ public static OptionBuilder isRequired( ) {
          *
          * @return the OptionBuilder instance
          */
    -    public static OptionBuilder withValueSeparator( char sep ) {
    +    public static OptionBuilder withValueSeparator(char sep) {
             instance.valuesep = sep;
             return instance;
         }
    @@ -191,7 +209,7 @@ public static OptionBuilder withValueSeparator( char sep ) {
         /**
          * 

    The next Option created uses '=' as a means to * separate argument values.

    - * + *

    * Example: *

          * Option opt = OptionBuilder.withValueSeparator( )
    @@ -204,7 +222,7 @@ public static OptionBuilder withValueSeparator( char sep ) {
          *
          * @return the OptionBuilder instance
          */
    -    public static OptionBuilder withValueSeparator( ) {
    +    public static OptionBuilder withValueSeparator() {
             instance.valuesep = '=';
             return instance;
         }
    @@ -216,7 +234,7 @@ public static OptionBuilder withValueSeparator( ) {
          * @param required if true then the Option is required
          * @return the OptionBuilder instance
          */
    -    public static OptionBuilder isRequired( boolean required ) {
    +    public static OptionBuilder isRequired(boolean required) {
             instance.required = required;
             return instance;
         }
    @@ -226,19 +244,19 @@ public static OptionBuilder isRequired( boolean required ) {
          *
          * @return the OptionBuilder instance
          */
    -    public static OptionBuilder hasArgs( ) {
    +    public static OptionBuilder hasArgs() {
             instance.numberOfArgs = Option.UNLIMITED_VALUES;
             return instance;
         }
     
         /**
    -     * 

    The next Option created can have num + *

    The next Option created can have num * argument values.

    * * @param num the number of args that the option can have * @return the OptionBuilder instance */ - public static OptionBuilder hasArgs( int num ) { + public static OptionBuilder hasArgs(int num) { instance.numberOfArgs = num; return instance; } @@ -248,7 +266,7 @@ public static OptionBuilder hasArgs( int num ) { * * @return the OptionBuilder instance */ - public static OptionBuilder hasOptionalArg( ) { + public static OptionBuilder hasOptionalArg() { instance.numberOfArgs = 1; instance.optionalArg = true; return instance; @@ -260,34 +278,34 @@ public static OptionBuilder hasOptionalArg( ) { * * @return the OptionBuilder instance */ - public static OptionBuilder hasOptionalArgs( ) { + public static OptionBuilder hasOptionalArgs() { instance.numberOfArgs = Option.UNLIMITED_VALUES; instance.optionalArg = true; return instance; } /** - *

    The next Option can have the specified number of + *

    The next Option can have the specified number of * optional arguments.

    * * @param numArgs - the maximum number of optional arguments - * the next Option created can have. + * the next Option created can have. * @return the OptionBuilder instance */ - public static OptionBuilder hasOptionalArgs( int numArgs ) { + public static OptionBuilder hasOptionalArgs(int numArgs) { instance.numberOfArgs = numArgs; instance.optionalArg = true; return instance; } /** - *

    The next Option created will have a value that will be an instance + *

    The next Option created will have a value that will be an instance * of type.

    * * @param type the type of the Options argument value * @return the OptionBuilder instance */ - public static OptionBuilder withType( Object type ) { + public static OptionBuilder withType(Object type) { instance.type = type; return instance; } @@ -298,24 +316,23 @@ public static OptionBuilder withType( Object type ) { * @param description a description of the Option's purpose * @return the OptionBuilder instance */ - public static OptionBuilder withDescription( String description ) { + public static OptionBuilder withDescription(String description) { instance.description = description; return instance; } /** - *

    Create an Option using the current settings and with + *

    Create an Option using the current settings and with * the specified Option char.

    * * @param opt the character representation of the Option * @return the Option instance * @throws IllegalArgumentException if opt is not - * a valid character. See Option. + * a valid character. See Option. */ - public static Option create( char opt ) - throws IllegalArgumentException - { - return create( String.valueOf( opt ) ); + public static Option create(char opt) + throws IllegalArgumentException { + return create(String.valueOf(opt)); } /** @@ -323,42 +340,40 @@ public static Option create( char opt ) * * @return the Option instance * @throws IllegalArgumentException if longOpt has - * not been set. + * not been set. */ - public static Option create() - throws IllegalArgumentException - { - if( longopt == null ) { - throw new IllegalArgumentException( "must specify longopt" ); + public static Option create() + throws IllegalArgumentException { + if (longopt == null) { + throw new IllegalArgumentException("must specify longopt"); } - return create( " " ); + return create(" "); } /** - *

    Create an Option using the current settings and with + *

    Create an Option using the current settings and with * the specified Option char.

    * - * @param opt the java.lang.String representation - * of the Option + * @param opt the java.lang.String representation + * of the Option * @return the Option instance * @throws IllegalArgumentException if opt is not - * a valid character. See Option. + * a valid character. See Option. */ - public static Option create( String opt ) - throws IllegalArgumentException - { + public static Option create(String opt) + throws IllegalArgumentException { // create the option - Option option = new Option( opt, description ); + Option option = new Option(opt, description); // set the option properties - option.setLongOpt( longopt ); - option.setRequired( required ); - option.setOptionalArg( optionalArg ); - option.setArgs( numberOfArgs ); - option.setType( type ); - option.setValueSeparator( valuesep ); - option.setArgName( argName ); + option.setLongOpt(longopt); + option.setRequired(required); + option.setOptionalArg(optionalArg); + option.setArgs(numberOfArgs); + option.setType(type); + option.setValueSeparator(valuesep); + option.setArgName(argName); // reset the OptionBuilder properties instance.reset(); diff --git a/src/org/apache/commons/cli/OptionGroup.java b/src/org/apache/commons/cli/OptionGroup.java index afa2f8c..ce3f1b1 100644 --- a/src/org/apache/commons/cli/OptionGroup.java +++ b/src/org/apache/commons/cli/OptionGroup.java @@ -67,18 +67,25 @@ /** * A group of mutually exclusive options. + * * @author John Keyes ( john at integralsource.com ) * @version $Revision: 1.2 $ */ public class OptionGroup { - /** hold the options */ + /** + * hold the options + */ private HashMap optionMap = new HashMap(); - /** the name of the selected option */ + /** + * the name of the selected option + */ private String selected; - /** specified whether this group is required */ + /** + * specified whether this group is required + */ private boolean required; /** @@ -90,12 +97,12 @@ public class OptionGroup { public OptionGroup addOption(Option opt) { // key - option name // value - the option - optionMap.put( "-" + opt.getOpt(), opt ); + optionMap.put("-" + opt.getOpt(), opt); return this; } /** - * @return the names of the options in this group as a + * @return the names of the options in this group as a * Collection */ public Collection getNames() { @@ -111,52 +118,53 @@ public Collection getOptions() { return optionMap.values(); } + /** + * @return the selected option name + */ + public String getSelected() { + return selected; + } + /** * set the selected option of this group to name. + * * @param opt the option that is selected - * @throws AlreadySelectedException if an option from this group has - * already been selected. + * @throws AlreadySelectedException if an option from this group has + * already been selected. */ public void setSelected(Option opt) throws AlreadySelectedException { - // if no option has already been selected or the + // if no option has already been selected or the // same option is being reselected then set the // selected member variable - if ( this.selected == null || this.selected.equals( opt.getOpt() ) ) { + if (this.selected == null || this.selected.equals(opt.getOpt())) { this.selected = opt.getOpt(); - } - else { - throw new AlreadySelectedException( "an option from this group has " + - "already been selected: '" + - selected + "'"); + } else { + throw new AlreadySelectedException("an option from this group has " + + "already been selected: '" + + selected + "'"); } } /** - * @return the selected option name + * Returns whether this option group is required. + * + * @returns whether this option group is required */ - public String getSelected() { - return selected; + public boolean isRequired() { + return this.required; } /** * @param required specifies if this group is required */ - public void setRequired( boolean required ) { + public void setRequired(boolean required) { this.required = required; } - /** - * Returns whether this option group is required. - * - * @returns whether this option group is required - */ - public boolean isRequired() { - return this.required; - } - /** *

    Returns the stringified version of this OptionGroup.

    + * * @return the stringified representation of this group */ public String toString() { @@ -164,20 +172,20 @@ public String toString() { Iterator iter = getOptions().iterator(); - buff.append( "[" ); - while( iter.hasNext() ) { - Option option = (Option)iter.next(); + buff.append("["); + while (iter.hasNext()) { + Option option = (Option) iter.next(); - buff.append( "-" ); - buff.append( option.getOpt() ); - buff.append( " " ); - buff.append( option.getDescription( ) ); + buff.append("-"); + buff.append(option.getOpt()); + buff.append(" "); + buff.append(option.getDescription()); - if( iter.hasNext() ) { - buff.append( ", " ); + if (iter.hasNext()) { + buff.append(", "); } } - buff.append( "]" ); + buff.append("]"); return buff.toString(); } diff --git a/src/org/apache/commons/cli/Options.java b/src/org/apache/commons/cli/Options.java index 994ab05..02b5969 100644 --- a/src/org/apache/commons/cli/Options.java +++ b/src/org/apache/commons/cli/Options.java @@ -61,46 +61,49 @@ package org.apache.commons.cli; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/**

    Main entry-point into the library.

    - * +import java.util.*; + +/** + *

    Main entry-point into the library.

    + *

    *

    Options represents a collection of {@link Option} objects, which * describe the possible options for a command-line.

    - * + *

    *

    It may flexibly parse long and short options, with or without * values. Additionally, it may parse only a portion of a commandline, * allowing for flexible multi-stage parsing.

    * - * @see org.apache.commons.cli.CommandLine - * * @author bob mcwhirter (bob @ werken.com) * @author James Strachan * @version $Revision: 1.5 $ + * @see org.apache.commons.cli.CommandLine */ public class Options { - /** a map of the options with the character key */ - private Map shortOpts = new HashMap(); + /** + * a map of the options with the character key + */ + private Map shortOpts = new HashMap(); - /** a map of the options with the long key */ - private Map longOpts = new HashMap(); + /** + * a map of the options with the long key + */ + private Map longOpts = new HashMap(); - /** a map of the required options */ + /** + * a map of the required options + */ private List requiredOpts = new ArrayList(); - - /** a map of the option groups */ - private Map optionGroups = new HashMap(); - /**

    Construct a new Options descriptor

    + /** + * a map of the option groups */ - public Options() { + private Map optionGroups = new HashMap(); + + /** + *

    Construct a new Options descriptor

    + */ + public Options() { } /** @@ -109,97 +112,98 @@ public Options() { * @param group the OptionGroup that is to be added * @return the resulting Options instance */ - public Options addOptionGroup( OptionGroup group ) { + public Options addOptionGroup(OptionGroup group) { Iterator options = group.getOptions().iterator(); - if( group.isRequired() ) { - requiredOpts.add( group ); + if (group.isRequired()) { + requiredOpts.add(group); } - while( options.hasNext() ) { - Option option = (Option)options.next(); + while (options.hasNext()) { + Option option = (Option) options.next(); // an Option cannot be required if it is in an // OptionGroup, either the group is required or // nothing is required - option.setRequired( false ); - addOption( option ); + option.setRequired(false); + addOption(option); - optionGroups.put( option.getOpt(), group ); + optionGroups.put(option.getOpt(), group); } return this; } - /**

    Add an option that only contains a short-name

    + /** + *

    Add an option that only contains a short-name

    *

    It may be specified as requiring an argument.

    * - * @param opt Short single-character name of the option. - * @param hasArg flag signally if an argument is required after this option + * @param opt Short single-character name of the option. + * @param hasArg flag signally if an argument is required after this option * @param description Self-documenting description * @return the resulting Options instance */ public Options addOption(String opt, boolean hasArg, String description) { - addOption( opt, null, hasArg, description ); + addOption(opt, null, hasArg, description); return this; } - - /**

    Add an option that contains a short-name and a long-name

    + + /** + *

    Add an option that contains a short-name and a long-name

    *

    It may be specified as requiring an argument.

    * - * @param opt Short single-character name of the option. - * @param longOpt Long multi-character name of the option. - * @param hasArg flag signally if an argument is required after this option + * @param opt Short single-character name of the option. + * @param longOpt Long multi-character name of the option. + * @param hasArg flag signally if an argument is required after this option * @param description Self-documenting description * @return the resulting Options instance */ public Options addOption(String opt, String longOpt, boolean hasArg, String description) { - addOption( new Option( opt, longOpt, hasArg, description ) ); + addOption(new Option(opt, longOpt, hasArg, description)); return this; } /** *

    Adds an option instance

    * - * @param opt the option that is to be added + * @param opt the option that is to be added * @return the resulting Options instance */ - public Options addOption(Option opt) { + public Options addOption(Option opt) { String shortOpt = "-" + opt.getOpt(); - + // add it to the long option list - if ( opt.hasLongOpt() ) { - longOpts.put( "--" + opt.getLongOpt(), opt ); + if (opt.hasLongOpt()) { + longOpts.put("--" + opt.getLongOpt(), opt); } - + // if the option is required add it to the required list - if ( opt.isRequired() ) { - requiredOpts.add( shortOpt ); + if (opt.isRequired()) { + requiredOpts.add(shortOpt); } - shortOpts.put( shortOpt, opt ); - + shortOpts.put(shortOpt, opt); + return this; } - - /**

    Retrieve a read-only list of options in this set

    + + /** + *

    Retrieve a read-only list of options in this set

    * * @return read-only Collection of {@link Option} objects in this descriptor */ public Collection getOptions() { - List opts = new ArrayList( shortOpts.values() ); + List opts = new ArrayList(shortOpts.values()); // now look through the long opts to see if there are any Long-opt // only options Iterator iter = longOpts.values().iterator(); - while (iter.hasNext()) - { + while (iter.hasNext()) { Object item = iter.next(); - if (!opts.contains(item)) - { + if (!opts.contains(item)) { opts.add(item); } } - return Collections.unmodifiableCollection( opts ); + return Collections.unmodifiableCollection(opts); } /** @@ -208,10 +212,11 @@ public Collection getOptions() { * @return the List of Options */ List helpOptions() { - return new ArrayList( shortOpts.values() ); + return new ArrayList(shortOpts.values()); } - /**

    Returns the required options as a + /** + *

    Returns the required options as a * java.util.Collection.

    * * @return Collection of required options @@ -219,33 +224,34 @@ List helpOptions() { public List getRequiredOptions() { return requiredOpts; } - - /**

    Retrieve the named {@link Option}

    + + /** + *

    Retrieve the named {@link Option}

    * * @param opt short or long name of the {@link Option} * @return the option represented by opt */ - public Option getOption( String opt ) { + public Option getOption(String opt) { Option option = null; // short option - if( opt.length() == 1 ) { - option = (Option)shortOpts.get( "-" + opt ); + if (opt.length() == 1) { + option = (Option) shortOpts.get("-" + opt); } // long option - else if( opt.startsWith( "--" ) ) { - option = (Option)longOpts.get( opt ); + else if (opt.startsWith("--")) { + option = (Option) longOpts.get(opt); } // a just-in-case else { - option = (Option)shortOpts.get( opt ); + option = (Option) shortOpts.get(opt); } - return (option == null) ? null : (Option)option.clone(); + return (option == null) ? null : (Option) option.clone(); } - /** + /** *

    Returns whether the named {@link Option} is a member * of this {@link Options}

    * @@ -253,46 +259,48 @@ else if( opt.startsWith( "--" ) ) { * @return true if the named {@link Option} is a member * of this {@link Options} */ - public boolean hasOption( String opt ) { + public boolean hasOption(String opt) { // short option - if( opt.length() == 1 ) { - return shortOpts.containsKey( "-" + opt ); + if (opt.length() == 1) { + return shortOpts.containsKey("-" + opt); } // long option - else if( opt.startsWith( "--" ) ) { - return longOpts.containsKey( opt ); + else if (opt.startsWith("--")) { + return longOpts.containsKey(opt); } // a just-in-case else { - return shortOpts.containsKey( opt ); + return shortOpts.containsKey(opt); } } - /**

    Returns the OptionGroup the opt + /** + *

    Returns the OptionGroup the opt * belongs to.

    - * @param opt the option whose OptionGroup is being queried. * + * @param opt the option whose OptionGroup is being queried. * @return the OptionGroup if opt is part * of an OptionGroup, otherwise return null */ - public OptionGroup getOptionGroup( Option opt ) { - return (OptionGroup)optionGroups.get( opt.getOpt() ); + public OptionGroup getOptionGroup(Option opt) { + return (OptionGroup) optionGroups.get(opt.getOpt()); } - - /**

    Dump state, suitable for debugging.

    + + /** + *

    Dump state, suitable for debugging.

    * * @return Stringified form of this object */ public String toString() { StringBuffer buf = new StringBuffer(); - + buf.append("[ Options: [ short "); - buf.append( shortOpts.toString() ); - buf.append( " ] [ long " ); - buf.append( longOpts ); - buf.append( " ]"); - + buf.append(shortOpts.toString()); + buf.append(" ] [ long "); + buf.append(longOpts); + buf.append(" ]"); + return buf.toString(); } } diff --git a/src/org/apache/commons/cli/ParseException.java b/src/org/apache/commons/cli/ParseException.java index dd14a23..33cb913 100644 --- a/src/org/apache/commons/cli/ParseException.java +++ b/src/org/apache/commons/cli/ParseException.java @@ -61,22 +61,21 @@ package org.apache.commons.cli; -/** +/** *

    Base for Exceptions thrown during parsing of a command-line.

    * * @author bob mcwhirter (bob @ werken.com) * @version $Revision: 1.2 $ */ -public class ParseException extends Exception -{ - - /** - *

    Construct a new ParseException +public class ParseException extends Exception { + + /** + *

    Construct a new ParseException * with the specified detail message.

    * * @param message the detail message */ - public ParseException( String message ) { - super( message ); + public ParseException(String message) { + super(message); } } diff --git a/src/org/apache/commons/cli/Parser.java b/src/org/apache/commons/cli/Parser.java index 1921e15..eb0beb3 100644 --- a/src/org/apache/commons/cli/Parser.java +++ b/src/org/apache/commons/cli/Parser.java @@ -70,68 +70,72 @@ *

    Parser creates {@link CommandLine}s.

    * * @author John Keyes (john at integralsource.com) - * @see Parser * @version $Revision: 1.7 $ + * @see Parser */ public abstract class Parser implements CommandLineParser { - /** commandline instance */ + /** + * commandline instance + */ private CommandLine cmd; - /** current Options */ + /** + * current Options + */ private Options options; - /** list of required options strings */ + /** + * list of required options strings + */ private List requiredOptions; /** *

    Subclasses must implement this method to reduce - * the arguments that have been passed to the parse + * the arguments that have been passed to the parse * method.

    * - * @param opts The Options to parse the arguments by. - * @param args The arguments that have to be flattened. - * @param stopAtNonOption specifies whether to stop - * flattening when a non option has been encountered + * @param opts The Options to parse the arguments by. + * @param arguments The arguments that have to be flattened. + * @param stopAtNonOption specifies whether to stop + * flattening when a non option has been encountered * @return a String array of the flattened arguments */ - abstract protected String[] flatten( Options opts, - String[] arguments, - boolean stopAtNonOption ); + abstract protected String[] flatten(Options opts, + String[] arguments, + boolean stopAtNonOption); /** - *

    Parses the specified arguments + *

    Parses the specified arguments * based on the specifed {@link Options}.

    * - * @param options the Options + * @param options the Options * @param arguments the arguments * @return the CommandLine * @throws ParseException if an error occurs when parsing the - * arguments. + * arguments. */ - public CommandLine parse( Options options, String[] arguments ) - throws ParseException - { - return parse( options, arguments, false ); + public CommandLine parse(Options options, String[] arguments) + throws ParseException { + return parse(options, arguments, false); } /** - *

    Parses the specified arguments + *

    Parses the specified arguments * based on the specifed {@link Options}.

    * - * @param options the Options - * @param arguments the arguments - * @param stopAtNonOption specifies whether to stop - * interpreting the arguments when a non option has - * been encountered and to add them to the CommandLines - * args list. + * @param opts the Options + * @param arguments the arguments + * @param stopAtNonOption specifies whether to stop + * interpreting the arguments when a non option has + * been encountered and to add them to the CommandLines + * args list. * @return the CommandLine * @throws ParseException if an error occurs when parsing the - * arguments. + * arguments. */ - public CommandLine parse( Options opts, - String[] arguments, - boolean stopAtNonOption ) - throws ParseException - { + public CommandLine parse(Options opts, + String[] arguments, + boolean stopAtNonOption) + throws ParseException { // initialise members options = opts; requiredOptions = options.getRequiredOptions(); @@ -139,51 +143,49 @@ public CommandLine parse( Options opts, boolean eatTheRest = false; - List tokenList = Arrays.asList( flatten( opts, arguments, stopAtNonOption ) ); + List tokenList = Arrays.asList(flatten(opts, arguments, stopAtNonOption)); ListIterator iterator = tokenList.listIterator(); // process each flattened token - while( iterator.hasNext() ) { - String t = (String)iterator.next(); + while (iterator.hasNext()) { + String t = (String) iterator.next(); // the value is the double-dash - if( "--".equals( t ) ) { + if ("--".equals(t)) { eatTheRest = true; } // the value is a single dash - else if( "-".equals( t ) ) { - if( stopAtNonOption ) { + else if ("-".equals(t)) { + if (stopAtNonOption) { eatTheRest = true; - } - else { - cmd.addArg(t ); + } else { + cmd.addArg(t); } } // the value is an option - else if( t.startsWith( "-" ) ) { - if ( stopAtNonOption && !options.hasOption( t ) ) { + else if (t.startsWith("-")) { + if (stopAtNonOption && !options.hasOption(t)) { eatTheRest = true; - cmd.addArg( t ); - } - else { - processOption( t, iterator ); + cmd.addArg(t); + } else { + processOption(t, iterator); } } // the value is an argument else { - cmd.addArg( t ); - if( stopAtNonOption ) { + cmd.addArg(t); + if (stopAtNonOption) { eatTheRest = true; } } // eat the remaining tokens - if( eatTheRest ) { - while( iterator.hasNext() ) { - String str = (String)iterator.next(); + if (eatTheRest) { + while (iterator.hasNext()) { + String str = (String) iterator.next(); // ensure only one double-dash is added - if( !"--".equals( str ) ) { - cmd.addArg( str ); + if (!"--".equals(str)) { + cmd.addArg(str); } } } @@ -197,86 +199,82 @@ else if( t.startsWith( "-" ) ) { * required options are no present.

    */ private void checkRequiredOptions() - throws MissingOptionException - { + throws MissingOptionException { // if there are required options that have not been // processsed - if( requiredOptions.size() > 0 ) { + if (requiredOptions.size() > 0) { Iterator iter = requiredOptions.iterator(); StringBuffer buff = new StringBuffer(); // loop through the required options - while( iter.hasNext() ) { - buff.append( iter.next() ); + while (iter.hasNext()) { + buff.append(iter.next()); } - throw new MissingOptionException( buff.toString() ); + throw new MissingOptionException(buff.toString()); } } - public void processArgs( Option opt, ListIterator iter ) - throws ParseException - { + public void processArgs(Option opt, ListIterator iter) + throws ParseException { // loop until an option is found - while( iter.hasNext() ) { - String var = (String)iter.next(); + while (iter.hasNext()) { + String var = (String) iter.next(); // found an Option - if( options.hasOption( var ) ) { + if (options.hasOption(var)) { iter.previous(); break; } // found a value - else if( !opt.addValue( var ) ) { + else if (!opt.addValue(var)) { iter.previous(); break; } } - if( opt.getValues() == null && !opt.hasOptionalArg() ) { - throw new MissingArgumentException( "no argument for:" + opt.getOpt() ); + if (opt.getValues() == null && !opt.hasOptionalArg()) { + throw new MissingArgumentException("no argument for:" + opt.getOpt()); } } - private void processOption( String arg, ListIterator iter ) - throws ParseException - { + private void processOption(String arg, ListIterator iter) + throws ParseException { // get the option represented by arg Option opt = null; - boolean hasOption = options.hasOption( arg ); + boolean hasOption = options.hasOption(arg); // if there is no option throw an UnrecognisedOptionException - if( !hasOption ) { + if (!hasOption) { throw new UnrecognizedOptionException("Unrecognized option: " + arg); - } - else { - opt = (Option) options.getOption( arg ); + } else { + opt = (Option) options.getOption(arg); } // if the option is a required option remove the option from // the requiredOptions list - if ( opt.isRequired() ) { - requiredOptions.remove( "-" + opt.getOpt() ); + if (opt.isRequired()) { + requiredOptions.remove("-" + opt.getOpt()); } // if the option is in an OptionGroup make that option the selected // option of the group - if ( options.getOptionGroup( opt ) != null ) { - OptionGroup group = ( OptionGroup ) options.getOptionGroup( opt ); - if( group.isRequired() ) { - requiredOptions.remove( group ); + if (options.getOptionGroup(opt) != null) { + OptionGroup group = (OptionGroup) options.getOptionGroup(opt); + if (group.isRequired()) { + requiredOptions.remove(group); } - group.setSelected( opt ); + group.setSelected(opt); } // if the option takes an argument value - if ( opt.hasArg() ) { - processArgs( opt, iter ); + if (opt.hasArg()) { + processArgs(opt, iter); } // set the option on the command line - cmd.addOption( opt ); + cmd.addOption(opt); } } \ No newline at end of file diff --git a/src/org/apache/commons/cli/PatternOptionBuilder.java b/src/org/apache/commons/cli/PatternOptionBuilder.java index d544e69..2efd844 100644 --- a/src/org/apache/commons/cli/PatternOptionBuilder.java +++ b/src/org/apache/commons/cli/PatternOptionBuilder.java @@ -61,10 +61,9 @@ package org.apache.commons.cli; -/** +/** * Allows Options to be created from a single String. * - * * @author Henri Yandell (bayard @ generationjava.com) * @version $Revision: 1.2 $ */ @@ -72,28 +71,46 @@ public class PatternOptionBuilder { /// TODO: These need to break out to OptionType and also to be pluggable. - /** String class */ - public static final Class STRING_VALUE = java.lang.String.class; - /** Object class */ - public static final Class OBJECT_VALUE = java.lang.Object.class; - /** Number class */ - public static final Class NUMBER_VALUE = java.lang.Number.class; - /** Date class */ - public static final Class DATE_VALUE = java.util.Date.class; - /** Class class */ - public static final Class CLASS_VALUE = java.lang.Class.class; + /** + * String class + */ + public static final Class STRING_VALUE = java.lang.String.class; + /** + * Object class + */ + public static final Class OBJECT_VALUE = java.lang.Object.class; + /** + * Number class + */ + public static final Class NUMBER_VALUE = java.lang.Number.class; + /** + * Date class + */ + public static final Class DATE_VALUE = java.util.Date.class; + /** + * Class class + */ + public static final Class CLASS_VALUE = java.lang.Class.class; /// can we do this one?? // is meant to check that the file exists, else it errors. // ie) it's for reading not writing. - /** FileInputStream class */ + /** + * FileInputStream class + */ public static final Class EXISTING_FILE_VALUE = java.io.FileInputStream.class; - /** File class */ - public static final Class FILE_VALUE = java.io.File.class; - /** File array class */ - public static final Class FILES_VALUE = java.io.File[].class; - /** URL class */ - public static final Class URL_VALUE = java.net.URL.class; + /** + * File class + */ + public static final Class FILE_VALUE = java.io.File.class; + /** + * File array class + */ + public static final Class FILES_VALUE = java.io.File[].class; + /** + * URL class + */ + public static final Class URL_VALUE = java.net.URL.class; /** *

    Retrieve the class that ch represents.

    @@ -123,33 +140,32 @@ public static Object getValueClass(char ch) { } return null; } - + /** *

    Returns whether ch is a value code, i.e. * whether it represents a class in a pattern.

    - * + * * @param ch the specified character * @return true if ch is a value code, otherwise false. */ public static boolean isValueCode(char ch) { - if( (ch != '@') && - (ch != ':') && - (ch != '%') && - (ch != '+') && - (ch != '#') && - (ch != '<') && - (ch != '>') && - (ch != '*') && - (ch != '/') - ) - { + if ((ch != '@') && + (ch != ':') && + (ch != '%') && + (ch != '+') && + (ch != '#') && + (ch != '<') && + (ch != '>') && + (ch != '*') && + (ch != '/') + ) { return false; } return true; - } - + } + /** - *

    Returns the {@link Options} instance represented by + *

    Returns the {@link Options} instance represented by * pattern.

    * * @param pattern the pattern string @@ -164,38 +180,37 @@ public static Options parsePattern(String pattern) { Object type = null; Options options = new Options(); - - for(int i=0; iAn implementation of {@link Parser}'s abstract - * {@link Parser#flatten(Options,String[],boolean) flatten} method.

    - * + * {@link Parser#flatten(Options, String[], boolean) flatten} method.

    + *

    *

    The following are the rules used by this flatten method. *

      - *
    1. if stopAtNonOption is true then do not - * burst anymore of arguments entries, just add each - * successive entry without further processing. Otherwise, ignore - * stopAtNonOption.
    2. - *
    3. if the current arguments entry is "--" - * just add the entry to the list of processed tokens
    4. - *
    5. if the current arguments entry is "-" - * just add the entry to the list of processed tokens
    6. - *
    7. if the current arguments entry is two characters - * in length and the first character is "-" then check if this - * is a valid {@link Option} id. If it is a valid id, then add the - * entry to the list of processed tokens and set the current {@link Option} - * member. If it is not a valid id and stopAtNonOption - * is true, then the remaining entries are copied to the list of - * processed tokens. Otherwise, the current entry is ignored.
    8. - *
    9. if the current arguments entry is more than two - * characters in length and the first character is "-" then - * we need to burst the entry to determine its constituents. For more - * information on the bursting algorithm see - * {@link PosixParser#burstToken( String, boolean) burstToken}.
    10. - *
    11. if the current arguments entry is not handled - * by any of the previous rules, then the entry is added to the list - * of processed tokens.
    12. + *
    13. if stopAtNonOption is true then do not + * burst anymore of arguments entries, just add each + * successive entry without further processing. Otherwise, ignore + * stopAtNonOption.
    14. + *
    15. if the current arguments entry is "--" + * just add the entry to the list of processed tokens
    16. + *
    17. if the current arguments entry is "-" + * just add the entry to the list of processed tokens
    18. + *
    19. if the current arguments entry is two characters + * in length and the first character is "-" then check if this + * is a valid {@link Option} id. If it is a valid id, then add the + * entry to the list of processed tokens and set the current {@link Option} + * member. If it is not a valid id and stopAtNonOption + * is true, then the remaining entries are copied to the list of + * processed tokens. Otherwise, the current entry is ignored.
    20. + *
    21. if the current arguments entry is more than two + * characters in length and the first character is "-" then + * we need to burst the entry to determine its constituents. For more + * information on the bursting algorithm see + * {@link PosixParser#burstToken(String, boolean) burstToken}.
    22. + *
    23. if the current arguments entry is not handled + * by any of the previous rules, then the entry is added to the list + * of processed tokens.
    24. *
    *

    * - * @param options The command line {@link Options} - * @param arguments The command line arguments to be parsed + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed * @param stopAtNonOption Specifies whether to stop flattening - * when an non option is found. + * when an non option is found. * @return The flattened arguments String array. */ - protected String[] flatten( Options options, - String[] arguments, - boolean stopAtNonOption ) - { + protected String[] flatten(Options options, + String[] arguments, + boolean stopAtNonOption) { init(); this.options = options; // an iterator for the command line tokens - Iterator iter = Arrays.asList( arguments ).iterator(); + Iterator iter = Arrays.asList(arguments).iterator(); String token = null; - + // process each command line token - while ( iter.hasNext() ) { + while (iter.hasNext()) { // get the next command line token token = (String) iter.next(); // handle SPECIAL TOKEN - if( token.startsWith( "--" ) ) { - if( token.indexOf( '=' ) != -1 ) { - tokens.add( token.substring( 0, token.indexOf( '=' ) ) ); - tokens.add( token.substring( token.indexOf( '=' ) + 1, - token.length() ) ); + if (token.startsWith("--")) { + if (token.indexOf('=') != -1) { + tokens.add(token.substring(0, token.indexOf('='))); + tokens.add(token.substring(token.indexOf('=') + 1, + token.length())); + } else { + tokens.add(token); } - else { - tokens.add( token ); - } } // single hyphen - else if( "-".equals( token ) ) { - processSingleHyphen( token ); - } - else if( token.startsWith( "-" ) ) { + else if ("-".equals(token)) { + processSingleHyphen(token); + } else if (token.startsWith("-")) { int tokenLength = token.length(); - if( tokenLength == 2 ) { - processOptionToken( token, stopAtNonOption ); + if (tokenLength == 2) { + processOptionToken(token, stopAtNonOption); } // requires bursting else { - burstToken( token, stopAtNonOption ); + burstToken(token, stopAtNonOption); } - } - else { - if( stopAtNonOption ) { - process( token ); - } - else { - tokens.add( token ); + } else { + if (stopAtNonOption) { + process(token); + } else { + tokens.add(token); } } - gobble( iter ); + gobble(iter); } - return (String[])tokens.toArray( new String[] {} ); + return (String[]) tokens.toArray(new String[]{}); } /** @@ -194,17 +197,17 @@ else if( token.startsWith( "-" ) ) { * * @param iter An iterator over the remaining tokens */ - private void gobble( Iterator iter ) { - if( eatTheRest ) { - while( iter.hasNext() ) { - tokens.add( iter.next() ); + private void gobble(Iterator iter) { + if (eatTheRest) { + while (iter.hasNext()) { + tokens.add(iter.next()); } } } /** *

    If there is a current option and it can have an argument - * value then add the token to the processed tokens list and + * value then add the token to the processed tokens list and * set the current option to null.

    *

    If there is a current option and it can have argument * values then add the token to the processed tokens list.

    @@ -215,20 +218,18 @@ private void gobble( Iterator iter ) { * * @param value The current token */ - private void process( String value ) { - if( currentOption != null && currentOption.hasArg() ) { - if( currentOption.hasArg() ) { - tokens.add( value ); + private void process(String value) { + if (currentOption != null && currentOption.hasArg()) { + if (currentOption.hasArg()) { + tokens.add(value); currentOption = null; + } else if (currentOption.hasArgs()) { + tokens.add(value); } - else if (currentOption.hasArgs() ) { - tokens.add( value ); - } - } - else { + } else { eatTheRest = true; - tokens.add( "--" ); - tokens.add( value ); + tokens.add("--"); + tokens.add(value); } } @@ -238,28 +239,27 @@ else if (currentOption.hasArgs() ) { * * @param hyphen The hyphen token */ - private void processSingleHyphen( String hyphen ) { - tokens.add( hyphen ); + private void processSingleHyphen(String hyphen) { + tokens.add(hyphen); } /** *

    If an {@link Option} exists for token then - * set the current option and add the token to the processed + * set the current option and add the token to the processed * list.

    *

    If an {@link Option} does not exist and stopAtNonOption * is set then ignore the current token and add the remaining tokens * to the processed tokens list directly.

    * - * @param token The current option token + * @param token The current option token * @param stopAtNonOption Specifies whether flattening should halt - * at the first non option. + * at the first non option. */ - private void processOptionToken( String token, boolean stopAtNonOption ) { - if( this.options.hasOption( token ) ) { - currentOption = this.options.getOption( token ); - tokens.add( token ); - } - else if( stopAtNonOption ) { + private void processOptionToken(String token, boolean stopAtNonOption) { + if (this.options.hasOption(token)) { + currentOption = this.options.getOption(token); + tokens.add(token); + } else if (stopAtNonOption) { eatTheRest = true; } } @@ -268,44 +268,42 @@ else if( stopAtNonOption ) { *

    Breaks token into its constituent parts * using the following algorithm. *

      - *
    • ignore the first character ("-" )
    • - *
    • foreach remaining character check if an {@link Option} - * exists with that id.
    • - *
    • if an {@link Option} does exist then add that character - * prepended with "-" to the list of processed tokens.
    • - *
    • if the {@link Option} can have an argument value and there - * are remaining characters in the token then add the remaining - * characters as a token to the list of processed tokens.
    • - *
    • if an {@link Option} does NOT exist AND - * stopAtNonOption IS set then add the special token - * "--" followed by the remaining characters and also - * the remaining tokens directly to the processed tokens list.
    • - *
    • if an {@link Option} does NOT exist AND - * stopAtNonOption IS NOT set then add that - * character prepended with "-".
    • + *
    • ignore the first character ("-" )
    • + *
    • foreach remaining character check if an {@link Option} + * exists with that id.
    • + *
    • if an {@link Option} does exist then add that character + * prepended with "-" to the list of processed tokens.
    • + *
    • if the {@link Option} can have an argument value and there + * are remaining characters in the token then add the remaining + * characters as a token to the list of processed tokens.
    • + *
    • if an {@link Option} does NOT exist AND + * stopAtNonOption IS set then add the special token + * "--" followed by the remaining characters and also + * the remaining tokens directly to the processed tokens list.
    • + *
    • if an {@link Option} does NOT exist AND + * stopAtNonOption IS NOT set then add that + * character prepended with "-".
    • *
    *

    */ - protected void burstToken( String token, boolean stopAtNonOption ) { + protected void burstToken(String token, boolean stopAtNonOption) { int tokenLength = token.length(); - for( int i = 1; i < tokenLength; i++) { - String ch = String.valueOf( token.charAt( i ) ); - boolean hasOption = options.hasOption( ch ); + for (int i = 1; i < tokenLength; i++) { + String ch = String.valueOf(token.charAt(i)); + boolean hasOption = options.hasOption(ch); - if( hasOption ) { - tokens.add( "-" + ch ); - currentOption = options.getOption( ch ); - if( currentOption.hasArg() && token.length()!=i+1 ) { - tokens.add( token.substring( i+1 ) ); + if (hasOption) { + tokens.add("-" + ch); + currentOption = options.getOption(ch); + if (currentOption.hasArg() && token.length() != i + 1) { + tokens.add(token.substring(i + 1)); break; } - } - else if( stopAtNonOption ) { - process( token.substring( i ) ); - } - else { - tokens.add( "-" + ch ); + } else if (stopAtNonOption) { + process(token.substring(i)); + } else { + tokens.add("-" + ch); } } } diff --git a/src/org/apache/commons/cli/TypeHandler.java b/src/org/apache/commons/cli/TypeHandler.java index 22011e9..3cd053c 100644 --- a/src/org/apache/commons/cli/TypeHandler.java +++ b/src/org/apache/commons/cli/TypeHandler.java @@ -61,22 +61,22 @@ package org.apache.commons.cli; +import org.apache.commons.lang.math.NumberUtils; + import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.Date; -import org.apache.commons.lang.math.NumberUtils; - /** - * This is a temporary implementation. TypeHandler will handle the - * pluggableness of OptionTypes and it will direct all of these types - * of conversion functionalities to ConvertUtils component in Commons - * alreayd. BeanUtils I think. - * - * @author Henri Yandell (bayard @ generationjava.com) - * @version $Revision: 1.2 $ - */ + * This is a temporary implementation. TypeHandler will handle the + * pluggableness of OptionTypes and it will direct all of these types + * of conversion functionalities to ConvertUtils component in Commons + * alreayd. BeanUtils I think. + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 1.2 $ + */ public class TypeHandler { /** @@ -89,44 +89,36 @@ public class TypeHandler { * the value of str. */ public static Object createValue(String str, Object obj) { - return createValue(str, (Class)obj); + return createValue(str, (Class) obj); } /** *

    Returns the Object of type clazz * with the value of str.

    * - * @param str the command line value + * @param str the command line value * @param clazz the type of argument * @return The instance of clazz initialised with * the value of str. */ public static Object createValue(String str, Class clazz) { - if( PatternOptionBuilder.STRING_VALUE == clazz) { + if (PatternOptionBuilder.STRING_VALUE == clazz) { return str; - } else - if( PatternOptionBuilder.OBJECT_VALUE == clazz) { + } else if (PatternOptionBuilder.OBJECT_VALUE == clazz) { return createObject(str); - } else - if( PatternOptionBuilder.NUMBER_VALUE == clazz) { + } else if (PatternOptionBuilder.NUMBER_VALUE == clazz) { return createNumber(str); - } else - if( PatternOptionBuilder.DATE_VALUE == clazz) { + } else if (PatternOptionBuilder.DATE_VALUE == clazz) { return createDate(str); - } else - if( PatternOptionBuilder.CLASS_VALUE == clazz) { + } else if (PatternOptionBuilder.CLASS_VALUE == clazz) { return createClass(str); - } else - if( PatternOptionBuilder.FILE_VALUE == clazz) { + } else if (PatternOptionBuilder.FILE_VALUE == clazz) { return createFile(str); - } else - if( PatternOptionBuilder.EXISTING_FILE_VALUE == clazz) { + } else if (PatternOptionBuilder.EXISTING_FILE_VALUE == clazz) { return createFile(str); - } else - if( PatternOptionBuilder.FILES_VALUE == clazz) { + } else if (PatternOptionBuilder.FILES_VALUE == clazz) { return createFiles(str); - } else - if( PatternOptionBuilder.URL_VALUE == clazz) { + } else if (PatternOptionBuilder.URL_VALUE == clazz) { return createURL(str); } else { return null; @@ -134,17 +126,17 @@ public static Object createValue(String str, Class clazz) { } /** - *

    Create an Object from the classname and empty constructor.

    - * - * @param str the argument value - * @return the initialised object, or null if it couldn't create the Object. - */ + *

    Create an Object from the classname and empty constructor.

    + * + * @param str the argument value + * @return the initialised object, or null if it couldn't create the Object. + */ public static Object createObject(String str) { Class cl = null; try { cl = Class.forName(str); } catch (ClassNotFoundException cnfe) { - System.err.println("Unable to find: "+str); + System.err.println("Unable to find: " + str); return null; } @@ -153,11 +145,10 @@ public static Object createObject(String str) { try { instance = cl.newInstance(); } catch (InstantiationException cnfe) { - System.err.println("InstantiationException; Unable to create: "+str); + System.err.println("InstantiationException; Unable to create: " + str); return null; - } - catch (IllegalAccessException cnfe) { - System.err.println("IllegalAccessException; Unable to create: "+str); + } catch (IllegalAccessException cnfe) { + System.err.println("IllegalAccessException; Unable to create: " + str); return null; } @@ -192,7 +183,7 @@ public static Class createClass(String str) { try { return Class.forName(str); } catch (ClassNotFoundException cnfe) { - System.err.println("Unable to find: "+str); + System.err.println("Unable to find: " + str); return null; } } @@ -206,8 +197,8 @@ public static Class createClass(String str) { */ public static Date createDate(String str) { Date date = null; - if(date == null) { - System.err.println("Unable to parse: "+str); + if (date == null) { + System.err.println("Unable to parse: " + str); } return date; } @@ -223,7 +214,7 @@ public static URL createURL(String str) { try { return new URL(str); } catch (MalformedURLException mue) { - System.err.println("Unable to parse: "+str); + System.err.println("Unable to parse: " + str); return null; } } diff --git a/src/org/apache/commons/cli/UnrecognizedOptionException.java b/src/org/apache/commons/cli/UnrecognizedOptionException.java index 553efc3..fb337db 100644 --- a/src/org/apache/commons/cli/UnrecognizedOptionException.java +++ b/src/org/apache/commons/cli/UnrecognizedOptionException.java @@ -61,7 +61,7 @@ package org.apache.commons.cli; -/** +/** *

    Exception thrown during parsing signalling an unrecognized * option was seen.

    * @@ -69,14 +69,14 @@ * @version $Revision: 1.2 $ */ public class UnrecognizedOptionException extends ParseException { - - /** - *

    Construct a new UnrecognizedArgumentException + + /** + *

    Construct a new UnrecognizedArgumentException * with the specified detail message.

    * * @param message the detail message */ - public UnrecognizedOptionException( String message ) { - super( message ); + public UnrecognizedOptionException(String message) { + super(message); } } diff --git a/src/org/apache/commons/cli/overview.html b/src/org/apache/commons/cli/overview.html index eadba1e..e25a2fc 100644 --- a/src/org/apache/commons/cli/overview.html +++ b/src/org/apache/commons/cli/overview.html @@ -1,28 +1,27 @@ - - -

    Commons CLI -- version ##VERSION## (##QUALITY##)

    -

    The commons-cli package aides in parsing command-line arguments.

    +

    Commons CLI -- version ##VERSION## (##QUALITY##)

    + +

    The commons-cli package aides in parsing command-line arguments.

    -

    Allow command-line arguments to be parsed against a descriptor of - valid options (long and short), potentially with arguments.

    +

    Allow command-line arguments to be parsed against a descriptor of + valid options (long and short), potentially with arguments.

    -

    command-line arguments may be of the typical String[] - form, but also may be a java.util.List. Indexes allow - for parsing only a portion of the command-line. Also, functionality - for parsing the command-line in phases is built in, allowing for - 'cvs-style' command-lines, where some global options are specified - before a 'command' argument, and command-specific options are - specified after the command argument: - - +

    command-line arguments may be of the typical String[] + form, but also may be a java.util.List. Indexes allow + for parsing only a portion of the command-line. Also, functionality + for parsing the command-line in phases is built in, allowing for + 'cvs-style' command-lines, where some global options are specified + before a 'command' argument, and command-specific options are + specified after the command argument: + +

     		myApp -p <port> command -p <printer>
     	
    - - + + -

    The homepage for the project is - jakarta commons/ +

    The homepage for the project is + jakarta commons/ diff --git a/src/org/apache/commons/cli/package.html b/src/org/apache/commons/cli/package.html index 5dcaef1..cec7304 100644 --- a/src/org/apache/commons/cli/package.html +++ b/src/org/apache/commons/cli/package.html @@ -1,6 +1,5 @@ - -

    Commons CLI 1.0

    +

    Commons CLI 1.0

    diff --git a/src/org/apache/commons/lang/math/NumberUtils.java b/src/org/apache/commons/lang/math/NumberUtils.java index cc39663..a8051e0 100644 --- a/src/org/apache/commons/lang/math/NumberUtils.java +++ b/src/org/apache/commons/lang/math/NumberUtils.java @@ -31,52 +31,88 @@ * @author Matthew Hawthorne * @author Gary Gregory * @author Fredrik Westermarck - * @since 2.0 * @version $Id: NumberUtils.java 437554 2006-08-28 06:21:41Z bayard $ + * @since 2.0 */ public class NumberUtils { - - /** Reusable Long constant for zero. */ + + /** + * Reusable Long constant for zero. + */ public static final Long LONG_ZERO = new Long(0L); - /** Reusable Long constant for one. */ + /** + * Reusable Long constant for one. + */ public static final Long LONG_ONE = new Long(1L); - /** Reusable Long constant for minus one. */ + /** + * Reusable Long constant for minus one. + */ public static final Long LONG_MINUS_ONE = new Long(-1L); - /** Reusable Integer constant for zero. */ + /** + * Reusable Integer constant for zero. + */ public static final Integer INTEGER_ZERO = new Integer(0); - /** Reusable Integer constant for one. */ + /** + * Reusable Integer constant for one. + */ public static final Integer INTEGER_ONE = new Integer(1); - /** Reusable Integer constant for minus one. */ + /** + * Reusable Integer constant for minus one. + */ public static final Integer INTEGER_MINUS_ONE = new Integer(-1); - /** Reusable Short constant for zero. */ + /** + * Reusable Short constant for zero. + */ public static final Short SHORT_ZERO = new Short((short) 0); - /** Reusable Short constant for one. */ + /** + * Reusable Short constant for one. + */ public static final Short SHORT_ONE = new Short((short) 1); - /** Reusable Short constant for minus one. */ + /** + * Reusable Short constant for minus one. + */ public static final Short SHORT_MINUS_ONE = new Short((short) -1); - /** Reusable Byte constant for zero. */ + /** + * Reusable Byte constant for zero. + */ public static final Byte BYTE_ZERO = new Byte((byte) 0); - /** Reusable Byte constant for one. */ + /** + * Reusable Byte constant for one. + */ public static final Byte BYTE_ONE = new Byte((byte) 1); - /** Reusable Byte constant for minus one. */ + /** + * Reusable Byte constant for minus one. + */ public static final Byte BYTE_MINUS_ONE = new Byte((byte) -1); - /** Reusable Double constant for zero. */ + /** + * Reusable Double constant for zero. + */ public static final Double DOUBLE_ZERO = new Double(0.0d); - /** Reusable Double constant for one. */ + /** + * Reusable Double constant for one. + */ public static final Double DOUBLE_ONE = new Double(1.0d); - /** Reusable Double constant for minus one. */ + /** + * Reusable Double constant for minus one. + */ public static final Double DOUBLE_MINUS_ONE = new Double(-1.0d); - /** Reusable Float constant for zero. */ + /** + * Reusable Float constant for zero. + */ public static final Float FLOAT_ZERO = new Float(0.0f); - /** Reusable Float constant for one. */ + /** + * Reusable Float constant for one. + */ public static final Float FLOAT_ONE = new Float(1.0f); - /** Reusable Float constant for minus one. */ + /** + * Reusable Float constant for minus one. + */ public static final Float FLOAT_MINUS_ONE = new Float(-1.0f); /** *

    NumberUtils instances should NOT be constructed in standard programming. * Instead, the class should be used as NumberUtils.stringToInt("6");.

    - * + *

    *

    This constructor is public to permit tools that require a JavaBean instance * to operate.

    */ @@ -85,23 +121,24 @@ public NumberUtils() { } //----------------------------------------------------------------------- + /** *

    Convert a String to an int, returning * zero if the conversion fails.

    - * + *

    *

    If the string is null, zero is returned.

    - * + *

    *

          *   NumberUtils.stringToInt(null) = 0
          *   NumberUtils.stringToInt("")   = 0
          *   NumberUtils.stringToInt("1")  = 1
          * 
    * - * @param str the string to convert, may be null + * @param str the string to convert, may be null * @return the int represented by the string, or zero if - * conversion fails + * conversion fails * @deprecated Use {@link #toInt(String)} - * This method will be removed in Commons Lang 3.0 + * This method will be removed in Commons Lang 3.0 */ public static int stringToInt(String str) { return toInt(str); @@ -110,18 +147,18 @@ public static int stringToInt(String str) { /** *

    Convert a String to an int, returning * zero if the conversion fails.

    - * + *

    *

    If the string is null, zero is returned.

    - * + *

    *

          *   NumberUtils.toInt(null) = 0
          *   NumberUtils.toInt("")   = 0
          *   NumberUtils.toInt("1")  = 1
          * 
    * - * @param str the string to convert, may be null + * @param str the string to convert, may be null * @return the int represented by the string, or zero if - * conversion fails + * conversion fails * @since 2.1 */ public static int toInt(String str) { @@ -131,20 +168,20 @@ public static int toInt(String str) { /** *

    Convert a String to an int, returning a * default value if the conversion fails.

    - * + *

    *

    If the string is null, the default value is returned.

    - * + *

    *

          *   NumberUtils.stringToInt(null, 1) = 1
          *   NumberUtils.stringToInt("", 1)   = 1
          *   NumberUtils.stringToInt("1", 0)  = 1
          * 
    * - * @param str the string to convert, may be null - * @param defaultValue the default value + * @param str the string to convert, may be null + * @param defaultValue the default value * @return the int represented by the string, or the default if conversion fails * @deprecated Use {@link #toInt(String, int)} - * This method will be removed in Commons Lang 3.0 + * This method will be removed in Commons Lang 3.0 */ public static int stringToInt(String str, int defaultValue) { return toInt(str, defaultValue); @@ -153,22 +190,22 @@ public static int stringToInt(String str, int defaultValue) { /** *

    Convert a String to an int, returning a * default value if the conversion fails.

    - * + *

    *

    If the string is null, the default value is returned.

    - * + *

    *

          *   NumberUtils.toInt(null, 1) = 1
          *   NumberUtils.toInt("", 1)   = 1
          *   NumberUtils.toInt("1", 0)  = 1
          * 
    * - * @param str the string to convert, may be null - * @param defaultValue the default value + * @param str the string to convert, may be null + * @param defaultValue the default value * @return the int represented by the string, or the default if conversion fails * @since 2.1 */ public static int toInt(String str, int defaultValue) { - if(str == null) { + if (str == null) { return defaultValue; } try { @@ -181,18 +218,18 @@ public static int toInt(String str, int defaultValue) { /** *

    Convert a String to a long, returning * zero if the conversion fails.

    - * + *

    *

    If the string is null, zero is returned.

    - * + *

    *

          *   NumberUtils.toLong(null) = 0L
          *   NumberUtils.toLong("")   = 0L
          *   NumberUtils.toLong("1")  = 1L
          * 
    * - * @param str the string to convert, may be null + * @param str the string to convert, may be null * @return the long represented by the string, or 0 if - * conversion fails + * conversion fails * @since 2.1 */ public static long toLong(String str) { @@ -202,17 +239,17 @@ public static long toLong(String str) { /** *

    Convert a String to a long, returning a * default value if the conversion fails.

    - * + *

    *

    If the string is null, the default value is returned.

    - * + *

    *

          *   NumberUtils.toLong(null, 1L) = 1L
          *   NumberUtils.toLong("", 1L)   = 1L
          *   NumberUtils.toLong("1", 0L)  = 1L
          * 
    * - * @param str the string to convert, may be null - * @param defaultValue the default value + * @param str the string to convert, may be null + * @param defaultValue the default value * @return the long represented by the string, or the default if conversion fails * @since 2.1 */ @@ -230,10 +267,10 @@ public static long toLong(String str, long defaultValue) { /** *

    Convert a String to a float, returning * 0.0f if the conversion fails.

    - * + *

    *

    If the string str is null, * 0.0f is returned.

    - * + *

    *

          *   NumberUtils.toFloat(null)   = 0.0f
          *   NumberUtils.toFloat("")     = 0.0f
    @@ -242,7 +279,7 @@ public static long toLong(String str, long defaultValue) {
          *
          * @param str the string to convert, may be null
          * @return the float represented by the string, or 0.0f
    -     *  if conversion fails
    +     * if conversion fails
          * @since 2.1
          */
         public static float toFloat(String str) {
    @@ -252,40 +289,40 @@ public static float toFloat(String str) {
         /**
          * 

    Convert a String to a float, returning a * default value if the conversion fails.

    - * + *

    *

    If the string str is null, the default * value is returned.

    - * + *

    *

          *   NumberUtils.toFloat(null, 1.1f)   = 1.0f
          *   NumberUtils.toFloat("", 1.1f)     = 1.1f
          *   NumberUtils.toFloat("1.5", 0.0f)  = 1.5f
          * 
    * - * @param str the string to convert, may be null + * @param str the string to convert, may be null * @param defaultValue the default value * @return the float represented by the string, or defaultValue - * if conversion fails + * if conversion fails * @since 2.1 */ public static float toFloat(String str, float defaultValue) { - if (str == null) { - return defaultValue; - } - try { - return Float.parseFloat(str); - } catch (NumberFormatException nfe) { - return defaultValue; - } + if (str == null) { + return defaultValue; + } + try { + return Float.parseFloat(str); + } catch (NumberFormatException nfe) { + return defaultValue; + } } /** *

    Convert a String to a double, returning * 0.0d if the conversion fails.

    - * + *

    *

    If the string str is null, * 0.0d is returned.

    - * + *

    *

          *   NumberUtils.toDouble(null)   = 0.0d
          *   NumberUtils.toDouble("")     = 0.0d
    @@ -294,7 +331,7 @@ public static float toFloat(String str, float defaultValue) {
          *
          * @param str the string to convert, may be null
          * @return the double represented by the string, or 0.0d
    -     *  if conversion fails
    +     * if conversion fails
          * @since 2.1
          */
         public static double toDouble(String str) {
    @@ -304,31 +341,31 @@ public static double toDouble(String str) {
         /**
          * 

    Convert a String to a double, returning a * default value if the conversion fails.

    - * + *

    *

    If the string str is null, the default * value is returned.

    - * + *

    *

          *   NumberUtils.toDouble(null, 1.1d)   = 1.1d
          *   NumberUtils.toDouble("", 1.1d)     = 1.1d
          *   NumberUtils.toDouble("1.5", 0.0d)  = 1.5d
          * 
    * - * @param str the string to convert, may be null + * @param str the string to convert, may be null * @param defaultValue the default value * @return the double represented by the string, or defaultValue - * if conversion fails + * if conversion fails * @since 2.1 */ public static double toDouble(String str, double defaultValue) { - if (str == null) { - return defaultValue; - } - try { - return Double.parseDouble(str); - } catch (NumberFormatException nfe) { - return defaultValue; - } + if (str == null) { + return defaultValue; + } + try { + return Double.parseDouble(str); + } catch (NumberFormatException nfe) { + return defaultValue; + } } //----------------------------------------------------------------------- @@ -369,27 +406,27 @@ public static double toDouble(String str, double defaultValue) { /** *

    Turns a string value into a java.lang.Number.

    - * + *

    *

    First, the value is examined for a type qualifier on the end - * ('f','F','d','D','l','L'). If it is found, it starts + * ('f','F','d','D','l','L'). If it is found, it starts * trying to create successively larger types from the type specified * until one is found that can represent the value.

    - * + *

    *

    If a type specifier is not found, it will check for a decimal point * and then try successively larger types from Integer to * BigInteger and from Float to * BigDecimal.

    - * + *

    *

    If the string starts with 0x or -0x, it * will be interpreted as a hexadecimal integer. Values with leading * 0's will not be interpreted as octal.

    - * + *

    *

    Returns null if the string is null.

    - * + *

    *

    This method does not trim the input string, i.e., strings with leading * or trailing spaces will generate NumberFormatExceptions.

    * - * @param str String containing a number, may be null + * @param str String containing a number, may be null * @return Number created from the string * @throws NumberFormatException if the value cannot be converted */ @@ -399,7 +436,7 @@ public static Number createNumber(String str) throws NumberFormatException { } if (str == null || str.trim().length() == 0) { throw new NumberFormatException("A blank string is not a valid number"); - } + } if (str.startsWith("--")) { // this is protection for poorness in java.lang.BigDecimal. // it accepts this as a legal value, but it does not appear @@ -409,7 +446,7 @@ public static Number createNumber(String str) throws NumberFormatException { } if (str.startsWith("0x") || str.startsWith("-0x")) { return createInteger(str); - } + } char lastChar = str.charAt(str.length() - 1); String mant; String dec; @@ -446,12 +483,12 @@ public static Number createNumber(String str) throws NumberFormatException { String numeric = str.substring(0, str.length() - 1); boolean allZeros = isAllZeros(mant) && isAllZeros(exp); switch (lastChar) { - case 'l' : - case 'L' : + case 'l': + case 'L': if (dec == null - && exp == null - && isDigits(numeric.substring(1)) - && (numeric.charAt(0) == '-' || Character.isDigit(numeric.charAt(0)))) { + && exp == null + && isDigits(numeric.substring(1)) + && (numeric.charAt(0) == '-' || Character.isDigit(numeric.charAt(0)))) { try { return createLong(numeric); } catch (NumberFormatException nfe) { @@ -461,8 +498,8 @@ && isDigits(numeric.substring(1)) } throw new NumberFormatException(str + " is not a valid number."); - case 'f' : - case 'F' : + case 'f': + case 'F': try { Float f = NumberUtils.createFloat(numeric); if (!(f.isInfinite() || (f.floatValue() == 0.0F && !allZeros))) { @@ -475,8 +512,8 @@ && isDigits(numeric.substring(1)) // ignore the bad number } //Fall through - case 'd' : - case 'D' : + case 'd': + case 'D': try { Double d = NumberUtils.createDouble(numeric); if (!(d.isInfinite() || (d.floatValue() == 0.0D && !allZeros))) { @@ -491,7 +528,7 @@ && isDigits(numeric.substring(1)) // ignore the bad number } //Fall through - default : + default: throw new NumberFormatException(str + " is not a valid number."); } @@ -545,10 +582,10 @@ && isDigits(numeric.substring(1)) /** *

    Utility method for {@link #createNumber(java.lang.String)}.

    - * + *

    *

    Returns true if s is null.

    - * - * @param str the String to check + * + * @param str the String to check * @return if it is all zeros or null */ private static boolean isAllZeros(String str) { @@ -564,12 +601,13 @@ private static boolean isAllZeros(String str) { } //----------------------------------------------------------------------- + /** *

    Convert a String to a Float.

    - * + *

    *

    Returns null if the string is null.

    - * - * @param str a String to convert, may be null + * + * @param str a String to convert, may be null * @return converted Float * @throws NumberFormatException if the value cannot be converted */ @@ -582,10 +620,10 @@ public static Float createFloat(String str) { /** *

    Convert a String to a Double.

    - * + *

    *

    Returns null if the string is null.

    * - * @param str a String to convert, may be null + * @param str a String to convert, may be null * @return converted Double * @throws NumberFormatException if the value cannot be converted */ @@ -599,10 +637,10 @@ public static Double createDouble(String str) { /** *

    Convert a String to a Integer, handling * hex and octal notations.

    - * + *

    *

    Returns null if the string is null.

    - * - * @param str a String to convert, may be null + * + * @param str a String to convert, may be null * @return converted Integer * @throws NumberFormatException if the value cannot be converted */ @@ -616,10 +654,10 @@ public static Integer createInteger(String str) { /** *

    Convert a String to a Long.

    - * + *

    *

    Returns null if the string is null.

    * - * @param str a String to convert, may be null + * @param str a String to convert, may be null * @return converted Long * @throws NumberFormatException if the value cannot be converted */ @@ -632,10 +670,10 @@ public static Long createLong(String str) { /** *

    Convert a String to a BigInteger.

    - * + *

    *

    Returns null if the string is null.

    - * - * @param str a String to convert, may be null + * + * @param str a String to convert, may be null * @return converted BigInteger * @throws NumberFormatException if the value cannot be converted */ @@ -648,10 +686,10 @@ public static BigInteger createBigInteger(String str) { /** *

    Convert a String to a BigDecimal.

    - * + *

    *

    Returns null if the string is null.

    * - * @param str a String to convert, may be null + * @param str a String to convert, may be null * @return converted BigDecimal * @throws NumberFormatException if the value cannot be converted */ @@ -662,16 +700,17 @@ public static BigDecimal createBigDecimal(String str) { // handle JDK1.3.1 bug where "" throws IndexOutOfBoundsException if (str == null || str.trim().length() == 0) { throw new NumberFormatException("A blank string is not a valid number"); - } + } return new BigDecimal(str); } // Min in array //-------------------------------------------------------------------- + /** *

    Returns the minimum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -683,7 +722,7 @@ public static long min(long[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns min long min = array[0]; for (int i = 1; i < array.length; i++) { @@ -691,14 +730,14 @@ public static long min(long[] array) { min = array[i]; } } - + return min; } /** *

    Returns the minimum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -710,7 +749,7 @@ public static int min(int[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns min int min = array[0]; for (int j = 1; j < array.length; j++) { @@ -718,14 +757,14 @@ public static int min(int[] array) { min = array[j]; } } - + return min; } /** *

    Returns the minimum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -737,7 +776,7 @@ public static short min(short[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns min short min = array[0]; for (int i = 1; i < array.length; i++) { @@ -745,14 +784,14 @@ public static short min(short[] array) { min = array[i]; } } - + return min; } - /** + /** *

    Returns the minimum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -764,7 +803,7 @@ public static double min(double[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns min double min = array[0]; for (int i = 1; i < array.length; i++) { @@ -772,14 +811,14 @@ public static double min(double[] array) { min = array[i]; } } - + return min; } /** *

    Returns the minimum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -791,7 +830,7 @@ public static float min(float[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns min float min = array[0]; for (int i = 1; i < array.length; i++) { @@ -799,16 +838,17 @@ public static float min(float[] array) { min = array[i]; } } - + return min; } // Max in array //-------------------------------------------------------------------- + /** *

    Returns the maximum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -834,8 +874,8 @@ public static long max(long[] array) { /** *

    Returns the maximum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -847,7 +887,7 @@ public static int max(int[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns max int max = array[0]; for (int j = 1; j < array.length; j++) { @@ -855,14 +895,14 @@ public static int max(int[] array) { max = array[j]; } } - + return max; } /** *

    Returns the maximum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -874,7 +914,7 @@ public static short max(short[] array) { } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns max short max = array[0]; for (int i = 1; i < array.length; i++) { @@ -882,26 +922,26 @@ public static short max(short[] array) { max = array[i]; } } - + return max; } /** *

    Returns the maximum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty */ public static double max(double[] array) { // Validates input - if (array== null) { + if (array == null) { throw new IllegalArgumentException("The Array must not be null"); } else if (array.length == 0) { throw new IllegalArgumentException("Array cannot be empty."); } - + // Finds and returns max double max = array[0]; for (int j = 1; j < array.length; j++) { @@ -909,14 +949,14 @@ public static double max(double[] array) { max = array[j]; } } - + return max; } /** *

    Returns the maximum value in an array.

    - * - * @param array an array, must not be null or empty + * + * @param array an array, must not be null or empty * @return the minimum value in the array * @throws IllegalArgumentException if array is null * @throws IllegalArgumentException if array is empty @@ -939,16 +979,17 @@ public static float max(float[] array) { return max; } - + // 3 param min //----------------------------------------------------------------------- + /** *

    Gets the minimum of three long values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values */ public static long min(long a, long b, long c) { if (b < a) { @@ -962,11 +1003,11 @@ public static long min(long a, long b, long c) { /** *

    Gets the minimum of three int values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values */ public static int min(int a, int b, int c) { if (b < a) { @@ -980,11 +1021,11 @@ public static int min(int a, int b, int c) { /** *

    Gets the minimum of three short values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values */ public static short min(short a, short b, short c) { if (b < a) { @@ -998,11 +1039,11 @@ public static short min(short a, short b, short c) { /** *

    Gets the minimum of three byte values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values */ public static byte min(byte a, byte b, byte c) { if (b < a) { @@ -1016,14 +1057,14 @@ public static byte min(byte a, byte b, byte c) { /** *

    Gets the minimum of three double values.

    - * + *

    *

    If any value is NaN, NaN is * returned. Infinity is handled.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values */ public static double min(double a, double b, double c) { return Math.min(Math.min(a, b), c); @@ -1031,14 +1072,14 @@ public static double min(double a, double b, double c) { /** *

    Gets the minimum of three float values.

    - * + *

    *

    If any value is NaN, NaN is * returned. Infinity is handled.

    * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the smallest of the values + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values */ public static float min(float a, float b, float c) { return Math.min(Math.min(a, b), c); @@ -1046,13 +1087,14 @@ public static float min(float a, float b, float c) { // 3 param max //----------------------------------------------------------------------- + /** *

    Gets the maximum of three long values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values */ public static long max(long a, long b, long c) { if (b > a) { @@ -1066,11 +1108,11 @@ public static long max(long a, long b, long c) { /** *

    Gets the maximum of three int values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values */ public static int max(int a, int b, int c) { if (b > a) { @@ -1084,11 +1126,11 @@ public static int max(int a, int b, int c) { /** *

    Gets the maximum of three short values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values */ public static short max(short a, short b, short c) { if (b > a) { @@ -1102,11 +1144,11 @@ public static short max(short a, short b, short c) { /** *

    Gets the maximum of three byte values.

    - * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values + * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values */ public static byte max(byte a, byte b, byte c) { if (b > a) { @@ -1120,14 +1162,14 @@ public static byte max(byte a, byte b, byte c) { /** *

    Gets the maximum of three double values.

    - * + *

    *

    If any value is NaN, NaN is * returned. Infinity is handled.

    * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values */ public static double max(double a, double b, double c) { return Math.max(Math.max(a, b), c); @@ -1135,53 +1177,54 @@ public static double max(double a, double b, double c) { /** *

    Gets the maximum of three float values.

    - * + *

    *

    If any value is NaN, NaN is * returned. Infinity is handled.

    * - * @param a value 1 - * @param b value 2 - * @param c value 3 - * @return the largest of the values + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values */ public static float max(float a, float b, float c) { return Math.max(Math.max(a, b), c); } //----------------------------------------------------------------------- + /** *

    Compares two doubles for order.

    - * + *

    *

    This method is more comprehensive than the standard Java greater * than, less than and equals operators.

    *
      - *
    • It returns -1 if the first value is less than the second.
    • - *
    • It returns +1 if the first value is greater than the second.
    • - *
    • It returns 0 if the values are equal.
    • + *
    • It returns -1 if the first value is less than the second.
    • + *
    • It returns +1 if the first value is greater than the second.
    • + *
    • It returns 0 if the values are equal.
    • *
    - * + *

    *

    * The ordering is as follows, largest to smallest: *

      - *
    • NaN - *
    • Positive infinity - *
    • Maximum double - *
    • Normal positive numbers - *
    • +0.0 - *
    • -0.0 - *
    • Normal negative numbers - *
    • Minimum double (-Double.MAX_VALUE) - *
    • Negative infinity + *
    • NaN + *
    • Positive infinity + *
    • Maximum double + *
    • Normal positive numbers + *
    • +0.0 + *
    • -0.0 + *
    • Normal negative numbers + *
    • Minimum double (-Double.MAX_VALUE) + *
    • Negative infinity *
    *

    - * + *

    *

    Comparing NaN with NaN will * return 0.

    - * - * @param lhs the first double - * @param rhs the second double + * + * @param lhs the first double + * @param rhs the second double * @return -1 if lhs is less, +1 if greater, - * 0 if equal to rhs + * 0 if equal to rhs */ public static int compare(double lhs, double rhs) { if (lhs < rhs) { @@ -1211,18 +1254,18 @@ public static int compare(double lhs, double rhs) { return +1; } } - + /** *

    Compares two floats for order.

    - * + *

    *

    This method is more comprehensive than the standard Java greater than, * less than and equals operators.

    *
      - *
    • It returns -1 if the first value is less than the second. - *
    • It returns +1 if the first value is greater than the second. - *
    • It returns 0 if the values are equal. + *
    • It returns -1 if the first value is less than the second. + *
    • It returns +1 if the first value is greater than the second. + *
    • It returns 0 if the values are equal. *
    - * + *

    *

    The ordering is as follows, largest to smallest: *

      *
    • NaN @@ -1235,14 +1278,14 @@ public static int compare(double lhs, double rhs) { *
    • Minimum float (-Float.MAX_VALUE) *
    • Negative infinity *
    - * + *

    *

    Comparing NaN with NaN will return * 0.

    - * - * @param lhs the first float - * @param rhs the second float + * + * @param lhs the first float + * @param rhs the second float * @return -1 if lhs is less, +1 if greater, - * 0 if equal to rhs + * 0 if equal to rhs */ public static int compare(float lhs, float rhs) { if (lhs < rhs) { @@ -1272,16 +1315,17 @@ public static int compare(float lhs, float rhs) { return +1; } } - + //----------------------------------------------------------------------- + /** *

    Checks whether the String contains only * digit characters.

    - * + *

    *

    Null and empty String will return * false.

    * - * @param str the String to check + * @param str the String to check * @return true if str contains only unicode numeric */ public static boolean isDigits(String str) { @@ -1298,15 +1342,15 @@ public static boolean isDigits(String str) { /** *

    Checks whether the String a valid Java number.

    - * + *

    *

    Valid numbers include hexadecimal marked with the 0x * qualifier, scientific notation and numbers marked with a type * qualifier (e.g. 123L).

    - * + *

    *

    Null and empty String will return * false.

    * - * @param str the String to check + * @param str the String to check * @return true if the string is a correctly formatted number */ public static boolean isNumber(String str) { @@ -1330,8 +1374,8 @@ public static boolean isNumber(String str) { // checking hex (it can't be anything else) for (; i < chars.length; i++) { if ((chars[i] < '0' || chars[i] > '9') - && (chars[i] < 'a' || chars[i] > 'f') - && (chars[i] < 'A' || chars[i] > 'F')) { + && (chars[i] < 'a' || chars[i] > 'f') + && (chars[i] < 'A' || chars[i] > 'F')) { return false; } } @@ -1339,7 +1383,7 @@ public static boolean isNumber(String str) { } } sz--; // don't want to loop to the last char, check it afterwords - // for type qualifiers + // for type qualifiers int i = start; // loop to the next to last char or to the last char if we need another digit to // make a valid number (e.g. chars[0..5] = "1234E") @@ -1386,14 +1430,14 @@ public static boolean isNumber(String str) { return false; } if (!allowSigns - && (chars[i] == 'd' + && (chars[i] == 'd' || chars[i] == 'D' || chars[i] == 'f' || chars[i] == 'F')) { return foundDigit; } if (chars[i] == 'l' - || chars[i] == 'L') { + || chars[i] == 'L') { // not allowing L with an exponent return foundDigit && !hasExp; } @@ -1404,5 +1448,5 @@ public static boolean isNumber(String str) { // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass return !allowSigns && foundDigit; } - + } From f841cf51d96da992470229bfca2fc32c2f29cfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Wed, 13 Sep 2017 20:01:23 +0300 Subject: [PATCH 20/55] Organised fixed GSI mode for Coordinator --- src/lia/util/net/common/Utils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index 0f28e42..b8372ff 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1,6 +1,7 @@ package lia.util.net.common; import apmon.ApMon; +import lia.gsi.FDTGSIServer; import lia.util.net.copy.FDT; import lia.util.net.copy.FDTServer; import lia.util.net.copy.FDTSessionManager; @@ -1803,6 +1804,10 @@ public static void initLogger(String level, File logFile, Properties localProps) } public static void waitAndWork(ExecutorService executor, ServerSocket ss, Selector sel, Config config) throws Exception { + if (config.isGSIModeEnabled()) { + FDTGSIServer gsiServer = new FDTGSIServer(config.getGSIPort()); + gsiServer.start(); + } waitForTask(executor, ss, sel); int transferPort = getFDTTransferPort(config); From 771accccd635b0e53817262ef68ab26e341d4f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 17 Sep 2017 21:22:35 +0300 Subject: [PATCH 21/55] Inform other FDT about missing file Also fix for releasing transfer port on failure --- src/lia/util/net/common/Config.java | 8 +++++++- src/lia/util/net/common/Utils.java | 8 +++++++- src/lia/util/net/copy/FDT.java | 10 +++++++++- src/lia/util/net/copy/FDTReaderSession.java | 6 ++++-- src/lia/util/net/copy/FDTSession.java | 10 ++++++++++ src/lia/util/net/copy/FileReaderSession.java | 3 ++- src/lia/util/net/copy/transport/ControlChannel.java | 7 +++++++ src/lia/util/net/copy/transport/CtrlMsg.java | 9 ++++++++- 8 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 0ed9096..454ecc9 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -38,7 +38,7 @@ public class Config { "-N", "-bio", "-gsi", "-gsissh", "-notmp", "-nolock", "-nolocks", "-nettest", "-genb"}; public static final String[] VALUE_CMDLINE_ARGS = {"-bs", "-P", "-ss", "-limit", "-preFilters", "-postFilters", "-monID", "-ms", "-c", "-p", "-sshp", "-gsip", "-iof", "-sn", "-rCount", "-wCount", "-pCount", "-d", - "-writeMode", "-lisa_rep_delay", "-apmon_rep_delay", "-fl", "-reportDelay", "-ka"}; + "-writeMode", "-lisa_rep_delay", "-apmon_rep_delay", "-fl", "-reportDelay", "-ka", "-tp"}; public static final String POSSIBLE_VALUE_CMDLINE_ARGS[] = {"-enable_apmon", "-lisafdtclient", "-lisafdtserver", "-f", "-F", "-h", "-H", "--help", "-help," + "-u", "-U", "--update", "-update"}; /** @@ -153,6 +153,7 @@ public class Config { private int destPort; private int remoteTransferPort; private ArrayBlockingQueue transportPorts; + private final List tp; private String[] fileList; private String[] remappedFileList; private String destDir; @@ -290,6 +291,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx portNo = Utils.getIntValue(configMap, "-p", DEFAULT_PORT_NO); transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_PORT_NO); + tp = Arrays.asList(transportPorts.toArray()); isCoordinatorMode = Boolean.getBoolean("coordinator"); isThirdPartyCopyAgent = (configMap.get("-agent") != null); @@ -901,6 +903,10 @@ public int getRemoteTransferPort() { return remoteTransferPort; } + public List getRemoteTransferPorts() { + return tp; + } + public void setRemoteTransferPort(int remoteTransferPort) { this.remoteTransferPort = remoteTransferPort; } diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index b8372ff..113e1b6 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1775,7 +1775,9 @@ public static void initLogger(String level, File logFile, Properties localProps) loggingProps.remove("handlers"); } - loggingProps.put("handlers", "java.util.logging.FileHandler"); + loggingProps.put("handlers", "java.util.logging.FileHandler,java.util.logging.ConsoleHandler"); + loggingProps.put("java.util.logging.ConsoleHandler.level", "FINEST"); + loggingProps.put("java.util.logging.ConsoleHandler.formatter", "java.util.logging.SimpleFormatter"); loggingProps.put("java.util.logging.FileHandler.level", "FINEST"); loggingProps.put("java.util.logging.FileHandler.formatter", "java.util.logging.SimpleFormatter"); loggingProps.put("java.util.logging.FileHandler.pattern", "" + logFile); @@ -1869,4 +1871,8 @@ private static void waitForTask(ExecutorService executor, ServerSocket ss, Selec } } + + public static boolean isTransferPort(int localPort) { + return Config.getInstance().getRemoteTransferPorts().contains(localPort); + } } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index ffa067d..939488d 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -10,6 +10,7 @@ import lia.util.net.copy.transport.internal.SelectionManager; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; @@ -78,7 +79,14 @@ public class FDT { } else { if (config.getHostName() != null) { // role == client config.setRemoteTransferPort(Utils.getFDTTransferPort(config)); - FDTSessionManager.getInstance().addFDTClientSession(config.getRemoteTransferPort()); + try { + FDTSessionManager.getInstance().addFDTClientSession(config.getRemoteTransferPort()); + } + catch (FileNotFoundException ex) + { + ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); + cc.sendCtrlMessage(new CtrlMsg(CtrlMsg.FILE_NOT_FOUND, ex.getMessage())); + } } else { // is server if (!DirectByteBufferPool.initInstance(config.getByteBufferSize(), Config.getMaxTakePollIter())) { // this is really wrong ... I cannot be already initialized diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index afb50d4..f40e1e0 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -12,7 +12,7 @@ import lia.util.net.copy.transport.*; import java.io.File; -import java.io.IOException; +import java.io.FileNotFoundException; import java.net.InetAddress; import java.net.URL; import java.net.URLClassLoader; @@ -249,7 +249,9 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi for (final String fName : newFileList) { if (!new File(fName).exists()) { logger.warning("File listed in file list does not exist! " + fName); - throw new IOException("File does not exist! " + fName); + controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.FILE_NOT_FOUND, fName)); + controlChannel.sendFailureMsg(); + throw new FileNotFoundException("File does not exist! " + fName); } if (new File(fName).isFile()) { FileReaderSession frs = new FileReaderSession(fName, this, isLoop, fcp); diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index de9991d..284a275 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -53,6 +53,7 @@ public abstract class FDTSession extends IOSession implements ControlChannelNoti public static final int END_RCV = 1 << 8; public static final int COORDINATOR_MSG_RCVD = 1 << 9; public static final int LIST_FILES_MSG_RCVD = 1 << 10; + public static final int MISSING_FILE = 1 << 11; protected static final String[] FDT_SESION_STATES = {"UNINITIALIZED", "STARTED", "INIT_CONF_SENT", "INIT_CONF_RCV", "FINAL_CONF_SENT", "FINAL_CONF_RCV", "START_SENT", "START_RCV", "TRANSFERING", "END_SENT", "END_RCV"}; @@ -429,6 +430,11 @@ public final void notifyCtrlMsg(ControlChannel controlChannel, Object o) throws handleGetRemoteTransferPortMessage(ctrlMsg); break; } + case CtrlMsg.FILE_NOT_FOUND: { + setCurrentState(MISSING_FILE); + handleFileNotFound(ctrlMsg); + break; + } default: { FDTProcolException fpe = new FDTProcolException("Illegal CtrlMsg tag [ " + ctrlMsg.tag + " ]"); fpe.fillInStackTrace(); @@ -445,6 +451,10 @@ public final void notifyCtrlMsg(ControlChannel controlChannel, Object o) throws } } + private void handleFileNotFound(CtrlMsg ctrlMsg) { + logger.log(Level.WARNING, "[ FDTSession ] [ handleFileNotFound File not found: ( " + ctrlMsg.message.toString() + " )"); + } + private void handleCoordinatorMessage(CtrlMsg ctrlMsg) { logger.log(Level.INFO, "[ FDTSession ] [ handleCoordinatorMessage ( " + ctrlMsg.message.toString() + " )"); diff --git a/src/lia/util/net/copy/FileReaderSession.java b/src/lia/util/net/copy/FileReaderSession.java index 70b8e1a..4d9c76c 100644 --- a/src/lia/util/net/copy/FileReaderSession.java +++ b/src/lia/util/net/copy/FileReaderSession.java @@ -5,6 +5,7 @@ import lia.util.net.common.FileChannelProvider; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.UUID; @@ -29,7 +30,7 @@ public FileReaderSession(UUID uid, FDTSession fdtSession, String fileName, boole this.file = this.fileChannelProvider.getFile(fileName); if (!fileName.startsWith(FileSession.DEV_ZERO_FILENAME) && !file.exists()) { - throw new IOException("No such file: " + fileName); + throw new FileNotFoundException("No such file: " + fileName); } sessionSize = file.length(); diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index 4900ee1..f1b8dc7 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -245,6 +245,9 @@ private void initStreams() throws Exception { Utils.initLogger(config.getLogLevel(), null, new Properties()); myName = " ControlThread for ( " + fdtSessionID + " ) " + controlSocket.getInetAddress() + ":" + controlSocket.getPort(); + if (Utils.isTransferPort(localPort)) { + config.registerTransferPortForSession(localPort, fdtSessionID.toString()); + } logger.log(Level.INFO, "NEW CONTROL stream for " + fdtSessionID + " initialized "); final long localKA = Config.getInstance().getKeepAliveDelay(TimeUnit.NANOSECONDS); @@ -431,6 +434,10 @@ private CtrlMsg getResponse() throws Exception { return null; } + public void sendFailureMsg() throws Exception { + sendAllMsgs(); + } + private void sendAllMsgs() throws Exception { for (; ; ) { final Object ctrlMsg = qToSend.poll(); diff --git a/src/lia/util/net/copy/transport/CtrlMsg.java b/src/lia/util/net/copy/transport/CtrlMsg.java index 2dc94ab..e721c55 100644 --- a/src/lia/util/net/copy/transport/CtrlMsg.java +++ b/src/lia/util/net/copy/transport/CtrlMsg.java @@ -82,11 +82,18 @@ public class CtrlMsg implements Serializable { * message types designated for remote transfer port feature */ public static final int REMOTE_TRANSFER_PORT = 15; + + /** + * message types designated for informing other side about non existing file + */ + public static final int FILE_NOT_FOUND = 16; + + private static final long serialVersionUID = 6815237091777780075L; private static final String[] CTRL_MSG_TAGS = new String[]{ "KEEP_ALIVE_MSG", "PROTOCOL_VERSION", "SESSION_ID", "SESSION_TYPE", "INIT_FDT_CONF", "PING_SESSION", "INIT_FDTSESSION_CONF", "FINAL_FDTSESSION_CONF", "FINISHED_FILE_SESSIONS", "START_SESSION", "END_SESSION", - "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES", "REMOTE_TRANSFER_PORT" + "GUI_MSG", "END_SESSION_FIN2", "THIRD_PARTY_COPY", "LIST_FILES", "REMOTE_TRANSFER_PORT", "FILE_NOT_FOUND" }; /** From 4766dd2c41d55ef9dec17b5644e054f90c33709e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 19 Sep 2017 21:37:21 +0300 Subject: [PATCH 22/55] Implemented per file result (OK or FAILED) --- src/lia/util/net/copy/FDTSession.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 284a275..66fdf6f 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -586,6 +586,9 @@ public void finishFileSession(UUID sessionID, Throwable downCause) { Thread.dumpStack(); } } else { + if (downCause == null) { + logger.log(Level.INFO, fs.fileName + " STATUS: OK"); + } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, " [ FDTSession ] [ HANDLED ] The fileSession [ " + sessionID + " ] added to finised sessions list"); @@ -600,6 +603,7 @@ public void finishFileSession(UUID sessionID, Throwable downCause) { } if (downCause != null) { + logger.log(Level.WARNING, fs.fileName + " STATUS: FAILED"); close("the file session: " + sessionID + " / " + fs.fileName + " finished with errors: " + downCause.getMessage(), downCause); } From 5fd5c8d1a8c8e2d484d89b162c7e724ccd83e8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 28 Sep 2017 20:38:46 +0300 Subject: [PATCH 23/55] Fix RMP build Fixed fdt launch script --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3ec43e1..5369dc4 100644 --- a/pom.xml +++ b/pom.xml @@ -239,8 +239,7 @@ - java - -jar fdt.jar "$@" + java -jar fdt.jar "$@" 755 From dd9c551a9b34c18362b5eeae827faa6a4e4b125a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 12 Oct 2017 22:18:30 +0300 Subject: [PATCH 24/55] Inform about transfer limit --- src/lia/util/net/common/Config.java | 2 +- src/lia/util/net/copy/FDTSession.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 454ecc9..7b14f36 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -222,7 +222,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx rateLimit = Utils.getLongValue(configMap, "-limit", -1); if ((rateLimit > 0) && (rateLimit < NETWORK_BUFF_LEN_SIZE)) { rateLimit = NETWORK_BUFF_LEN_SIZE; - logger.log(Level.WARNING, " The rate limit (-limit) is too small. It will be set to " + rateLimit + logger.log(Level.INFO, " The rate limit (-limit) is too small. It will be set to " + rateLimit + " Bytes/s"); } configMap.put("-limit", String.valueOf(rateLimit)); diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 66fdf6f..795fe13 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -133,9 +133,12 @@ public FDTSession(short role, int transferPort) throws Exception { rateLimit.set(config.getRateLimit()); final long remoteRateLimit = Utils.getLongValue(controlChannel.remoteConf, "-limit", -1); rateLimitDelay.set(config.getRateLimitDelay()); - setNewRateLimit(remoteRateLimit, false); + if (rateLimit.get() > 0) { + logger.log(Level.INFO, "Adding rate limit " + rateLimit + " bytes to the FDT session " + sessionID); + } + useFixedBlockSize = (useFixedBlockSize || (this.controlChannel.remoteConf.get("-fbs") != null)); localLoop = (localLoop || (this.controlChannel.remoteConf.get("-ll") != null)); isLoop = (isLoop || (this.controlChannel.remoteConf.get("-loop") != null)); @@ -183,11 +186,13 @@ public FDTSession(ControlChannel controlChannel, short role) throws Exception { rateLimit.set(config.getRateLimit()); rateLimitDelay.set(config.getRateLimitDelay()); - final long remoteRateLimit = Utils.getLongValue(controlChannel.remoteConf, "-limit", -1); - setNewRateLimit(remoteRateLimit, false); + if (rateLimit.get() > 0) { + logger.log(Level.INFO, "Adding rate limit " + rateLimit + " bytes to the FDT session " + sessionID); + } + useFixedBlockSize = (useFixedBlockSize || (this.controlChannel.remoteConf.get("-fbs") != null)); localLoop = (localLoop || (this.controlChannel.remoteConf.get("-ll") != null)); isLoop = (isLoop || (this.controlChannel.remoteConf.get("-loop") != null)); From b604167a6df06845256049229a3f3afb96de0178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 12 Oct 2017 22:19:04 +0300 Subject: [PATCH 25/55] Log fdt server port in GSI mode --- src/lia/util/net/common/Utils.java | 1 + src/lia/util/net/copy/FDTServer.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index 113e1b6..cdab3d1 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1808,6 +1808,7 @@ public static void initLogger(String level, File logFile, Properties localProps) public static void waitAndWork(ExecutorService executor, ServerSocket ss, Selector sel, Config config) throws Exception { if (config.isGSIModeEnabled()) { FDTGSIServer gsiServer = new FDTGSIServer(config.getGSIPort()); + logger.log(Level.INFO, "FDT started in GSI mode on port: " + config.getGSIPort()); gsiServer.start(); } waitForTask(executor, ss, sel); diff --git a/src/lia/util/net/copy/FDTServer.java b/src/lia/util/net/copy/FDTServer.java index 423e9bd..f6a48d0 100644 --- a/src/lia/util/net/copy/FDTServer.java +++ b/src/lia/util/net/copy/FDTServer.java @@ -67,6 +67,7 @@ public FDTServer(int port) throws Exception { if (config.isGSIModeEnabled()) { FDTGSIServer gsiServer = new FDTGSIServer(config.getGSIPort()); + logger.log(Level.INFO, "FDT started in GSI mode on port: " + config.getGSIPort()); gsiServer.start(); } // Monitoring & Nice Prnting From a2eeefd4f53c4adaee424feffc3c810d4f2d586b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 12 Oct 2017 22:23:40 +0300 Subject: [PATCH 26/55] Log fdt server port in GSI mode after starting FDT --- src/lia/util/net/common/Utils.java | 2 +- src/lia/util/net/copy/FDTServer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index cdab3d1..93d9fce 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -1808,8 +1808,8 @@ public static void initLogger(String level, File logFile, Properties localProps) public static void waitAndWork(ExecutorService executor, ServerSocket ss, Selector sel, Config config) throws Exception { if (config.isGSIModeEnabled()) { FDTGSIServer gsiServer = new FDTGSIServer(config.getGSIPort()); - logger.log(Level.INFO, "FDT started in GSI mode on port: " + config.getGSIPort()); gsiServer.start(); + logger.log(Level.INFO, "FDT started in GSI mode on port: " + config.getGSIPort()); } waitForTask(executor, ss, sel); diff --git a/src/lia/util/net/copy/FDTServer.java b/src/lia/util/net/copy/FDTServer.java index f6a48d0..3cee622 100644 --- a/src/lia/util/net/copy/FDTServer.java +++ b/src/lia/util/net/copy/FDTServer.java @@ -67,8 +67,8 @@ public FDTServer(int port) throws Exception { if (config.isGSIModeEnabled()) { FDTGSIServer gsiServer = new FDTGSIServer(config.getGSIPort()); - logger.log(Level.INFO, "FDT started in GSI mode on port: " + config.getGSIPort()); gsiServer.start(); + logger.log(Level.INFO, "FDT started in GSI mode on port: " + config.getGSIPort()); } // Monitoring & Nice Prnting final ScheduledExecutorService monitoringService = Utils.getMonitoringExecService(); From 36e9369d69e45b2c07ae838dc006f52a962e198f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 12 Oct 2017 22:48:47 +0300 Subject: [PATCH 27/55] Store FDT version information in one place --- src/lia/util/net/common/Config.java | 5 +++-- src/lia/util/net/copy/FDT.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 7b14f36..1cd1d04 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -48,11 +48,12 @@ public class Config { public static final String REGEX_REMAP_DELIMITER = "(\\s)+/(\\s)+"; // all of this are set by the ant script public static final String FDT_MAJOR_VERSION = "0"; - public static final String FDT_MINOR_VERSION = "25"; + public static final String FDT_MINOR_VERSION = "26"; public static final String FDT_MAINTENANCE_VERSION = "0"; public static final String FDT_FULL_VERSION = FDT_MAJOR_VERSION + "." + FDT_MINOR_VERSION + "." + FDT_MAINTENANCE_VERSION; - public static final String FDT_RELEASE_DATE = "2017-04-20"; + public static final String FDT_RELEASE_DATE = "2017-08-08"; + public static final String FDT_RELEASE_TIME = "1830"; // the size of header packet sent over the wire - // TODO - this should be dynamic ... or not ( performance resons ?! ) public static final int HEADER_SIZE = 56; diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 939488d..f960acf 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -35,7 +35,8 @@ public class FDT { public static final String MONALISA2_CERN_CH = "monalisa2.cern.ch:28884"; - public static final String FDT_FULL_VERSION = "0.26.0-201708081850"; + public static final String RELEASE_DATE = Config.FDT_RELEASE_DATE.replaceAll("-", ""); + public static final String FDT_FULL_VERSION = Config.FDT_FULL_VERSION+"-"+ RELEASE_DATE + Config.FDT_RELEASE_TIME; /** * two weeks between checking for updates */ From a083f089b33331ccdfc0357ce7193919308c2aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Fri, 13 Oct 2017 18:35:57 +0300 Subject: [PATCH 28/55] Restore original fdt default port --- src/lia/util/net/common/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 1cd1d04..94a7095 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -66,7 +66,7 @@ public class Config { public static final Object BIG_FDTAPP_LOCK = new Object(); // default is 4 public static final int DEFAULT_SOCKET_NO = 4; - public static final int DEFAULT_PORT_NO = 43210; + public static final int DEFAULT_PORT_NO = 54321; public static final long DEFAULT_KEEP_ALIVE_NANOS = TimeUnit.MINUTES.toNanos(2); public static final int DEFAULT_PORT_NO_GSI = 54320; public static final int DEFAULT_PORT_NO_SSH = 22; From 5f8c39d06422ab286e4caee7a8f08efed9183795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Fri, 13 Oct 2017 18:49:04 +0300 Subject: [PATCH 29/55] Add default transfer port --- src/lia/util/net/common/Config.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 94a7095..1337a2e 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -67,6 +67,7 @@ public class Config { // default is 4 public static final int DEFAULT_SOCKET_NO = 4; public static final int DEFAULT_PORT_NO = 54321; + public static final int DEFAULT_TRANSFER_PORT_NO = 43210; public static final long DEFAULT_KEEP_ALIVE_NANOS = TimeUnit.MINUTES.toNanos(2); public static final int DEFAULT_PORT_NO_GSI = 54320; public static final int DEFAULT_PORT_NO_SSH = 22; @@ -291,7 +292,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx configMap.put("-ka", String.valueOf(TimeUnit.NANOSECONDS.toSeconds(this.keepAliveDelayNanos))); portNo = Utils.getIntValue(configMap, "-p", DEFAULT_PORT_NO); - transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_PORT_NO); + transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_TRANSFER_PORT_NO); tp = Arrays.asList(transportPorts.toArray()); isCoordinatorMode = Boolean.getBoolean("coordinator"); isThirdPartyCopyAgent = (configMap.get("-agent") != null); From 4dcab6e7efd823e44db0ea511a57d393cfa7c2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 14 Oct 2017 14:34:23 +0300 Subject: [PATCH 30/55] Close FDT session used to retrieve transfer port --- src/lia/util/net/copy/FDTSession.java | 2 ++ src/lia/util/net/copy/transport/ControlChannel.java | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 795fe13..4c12fc4 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -520,7 +520,9 @@ private void handleGetRemoteTransferPortMessage(CtrlMsg ctrlMsg) { if (newTransferPort > 0) { openSocketForTransferPort(newTransferPort); ctrlChann.sendRemoteTransferPort(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, newTransferPort)); + this.internalClose(); Utils.waitAndWork(executor, ss, sel, config); + } else { ctrlChann.sendRemoteTransferPort(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, -1)); logger.warning("There are no free transfer ports at this moment, please try again later"); diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index f1b8dc7..66c74d0 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -103,7 +103,6 @@ public ControlChannel(InetAddress inetAddress, int port, UUID fdtSessionID, Cont initStreams(); controlSocket.setSoTimeout(1000); - // } catch (Throwable t) { close("Cannot instantiate ControlChannel", t); throw new Exception(t); @@ -175,9 +174,6 @@ public String toString() { @SuppressWarnings("unchecked") private void initStreams() throws Exception { oos = new ObjectOutputStream(new BufferedOutputStream(controlSocket.getOutputStream())); - - // send the version - sendMsgImpl(versionMsg); try { From 6345281c72485c1f841b231418d59d35c46cb274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 14 Oct 2017 17:20:51 +0300 Subject: [PATCH 31/55] Try to read response more often --- src/lia/util/net/copy/transport/ControlChannel.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index 66c74d0..94a7205 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -29,7 +29,8 @@ public class ControlChannel extends AbstractFDTCloseable implements Runnable { public static final int CONNECT_TIMEOUT = 20 * 1000; public static final int SOCKET_TIMEOUT = 60 * 1000; - public static final int MAX_RETRIES = 3; + public static final int MAX_RETRIES = 1000; + public static final int RETRY_TIMEOUT = 300; private static final Logger logger = Logger.getLogger(ControlChannel.class.getName()); private static final CtrlMsg versionMsg = new CtrlMsg(CtrlMsg.PROTOCOL_VERSION, Config.FDT_FULL_VERSION + "-" + Config.FDT_RELEASE_DATE); @@ -420,7 +421,7 @@ private CtrlMsg getResponse() throws Exception { return newCtrlMsg; } catch (Exception e) { t = e; - Thread.sleep(i * CONNECT_TIMEOUT / 2); + Thread.sleep(RETRY_TIMEOUT); } finally { if (newCtrlMsg == null && i == MAX_RETRIES) { throw t; From b8c864810657ae034aeafa61edfbe3f74015b133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sat, 14 Oct 2017 17:52:27 +0300 Subject: [PATCH 32/55] Set TCP no delay --- src/lia/util/net/common/AcceptableTask.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lia/util/net/common/AcceptableTask.java b/src/lia/util/net/common/AcceptableTask.java index 48fc3e1..a087e6f 100644 --- a/src/lia/util/net/common/AcceptableTask.java +++ b/src/lia/util/net/common/AcceptableTask.java @@ -43,6 +43,7 @@ public AcceptableTask(final SocketChannel sc) throws IOException { this.sc = sc; this.s = sc.socket(); + this.s.setTcpNoDelay(true); } public void run() { From b5a3b2cfbd7c8cc998dc486ea795d8e53f961cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 15 Oct 2017 12:11:50 +0300 Subject: [PATCH 33/55] Fix coordinator mode enabling --- src/lia/util/net/common/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 1337a2e..07c1bdf 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -294,7 +294,7 @@ private Config(final Map configMap) throws InvalidFDTParameterEx portNo = Utils.getIntValue(configMap, "-p", DEFAULT_PORT_NO); transportPorts = Utils.getTransportPortsValue(configMap, "-tp", DEFAULT_TRANSFER_PORT_NO); tp = Arrays.asList(transportPorts.toArray()); - isCoordinatorMode = Boolean.getBoolean("coordinator"); + isCoordinatorMode = (configMap.get("-coord") != null); isThirdPartyCopyAgent = (configMap.get("-agent") != null); portNoGSI = Utils.getIntValue(configMap, "-gsip", DEFAULT_PORT_NO_GSI); From d87b387079a065c0eea0a41eae44cc4953e64e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 15 Oct 2017 12:14:53 +0300 Subject: [PATCH 34/55] Ignore FDT -S option on Agent mode --- src/lia/util/net/copy/FDT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index f960acf..1a0783d 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -150,7 +150,7 @@ private static int doWork() { } } else { if (!config.isStandAlone() && fdtSessionManager.isInited() - && fdtSessionManager.sessionsNumber() == 0) { + && fdtSessionManager.sessionsNumber() == 0 && !config.isThirdPartyCopyAgent()) { SelectionManager.getInstance().stopIt(); logger.info( "Server started with -S flag set and all the sessions have finished ... FDT will stop now"); From c24361b07bc9bea00c87dd36ced8d3fa6cce2fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 19 Oct 2017 23:30:37 +0300 Subject: [PATCH 35/55] FDT fdt message hanging and FileSession comparator --- src/lia/util/net/copy/FDT.java | 2 - src/lia/util/net/copy/FDTReaderSession.java | 41 +++++---- src/lia/util/net/copy/FDTSession.java | 15 ++-- .../net/copy/transport/ControlChannel.java | 88 +++++++++---------- 4 files changed, 77 insertions(+), 69 deletions(-) diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 1a0783d..26e8848 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -60,7 +60,6 @@ public class FDT { if (config.isCoordinatorMode()) { ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); String sessionID = cc.sendCoordinatorMessage(new CtrlMsg(CtrlMsg.THIRD_PARTY_COPY, new FDTSessionConfigMsg(config))); - // wait for remote config if (sessionID.equals("-1")) { logger.log(Level.WARNING, "Message sent to: " + config.getHostName() + ":" + config.getPort() + " but no free transfer ports available"); } else { @@ -70,7 +69,6 @@ public class FDT { } else if (config.isListFilesMode()) { ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); List filesInDir = cc.sendListFilesMessage(new CtrlMsg(CtrlMsg.LIST_FILES, new FDTListFilesMsg(config.getListFilesFrom()))); - // wait for remote config logger.log(Level.INFO, "Message sent to: " + config.getHostName() + ":" + config.getPort()); printOutResults(filesInDir); System.exit(0); diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index f40e1e0..e0454c6 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -250,7 +250,7 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi if (!new File(fName).exists()) { logger.warning("File listed in file list does not exist! " + fName); controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.FILE_NOT_FOUND, fName)); - controlChannel.sendFailureMsg(); + controlChannel.emptyMsgQueue(); throw new FileNotFoundException("File does not exist! " + fName); } if (new File(fName).isFile()) { @@ -435,21 +435,7 @@ public void startReading() { if (realReadersCount > 1) { FileSession[] filesArray = files.toArray(new FileSession[files.size()]); - Arrays.sort(filesArray, new Comparator() { - - @Override - public int compare(FileSession fileSession1, FileSession fileSession2) { - if (fileSession1.file.equals(fileSession2.file)) { - return fileSession1.sessionID.compareTo(fileSession2.sessionID); - } - - if (fileSession1.sessionSize < fileSession2.sessionSize) { - return -1; - } - - return 1; - } - }); + Arrays.sort(filesArray, new FileSessionComparator()); if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "Sorted FileSession-s array: " + Arrays.toString(filesArray)); @@ -949,4 +935,27 @@ public FileBlock poll(long delay, TimeUnit unit) throws InterruptedException { return fb; } + + private static class FileSessionComparator implements Comparator { + + @Override + public int compare(FileSession fileSession1, FileSession fileSession2) { + System.out.println("Comparing " + fileSession1.fileName + " and " + fileSession2.fileName); + if (fileSession1.fileName.equals(fileSession2.fileName)) { + if (fileSession1.sessionSize < fileSession2.sessionSize) { + System.out.println("Comparing session size " + fileSession1.sessionSize + " and " + fileSession2.sessionSize); + return -1; + } + else if (fileSession1.file.length() < fileSession2.file.length()) + { + System.out.println("Comparing file size " + fileSession1.file.length() + " and " + fileSession2.file.length()); + return -1; + } + } + + + System.out.println("Return 1"); + return 1; + } + } } diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 4c12fc4..8d461cb 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -225,6 +225,7 @@ public FDTSession(ControlChannel controlChannel, short role) throws Exception { } public static List getListOfFiles() { + logger.log(Level.FINEST, " [ getListOfFiles ] "); File[] filesList = new File(config.getListFilesFrom()).listFiles(); List listOfFiles = new ArrayList<>(); @@ -235,6 +236,7 @@ public static List getListOfFiles() { } } } + logger.log(Level.FINEST, " [ getListOfFiles ] file list collected from directory: " + config.getListFilesFrom()); return listOfFiles; } @@ -502,9 +504,10 @@ private void handleListFilesMessage(CtrlMsg ctrlMsg) { try { FDTListFilesMsg lsMsg = (FDTListFilesMsg) ctrlMsg.message; config.setListFilesFrom(lsMsg.listFilesFrom); - final ControlChannel ctrlChann = this.controlChannel; lsMsg.filesInDir = getListOfFiles(); - ctrlChann.sendSessionIDToCoordinator(new CtrlMsg(CtrlMsg.LIST_FILES, lsMsg)); + logger.log(Level.FINEST, "[ FDTSession ] [ handleListFilesMessage ] collected " + lsMsg.filesInDir.size()); + controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.LIST_FILES, lsMsg)); + controlChannel.emptyMsgQueue(); } catch (Exception ex) { logger.log(Level.WARNING, "Exception while handling 'list files in dir' message", ex); } finally { @@ -515,16 +518,18 @@ private void handleListFilesMessage(CtrlMsg ctrlMsg) { private void handleGetRemoteTransferPortMessage(CtrlMsg ctrlMsg) { logger.log(Level.INFO, "[ FDTSession ] [ handleGetRemoteTransferPortMessage ( " + ctrlMsg.message.toString() + " )"); try { - final ControlChannel ctrlChann = this.controlChannel; int newTransferPort = config.getNewRemoteTransferPort(); if (newTransferPort > 0) { openSocketForTransferPort(newTransferPort); - ctrlChann.sendRemoteTransferPort(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, newTransferPort)); + controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, newTransferPort)); + controlChannel.emptyMsgQueue(); this.internalClose(); + logger.log(Level.INFO, "[ FDTSession ] [ handleGetRemoteTransferPortMessage ( closing session )"); Utils.waitAndWork(executor, ss, sel, config); } else { - ctrlChann.sendRemoteTransferPort(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, -1)); + controlChannel.sendCtrlMessage(new CtrlMsg(CtrlMsg.REMOTE_TRANSFER_PORT, -1)); + controlChannel.emptyMsgQueue(); logger.warning("There are no free transfer ports at this moment, please try again later"); } } catch (Exception ex) { diff --git a/src/lia/util/net/copy/transport/ControlChannel.java b/src/lia/util/net/copy/transport/ControlChannel.java index 94a7205..a1dd59f 100644 --- a/src/lia/util/net/copy/transport/ControlChannel.java +++ b/src/lia/util/net/copy/transport/ControlChannel.java @@ -127,6 +127,7 @@ public ControlChannel(Socket s, ControlChannelNotifier notifier) throws Exceptio this.notifier = notifier; initStreams(); + controlSocket.setTcpNoDelay(true); controlSocket.setSoTimeout(1000); } catch (Throwable t) { @@ -151,6 +152,7 @@ public ControlChannel(GSIServer parent, Socket s, Subject peerSubject, ControlCh this.notifier = notifier; initStreams(); + controlSocket.setTcpNoDelay(true); controlSocket.setSoTimeout(1000); } catch (Throwable t) { @@ -213,13 +215,9 @@ private void initStreams() throws Exception { if (DirectByteBufferPool.initInstance(Integer.parseInt((String) remoteConf.get("-bs")), Config.getMaxTakePollIter())) { - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "The buffer pool has been initialized"); - } + logger.log(Level.FINER, "The buffer pool has been initialized"); } else { - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "The buffer pool is already initialized"); - } + logger.log(Level.FINER, "The buffer pool is already initialized"); } } catch (Throwable t) { @@ -300,27 +298,22 @@ public void sendCtrlMessage(final CtrlMsg ctrlMsg) { if (ctrlMsg == null) { throw new NullPointerException("Control message cannot be null over the ControlChannel"); } - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); - if (logger.isLoggable(Level.FINEST)) { - // do a thread dump - Thread.dumpStack(); - } + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); } qToSend.add(ctrlMsg); } public void sendSessionIDToCoordinator(CtrlMsg ctrlMsg) { logger.log(Level.INFO, "[ ControlChannel ] [ sendSessionIDToCoordinator ( " + ctrlMsg.message.toString() + " )"); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); - if (logger.isLoggable(Level.FINEST)) { - Thread.dumpStack(); - } + logger.log(Level.FINER, "[ ControlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); } - try { sendMsgImpl(ctrlMsg); + sendAllMsgs(); } catch (Exception e) { e.printStackTrace(); } @@ -329,14 +322,13 @@ public void sendSessionIDToCoordinator(CtrlMsg ctrlMsg) { public void sendRemoteTransferPort(CtrlMsg ctrlMsg) { logger.log(Level.INFO, "[ ControlChannel ] [ sendRemoteTransferPort ( " + ctrlMsg.message.toString() + " )" + this.remoteAddress + ":" + this.remotePort); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); - if (logger.isLoggable(Level.FINEST)) { - Thread.dumpStack(); - } + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); } try { sendMsgImpl(ctrlMsg); + sendAllMsgs(); } catch (Exception e) { e.printStackTrace(); } @@ -346,18 +338,17 @@ public void sendRemoteTransferPort(CtrlMsg ctrlMsg) { public List sendListFilesMessage(CtrlMsg ctrlMsg) throws IOException { logger.log(Level.INFO, "[ ControlChannel ] [ sendListFilesMessage ]"); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); - if (logger.isLoggable(Level.FINEST)) { - Thread.dumpStack(); - } + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); } try { + logger.log(Level.FINEST, "[ ControlChannel ] [ sendListFilesMessage ] sendMsgImpl " + ctrlMsg); sendMsgImpl(ctrlMsg); + sendAllMsgs(); + logger.log(Level.FINEST, "[ ControlChannel ] [ sendListFilesMessage ] waiting for response " + ctrlMsg); CtrlMsg newCtrlMsg = getResponse(); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] [ sendListFilesMessage ] listing files on remote machine: " + newCtrlMsg.message.toString()); - } + logger.log(Level.FINER, "[ CtrlChannel ] [ sendListFilesMessage ] listing files on remote machine: " + newCtrlMsg.message.toString()); FDTListFilesMsg msg = (FDTListFilesMsg) newCtrlMsg.message; return msg.filesInDir; } catch (Exception e) { @@ -370,14 +361,13 @@ public List sendListFilesMessage(CtrlMsg ctrlMsg) throws IOException { public int sendTransferPortMessage(CtrlMsg ctrlMsg) throws IOException { logger.log(Level.INFO, "[ ControlChannel ] [ sendTransferPortMessage ]"); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); - if (logger.isLoggable(Level.FINEST)) { - Thread.dumpStack(); - } + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); } try { sendMsgImpl(ctrlMsg); + sendAllMsgs(); CtrlMsg newCtrlMsg = getResponse(); if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "[ CtrlChannel ] [ sendTransferPortMessage ] got response: " + newCtrlMsg.message); @@ -393,14 +383,13 @@ public int sendTransferPortMessage(CtrlMsg ctrlMsg) throws IOException { public String sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { logger.log(Level.INFO, "[ ControlChannel ] [ sendCoordinatorMessage ]"); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); - if (logger.isLoggable(Level.FINEST)) { - Thread.dumpStack(); - } + logger.log(Level.FINER, "[ CtrlChannel ] adding to send queue msg: " + ctrlMsg.toString()); + if (logger.isLoggable(Level.FINEST)) { + Thread.dumpStack(); } try { sendMsgImpl(ctrlMsg); + sendAllMsgs(); CtrlMsg newCtrlMsg = getResponse(); logger.info("Remote job session ID: " + newCtrlMsg.message.toString()); return newCtrlMsg.message.toString(); @@ -415,27 +404,35 @@ public String sendCoordinatorMessage(CtrlMsg ctrlMsg) throws IOException { private CtrlMsg getResponse() throws Exception { Exception t = null; CtrlMsg newCtrlMsg = null; + logger.log(Level.FINEST, "[ ControlChannel ] [ getResponse] will wait for response " + (MAX_RETRIES * RETRY_TIMEOUT) + "ms"); for (int i = 1; i <= MAX_RETRIES; i++) { try { + logger.log(Level.FINER, "[ ControlChannel ] [ getResponse] trying to read CtrlMsg for " + i + " time"); newCtrlMsg = (CtrlMsg) ois.readObject(); + logger.log(Level.FINER, "[ ControlChannel ] [ getResponse] read CtrlMsg " + newCtrlMsg); return newCtrlMsg; } catch (Exception e) { + logger.log(Level.FINEST, "[ ControlChannel ] [ getResponse] failed to read CtrlMsg ", e); t = e; Thread.sleep(RETRY_TIMEOUT); + logger.log(Level.FINEST, "[ ControlChannel ] [ getResponse] waited for " + RETRY_TIMEOUT + "ms"); } finally { + if (newCtrlMsg == null && i == MAX_RETRIES) { + logger.log(Level.FINEST, "[ ControlChannel ] [ getResponse] CtrlMsg " + newCtrlMsg, t); throw t; } } } + logger.log(Level.FINEST, "[ ControlChannel ] [ getResponse] got message null"); return null; } - public void sendFailureMsg() throws Exception { + public void emptyMsgQueue() throws Exception { sendAllMsgs(); } - private void sendAllMsgs() throws Exception { + private synchronized void sendAllMsgs() throws Exception { for (; ; ) { final Object ctrlMsg = qToSend.poll(); if (ctrlMsg == null) { @@ -447,12 +444,11 @@ private void sendAllMsgs() throws Exception { private void sendMsgImpl(Object o) throws Exception { try { - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, " [ ControlChannel ] sending message " + o); - } + logger.log(Level.INFO, " [ ControlChannel ] sending message " + o); oos.writeObject(o); oos.reset(); oos.flush(); + logger.log(Level.FINER, " [ ControlChannel ] sent message " + o); } catch (Throwable t) { if (!isClosed()) { close("Exception sending control data", t); From 5ad956e1b0a2d4ccdd0a357dc31c28cf03d38d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 19 Oct 2017 23:37:21 +0300 Subject: [PATCH 36/55] Fix FileSession comparator logging --- src/lia/util/net/copy/FDTReaderSession.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index e0454c6..7e5f48e 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -940,21 +940,20 @@ private static class FileSessionComparator implements Comparator { @Override public int compare(FileSession fileSession1, FileSession fileSession2) { - System.out.println("Comparing " + fileSession1.fileName + " and " + fileSession2.fileName); + logger.log(Level.FINEST, "[ FileSessionComparator ] Comparing " + fileSession1.fileName + " and " + fileSession2.fileName); if (fileSession1.fileName.equals(fileSession2.fileName)) { if (fileSession1.sessionSize < fileSession2.sessionSize) { - System.out.println("Comparing session size " + fileSession1.sessionSize + " and " + fileSession2.sessionSize); + logger.log(Level.FINEST, "[ FileSessionComparator ] Comparing session size " + fileSession1.sessionSize + " and " + fileSession2.sessionSize); return -1; } else if (fileSession1.file.length() < fileSession2.file.length()) { - System.out.println("Comparing file size " + fileSession1.file.length() + " and " + fileSession2.file.length()); + logger.log(Level.FINEST, "[ FileSessionComparator ] Comparing file size " + fileSession1.file.length() + " and " + fileSession2.file.length()); return -1; } } - - System.out.println("Return 1"); + logger.log(Level.FINEST, "[ FileSessionComparator ] Return 1"); return 1; } } From d0eb60f884368847c129d05dd97aef98968905f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 22 Oct 2017 22:02:01 +0300 Subject: [PATCH 37/55] Fix nettest --- src/lia/util/net/copy/FDTReaderSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 7e5f48e..3971ef2 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -253,7 +253,7 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi controlChannel.emptyMsgQueue(); throw new FileNotFoundException("File does not exist! " + fName); } - if (new File(fName).isFile()) { + if (new File(fName).isFile() || config.isNetTest()) { FileReaderSession frs = new FileReaderSession(fName, this, isLoop, fcp); fileSessions.put(frs.sessionID, frs); setSessionSize(sessionSize() + frs.sessionSize()); From 7a46c6c36c149f5e9a517493ce7e66c1cefbd8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Sun, 22 Oct 2017 23:13:14 +0300 Subject: [PATCH 38/55] Fix nettest with /dev/null --- src/lia/util/net/copy/FDTReaderSession.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 3971ef2..42f0239 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -253,13 +253,10 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi controlChannel.emptyMsgQueue(); throw new FileNotFoundException("File does not exist! " + fName); } - if (new File(fName).isFile() || config.isNetTest()) { FileReaderSession frs = new FileReaderSession(fName, this, isLoop, fcp); fileSessions.put(frs.sessionID, frs); setSessionSize(sessionSize() + frs.sessionSize()); - } else { - logger.warning("File listed in file list is not a file! " + fName); - } + } buildPartitionMap(); From e1b438a73103c526a31c2eeeb16fd45f9c02374e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 24 Oct 2017 20:30:15 +0300 Subject: [PATCH 39/55] Change monalisa port --- src/lia/util/net/copy/FDT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 26e8848..50d7bc3 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -34,7 +34,7 @@ */ public class FDT { - public static final String MONALISA2_CERN_CH = "monalisa2.cern.ch:28884"; + public static final String MONALISA2_CERN_CH = "monalisa2.cern.ch:8884"; public static final String RELEASE_DATE = Config.FDT_RELEASE_DATE.replaceAll("-", ""); public static final String FDT_FULL_VERSION = Config.FDT_FULL_VERSION+"-"+ RELEASE_DATE + Config.FDT_RELEASE_TIME; /** From 71bfee1d2a82f3e86bce7a56a0d8d93ada92ce55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 24 Oct 2017 20:33:24 +0300 Subject: [PATCH 40/55] Fixed some IDE warnings --- src/lia/util/net/copy/FDTReaderSession.java | 48 +++++++++------------ 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 42f0239..6328576 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -61,20 +61,16 @@ public class FDTReaderSession extends FDTSession implements FileBlockProducer { public FDTReaderSession(int transferPort) throws Exception { super(FDTSession.CLIENT, transferPort); Utils.initLogger(config.getLogLevel(), new File("/tmp/" + sessionID + ".log"), new Properties()); - final int rMul = Integer.getInteger("fdt.rQueueM", 2).intValue(); - final int avProcProp = Integer.getInteger("fdt.avProc", 1).intValue(); + final int rMul = Integer.getInteger("fdt.rQueueM", 2); + final int avProcProp = Integer.getInteger("fdt.avProc", 1); final int avProcMax = Math.max(avProcProp, Utils.availableProcessors()); - fileBlockQueue = new ArrayBlockingQueue(avProcMax * rMul); - - // I am already connected here with the remote peer - readersMap = new TreeMap>(); + fileBlockQueue = new ArrayBlockingQueue<>(avProcMax * rMul); + readersMap = new TreeMap<>(); diskManager.addSession(this); - this.remoteDir = config.getDestinationDir(); this.recursive = config.isRecursive(); this.isFileList = (config.getConfigMap().get("-fl") != null); this.monID = config.getMonID(); - readersCount = config.getReadersCount(); if (readersCount <= 0) { @@ -144,7 +140,7 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi } int filtersCount = 0; - final ProcessorInfo processorInfo = new ProcessorInfo(); + final ProcessorInfo info = new ProcessorInfo(); final long sTime = System.nanoTime(); try { @@ -161,17 +157,17 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi } else { filtersCount = preProcessFilters.length; - processorInfo.fileList = new String[fileList.length]; - processorInfo.destinationDir = (this.remoteDir == null) ? config.getDestinationDir() + info.fileList = new String[fileList.length]; + info.destinationDir = (this.remoteDir == null) ? config.getDestinationDir() : this.remoteDir; - processorInfo.remoteAddress = this.controlChannel.remoteAddress; - processorInfo.remotePort = this.controlChannel.remotePort; - processorInfo.recursive = this.recursive; + info.remoteAddress = this.controlChannel.remoteAddress; + info.remotePort = this.controlChannel.remotePort; + info.recursive = this.recursive; - System.arraycopy(fileList, 0, processorInfo.fileList, 0, fileList.length); + System.arraycopy(fileList, 0, info.fileList, 0, fileList.length); for (final String filterName : preProcessFilters) { - preProcess(processorInfo, filterName); + preProcess(info, filterName); } } } @@ -191,10 +187,10 @@ private void internalInit(final String[] fileList, final String[] remappedFileLi Map newRemappedFileList = new HashMap(); if (filtersCount > 0) { - this.processorInfo = processorInfo; - this.remoteDir = processorInfo.destinationDir; - newFileList = new ArrayList(processorInfo.fileList.length); - newFileList.addAll(Arrays.asList(processorInfo.fileList)); + this.processorInfo = info; + this.remoteDir = info.destinationDir; + newFileList = new ArrayList<>(info.fileList.length); + newFileList.addAll(Arrays.asList(info.fileList)); } else { if (recursive) { @@ -333,13 +329,11 @@ private void sendRemoteSessions(final Map initialMapping, Map initialMapping, Map Date: Fri, 3 Nov 2017 22:20:44 +0200 Subject: [PATCH 41/55] Add monitoring to OpenTSDB for Grafana --- add-lib-to-local-maven.sh | 2 + lib/opentsdb/opentsdb-client-2.1.0.jar | Bin 0 -> 21461 bytes pom.xml | 25 ++ src/apmon/ApMon.java | 7 + src/apmon/BkThread.java | 1 - src/lia/util/net/common/Config.java | 47 +++- src/lia/util/net/common/MonitoringUtils.java | 226 ++++++++++++++++++ src/lia/util/net/common/Utils.java | 9 +- src/lia/util/net/copy/FDT.java | 66 ++++- src/lia/util/net/copy/FDTReaderSession.java | 11 + src/lia/util/net/copy/FDTServer.java | 11 +- src/lia/util/net/copy/FDTSession.java | 18 +- src/lia/util/net/copy/FDTWriterSession.java | 11 + .../AbstractAccountableMonitoringTask.java | 1 - 14 files changed, 419 insertions(+), 16 deletions(-) create mode 100644 lib/opentsdb/opentsdb-client-2.1.0.jar create mode 100644 src/lia/util/net/common/MonitoringUtils.java diff --git a/add-lib-to-local-maven.sh b/add-lib-to-local-maven.sh index b526aa5..c33269c 100755 --- a/add-lib-to-local-maven.sh +++ b/add-lib-to-local-maven.sh @@ -32,4 +32,6 @@ mvn install:install-file -Dfile="lib/gsi-sshterm/BCGSS.jar" -DgroupId=edu.illino mvn install:install-file -Dfile="lib/gsi-sshterm/swing-layout-1.0.3.jar" -DgroupId=org.jdesktop -DartifactId=swing-layout -Dversion=1.0.3 -Dpackaging=jar #mvn install:install-file -Dfile="bbbb" -DgroupId=aaaaa -DartifactId=BCGSS -Dversion=1.0.0 -Dpackaging=jar +mvn install:install-file -Dfile="lib/opentsdb/opentsdb-client-2.1.0.jar" -DgroupId=org.opentsdb -DartifactId=opentsdb-client -Dversion=2.1.0 -Dpackaging=jar + echo "FINISHED INSTALLING LOCAL DEPENDENCIES" \ No newline at end of file diff --git a/lib/opentsdb/opentsdb-client-2.1.0.jar b/lib/opentsdb/opentsdb-client-2.1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..48572d9e39e2a9e68d241a90f684786ba4f8d093 GIT binary patch literal 21461 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 WR^UPi1_n7GJPkbE?F-N(1_l6?^K~Bp literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index 5369dc4..cf24df3 100644 --- a/pom.xml +++ b/pom.xml @@ -142,11 +142,31 @@ io 2.2.0 + + org.apache.httpcomponents + httpclient + 4.5 + + + com.google.code.gson + gson + 2.8.1 + org.globus.jsse jsse 2.2.0 + + com.google.guava + guava + r05 + + + commons-lang + commons-lang + 2.6 + org.globus.myproxy myproxy @@ -157,6 +177,11 @@ ssl-proxies 2.2.0 + + org.opentsdb + opentsdb-client + 2.1.0 + src diff --git a/src/apmon/ApMon.java b/src/apmon/ApMon.java index 397b713..80beb20 100644 --- a/src/apmon/ApMon.java +++ b/src/apmon/ApMon.java @@ -33,6 +33,8 @@ package apmon; import apmon.host.cmdExec; +import lia.util.net.common.Config; +import lia.util.net.common.MonitoringUtils; import java.io.*; import java.net.*; @@ -818,6 +820,11 @@ public void sendTimedParameters(String clusterName, String nodeName, int nParams int i; + if (Config.getInstance().getMonitor().equals(Config.OPENTSDB)) { + MonitoringUtils monUtils = new MonitoringUtils(Config.getInstance()); + monUtils.shareMetrics(clusterName, nodeName, paramNames, paramValues); + } + if (!shouldSend()) return; diff --git a/src/apmon/BkThread.java b/src/apmon/BkThread.java index 041b151..2c6d735 100644 --- a/src/apmon/BkThread.java +++ b/src/apmon/BkThread.java @@ -595,7 +595,6 @@ void sendSysInfo() { } } catch (Exception e) { logger.log(Level.WARNING, "", e); - ; if (apm.autoDisableMonitoring) { logger.warning("parameter processes disabled"); apm.sysMonitorParams &= ~ApMonMonitoringConstants.SYS_NET_TCP_DETAILS; diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 07c1bdf..1dadc69 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -8,10 +8,7 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; -import java.net.NetworkInterface; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; +import java.net.*; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.*; @@ -93,6 +90,9 @@ public class Config { public static final int SSH_REMOTE_SERVER_LOCAL_CLIENT_PULL = 2; // REMOTE server, REMOTE client in push mode) public static final int SSH_REMOTE_SERVER_REMOTE_CLIENT_PUSH = 3; + + public static final String APMON="APMON"; + public static final String OPENTSDB="OPENTSDB"; /** * Logger used by this class */ @@ -123,7 +123,7 @@ public class Config { private final boolean isNagleEnabled; private final boolean isStandAlone; private final String sshKeyPath; - private final String apMonHosts; + private String apMonHosts; private final boolean isLisaRestartEnabled; private final String writeMode; private final String preFilters; @@ -162,6 +162,7 @@ public class Config { private String listFilesFrom; private String sIP; private String dIP; + private String opentsdb = null; private boolean bComputeMD5 = false; private boolean bRecursive = false; private boolean bCheckUpdate = false; @@ -238,6 +239,8 @@ private Config(final Map configMap) throws InvalidFDTParameterEx this.massStorageConfig = Utils.getStringValue(configMap, "-ms", null); this.massStorageType = Utils.getStringValue(configMap, "-mst", null); + this.opentsdb = Utils.getStringValue(configMap, "-opentsdb", "localhost:4242"); + try { if ((massStorageType() != null) && massStorageType().equals("dcache")) { System.setProperty("lia.util.net.common.FileChannelProviderFactory", @@ -533,6 +536,19 @@ private Config(final Map configMap) throws InvalidFDTParameterEx } } + public String getMonitor() + { + if(configMap.get("-opentsdb") != null) + { + return OPENTSDB; + } + if (configMap.get("-apmon") != null) + { + return APMON; + } + return APMON; + } + private static final int getMinMTU() { int retMTU = 1500; @@ -791,6 +807,10 @@ public int getPort() { return portNo; } + public int getDefaultPort() { + return DEFAULT_PORT_NO; + } + public int getGSIPort() { return portNoGSI; } @@ -934,6 +954,10 @@ public String getApMonHosts() { return apMonHosts; } + public void setApMonHosts(String apMonHosts) { + this.apMonHosts = apMonHosts; + } + public boolean isCoordinatorMode() { return isCoordinatorMode; } @@ -1124,4 +1148,17 @@ public void setThirdPartyCopyAgent(boolean isThirdPartyCopyAgent) { this.configMap.remove("-agent"); } } + + public String getListenAddress() + { + return (String)this.configMap.get("-FDT_LISTEN"); + } + + public String getOpentsdb() { + return opentsdb; + } + + public void setOpentsdb(String opentsdb) { + this.opentsdb = opentsdb; + } } diff --git a/src/lia/util/net/common/MonitoringUtils.java b/src/lia/util/net/common/MonitoringUtils.java new file mode 100644 index 0000000..997e85a --- /dev/null +++ b/src/lia/util/net/common/MonitoringUtils.java @@ -0,0 +1,226 @@ +package lia.util.net.common; + +import lia.util.net.copy.FDT; +import lia.util.net.copy.FDTSession; +import org.opentsdb.client.ExpectResponse; +import org.opentsdb.client.HttpClient; +import org.opentsdb.client.HttpClientImpl; +import org.opentsdb.client.builder.Metric; +import org.opentsdb.client.builder.MetricBuilder; +import org.opentsdb.client.response.Response; + +import java.io.IOException; +import java.net.*; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Raimondas Sirvinskas + * @version 1.0 + */ +public class MonitoringUtils { + + private static Logger logger = Logger.getLogger(MonitoringUtils.class.getName()); + + private Config config; + private HttpClient client; + private String hostName; + private FDTSession session; + + public MonitoringUtils(Config config, FDTSession session) { + this.session = session; + this.config = config; + this.client = new HttpClientImpl("http://" + config.getOpentsdb()); + this.hostName = getHostName(); + } + + public MonitoringUtils(Config config) { + this.config = config; + this.client = new HttpClientImpl("http://" + config.getOpentsdb()); + this.hostName = getHostName(); + } + + public void monitorStart(long timeRequested, String clusterName) { + MetricBuilder builder = MetricBuilder.getInstance(); + Metric metric = builder.addMetric("fdt.transferstatus.timeStarted") + .setDataPoint(timeRequested, timeRequested); + addTags(metric, clusterName, null); + monitorStartSession(builder, timeRequested, clusterName); + sendMetricsToServer(builder); + } + + public void monitorFinish(long timeRequested, String clusterName) { + MetricBuilder builder = MetricBuilder.getInstance(); + Metric metric = builder.addMetric("fdt.transferstatus.timeFinished") + .setDataPoint(timeRequested, timeRequested); + addTags(metric, clusterName, null); + monitorFinishSession(builder, timeRequested, clusterName); + sendMetricsToServer(builder); + } + + private synchronized void sendMetricsToServer(MetricBuilder builder) { + if (builder.getMetrics().size() > 0) { + if (logger.isLoggable(Level.FINE)) { + Iterator iterator = builder.getMetrics().listIterator(); + while (iterator.hasNext()) { + try { + Metric metric = (Metric) iterator.next(); + logger.log(Level.FINE, "metric " + metric.getName() + " " + metric.stringValue()); + logger.log(Level.FINE, "metric tag names: " + builder.getMetrics().listIterator().next().getTags().keySet().toString()); + logger.log(Level.FINE, "metric tag values: " + builder.getMetrics().listIterator().next().getTags().values().toString()); + } catch (Exception e) { + //do nothing + } + } + } + try { + Response response = client.pushMetrics(builder, ExpectResponse.SUMMARY); + logger.log(Level.FINE, "Response from OpenTSDB server: " + response.toString()); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to send metrics to OpenTSDB server", e); + } + } + + } + + private void monitorStartSession(MetricBuilder builder, long timeStarted, String clusterName) { + monitorStartConfiguration(builder, timeStarted, clusterName, "debugLevel", Level.parse(config.getLogLevel()).intValue()); + monitorStartConfiguration(builder, timeStarted, clusterName, "bio", config.isBlocking() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "notmp", config.isNoTmpFlagSet() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "nolock", config.isNoLockFlagSet() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "nolocks", config.getConfigMap().get("-nolocks") != null ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "nettest", config.isNetTest() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "bs", config.getByteBufferSize()); + monitorStartConfiguration(builder, timeStarted, clusterName, "P", config.getSockNum()); + monitorStartConfiguration(builder, timeStarted, clusterName, "ss", config.getSockBufSize()); + monitorStartConfiguration(builder, timeStarted, clusterName, "limit", config.getRateLimit()); + monitorStartConfiguration(builder, timeStarted, clusterName, "iof", config.getRetryIOCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "rCount", config.getReadersCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "wCount", config.getWritersCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "pCount", config.getMaxPartitionCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "sourcePort", config.getPort()); + monitorStartConfiguration(builder, timeStarted, clusterName, "destPort", session.getRemotePort()); + monitorStartConfiguration(builder, timeStarted, clusterName, "transferPort", session.getTransferPort()); + monitorStartConfiguration(builder, timeStarted, clusterName, "totalBytes", session.getTotalBytes()); + monitorStartConfiguration(builder, timeStarted, clusterName, "status", 1); + monitorStartConfiguration(builder, timeStarted, clusterName, "recursive", config.isRecursive() ? 1 : 0); + } + + private void monitorFinishSession(MetricBuilder builder, long timeStarted, String clusterName) { + monitorStartConfiguration(builder, timeStarted, clusterName, "debugLevel", Level.parse(config.getLogLevel()).intValue()); + monitorStartConfiguration(builder, timeStarted, clusterName, "bio", config.isBlocking() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "notmp", config.isNoTmpFlagSet() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "nolock", config.isNoLockFlagSet() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "nolocks", config.getConfigMap().get("-nolocks") != null ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "nettest", config.isNetTest() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "bs", config.getByteBufferSize()); + monitorStartConfiguration(builder, timeStarted, clusterName, "P", config.getSockNum()); + monitorStartConfiguration(builder, timeStarted, clusterName, "ss", config.getSockBufSize()); + monitorStartConfiguration(builder, timeStarted, clusterName, "limit", config.getRateLimit()); + monitorStartConfiguration(builder, timeStarted, clusterName, "iof", config.getRetryIOCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "rCount", config.getReadersCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "wCount", config.getWritersCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "pCount", config.getMaxPartitionCount()); + monitorStartConfiguration(builder, timeStarted, clusterName, "sourcePort", config.getPort()); + monitorStartConfiguration(builder, timeStarted, clusterName, "destPort", session.getRemotePort()); + monitorStartConfiguration(builder, timeStarted, clusterName, "transferPort", session.getTransferPort()); + monitorStartConfiguration(builder, timeStarted, clusterName, "totalBytes", session.getTotalBytes()); + monitorStartConfiguration(builder, timeStarted, clusterName, "status", 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "recursive", config.isRecursive() ? 1 : 0); + monitorStartConfiguration(builder, timeStarted, clusterName, "exitStatus", session.getCurrentStatus()); + } + + public void monitorStartConfiguration(MetricBuilder builder, long timeStarted, String clusterName, String configParam, long configValue) { + Metric metric = builder.addMetric("fdt.transferstatus." + configParam) + .setDataPoint(timeStarted, configValue); + addTags(metric, clusterName, null); + } + + private void addTags(Metric metric, String clusterName, String destIP) { + metric.addTag("fdthost", hostName != null ? hostName : "none") + .addTag("fdtversion", FDT.FDT_FULL_VERSION) + .addTag("fdtsourceIP", getHostIP()) + .addTag("fdtdestIP", destIP == null ? session != null ? session.getRemoteAddress().getHostAddress() : "0.0.0.0" : destIP) + .addTag("fdtclusterName", clusterName) + .addTag("fdtcustomFDTTag", Utils.getStringValue(config.getConfigMap(), "-fdtTAG", "DEFAULT_FDT_TAG")); + } + + public synchronized void shareMetrics(String clusterName, String nodeName, Vector paramNames, Vector paramValues) { + final String hostName = getHostName(); + + MetricBuilder builder = MetricBuilder.getInstance(); + Iterator nameIterator = paramNames.iterator(); + Iterator valueIterator = paramValues.iterator(); + String[] node = nodeName.split(":"); + + while (nameIterator.hasNext() && valueIterator.hasNext() && paramNames.size() == paramValues.size()) { + try { + Object nameObject = nameIterator.next(); + Object valueObject = valueIterator.next(); + double value = Double.valueOf(valueObject.toString()); + Metric metric = builder.addMetric("fdt.transferstatus." + String.valueOf(nameObject)) + .setDataPoint(System.currentTimeMillis(), value); + addTags(metric, clusterName, node[0]); + + logger.log(Level.FINE, "metric " + metric.getName() + " " + metric.stringValue()); + logger.log(Level.FINE, "metric tag names: " + metric.getTags().keySet().toString()); + logger.log(Level.FINE, "metric tag values: " + metric.getTags().values().toString()); + } catch (Exception e) { + logger.log(Level.FINEST, "Failed to parse metric value ", e); + } + } + + sendMetricsToServer(builder); + } + + private String getHostName() { + String host = null; + try { + InetAddress localhost = InetAddress.getLocalHost(); + host = localhost.getHostName(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + if (host == null) { + host = System.getenv("HOSTNAME"); + } + return host; + } + + private String getHostIP() { + String ip = null; + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface iface = interfaces.nextElement(); + if (iface.isLoopback() || !iface.isUp()) + continue; + + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr instanceof Inet6Address) continue; + ip = addr.getHostAddress(); + } + } + } catch (SocketException e) { + throw new RuntimeException(e); + } + return ip != null ? ip : "0.0.0.0"; + } + + public void monitorEndStats(boolean finishStatus, long totalBytes, long utilBytes, long startTimeMillis, long endTime, long period, String clusterName) { + MetricBuilder builder = MetricBuilder.getInstance(); + monitorStartConfiguration(builder, endTime, clusterName, "destPort", session.getRemotePort()); + monitorStartConfiguration(builder, endTime, clusterName, "transferTime", period); + monitorStartConfiguration(builder, endTime, clusterName, "totalFileBytes", totalBytes); + monitorStartConfiguration(builder, endTime, clusterName, "totalNetworkBytes", utilBytes); + monitorStartConfiguration(builder, endTime, clusterName, "finishStatus", finishStatus ? 1 : 0); + monitorStartConfiguration(builder, endTime, clusterName, "startTime", startTimeMillis); + monitorStartConfiguration(builder, endTime, clusterName, "endTime", endTime); + sendMetricsToServer(builder); + } +} diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index 93d9fce..fbe9842 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -12,6 +12,12 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.opentsdb.client.ExpectResponse; +import org.opentsdb.client.HttpClient; +import org.opentsdb.client.HttpClientImpl; +import org.opentsdb.client.builder.Metric; +import org.opentsdb.client.builder.MetricBuilder; +import org.opentsdb.client.response.Response; import java.io.*; import java.net.*; @@ -404,7 +410,8 @@ public static ApMon getApMon() { synchronized (Utils.class) { // just check that initApMonInstance will be ever called .... - if (Config.getInstance().getApMonHosts() == null) { + Config config = Config.getInstance(); + if (config.getApMonHosts() == null && !config.getMonitor().equals(config.OPENTSDB)) { return null; } diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 50d7bc3..8a5e8be 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -36,7 +36,7 @@ public class FDT { public static final String MONALISA2_CERN_CH = "monalisa2.cern.ch:8884"; public static final String RELEASE_DATE = Config.FDT_RELEASE_DATE.replaceAll("-", ""); - public static final String FDT_FULL_VERSION = Config.FDT_FULL_VERSION+"-"+ RELEASE_DATE + Config.FDT_RELEASE_TIME; + public static final String FDT_FULL_VERSION = Config.FDT_FULL_VERSION + "-" + RELEASE_DATE + Config.FDT_RELEASE_TIME; /** * two weeks between checking for updates */ @@ -51,9 +51,18 @@ public class FDT { private static Properties localProps = new Properties(); FDT() throws Exception { + String monitor = config.getMonitor(); + switch (monitor) { + case Config.APMON: + initApMon(); + break; + case Config.OPENTSDB: + initOpenTSDB(); + break; + default: + break; + } - // initialize monitoring, if requested - initApMon(); scheduleReportingTasks(); @@ -80,9 +89,7 @@ public class FDT { config.setRemoteTransferPort(Utils.getFDTTransferPort(config)); try { FDTSessionManager.getInstance().addFDTClientSession(config.getRemoteTransferPort()); - } - catch (FileNotFoundException ex) - { + } catch (FileNotFoundException ex) { ControlChannel cc = new ControlChannel(config.getHostName(), config.getPort(), UUID.randomUUID(), FDTSessionManager.getInstance()); cc.sendCtrlMessage(new CtrlMsg(CtrlMsg.FILE_NOT_FOUND, ex.getMessage())); } @@ -97,6 +104,53 @@ public class FDT { } } + private void initOpenTSDB() throws Exception { + ApMon apmon = null; + long lStart = System.currentTimeMillis(); + try { + Vector vHosts = new Vector<>(); + Vector vPorts = new Vector<>(); + //dummy host + vHosts.add("127.0.0.1"); + vPorts.add(12345); + ApMon.setLogLevel("WARNING"); + apmon = new ApMon(vHosts, vPorts); + apmon.setConfRecheck(false, -1); + apmon.setGenMonitoring(true, 20); + String cluster_name; + String node_name; + if (config.getHostName() != null) { + cluster_name = "Clients"; + node_name = config.getHostName(); + } else {// server + cluster_name = "Servers"; + node_name = apmon.getMyHostname(); + } + apmon.setMonitorClusterNode(cluster_name, node_name); + apmon.setSysMonitoring(true, 10); + } catch (Throwable ex) { + logger.log(Level.WARNING, "Error initializing ApMon engine.", ex); + } finally { + Utils.initApMonInstance(apmon); + } + + try { + if (Utils.getApMon() != null) { + ApMonReportingTask apmrt = new ApMonReportingTask(); + Utils.getMonitoringExecService().scheduleWithFixedDelay(apmrt, 1, + config.getApMonReportingInterval(), TimeUnit.SECONDS); + } else { + logger.log(Level.WARNING, "Cannot start ApMonReportingTask because apMon is null!"); + } + } catch (Throwable t) { + logger.log(Level.WARNING, "Cannot start ApMonReportingTask because got Exception.", t); + } + + long lEnd = System.currentTimeMillis(); + logger.info("ApMon for OpenTSDB initialization took " + (lEnd - lStart) + " ms"); + } + + private static void scheduleReportingTasks() { Utils.getMonitoringExecService().scheduleWithFixedDelay(FDTInternalMonitoringTask.getInstance(), 1, 5, TimeUnit.SECONDS); diff --git a/src/lia/util/net/copy/FDTReaderSession.java b/src/lia/util/net/copy/FDTReaderSession.java index 6328576..aaaa38f 100644 --- a/src/lia/util/net/copy/FDTReaderSession.java +++ b/src/lia/util/net/copy/FDTReaderSession.java @@ -634,6 +634,7 @@ private void finalCleanup() { sb.append(" ) final stats:"); sb.append("\n Started: ").append(new Date(startTimeMillis)); sb.append("\n Ended: ").append(endDate); + long period = System.nanoTime() - startTimeNanos; sb.append("\n Transfer period: ") .append(Utils.getETA(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTimeNanos))); sb.append("\n TotalBytes: ").append(getTotalBytes()); @@ -663,6 +664,11 @@ private void finalCleanup() { sb.append("\n"); logger.info(sb.toString()); System.out.println(sb.toString()); + if (config.getMonitor().equals(Config.OPENTSDB)) { + MonitoringUtils monUtils = new MonitoringUtils(config, this); + monUtils.monitorEndStats(((downCause() == null) && (downMessage() == null)),getTotalBytes(), transportProvider.getUtilBytes(), + startTimeMillis, endDate.getTime(), period, "Readers"); + } } catch (Throwable t) { logger.log(Level.WARNING, "[ FDTReaderSession ] [ finalCleanup ] [ HANDLED ] Exception getting final statistics. Smth went dreadfully wrong!", @@ -868,6 +874,11 @@ public void handleStartFDTSession(CtrlMsg ctrlMsg) throws Exception { // I'm still in sync ... if smth goes wrong the state will not be set setCurrentState(START_SENT); + if (config.getMonitor().equals(Config.OPENTSDB)) { + MonitoringUtils monUtils = new MonitoringUtils(config, this); + monUtils.monitorStart(System.currentTimeMillis(), "Readers"); + } + startReading(); setCurrentState(TRANSFERING); diff --git a/src/lia/util/net/copy/FDTServer.java b/src/lia/util/net/copy/FDTServer.java index 3cee622..8958694 100644 --- a/src/lia/util/net/copy/FDTServer.java +++ b/src/lia/util/net/copy/FDTServer.java @@ -3,6 +3,7 @@ import lia.gsi.FDTGSIServer; import lia.util.net.common.*; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; @@ -60,7 +61,15 @@ public FDTServer(int port) throws Exception { ssc.configureBlocking(false); ss = ssc.socket(); - ss.bind(new InetSocketAddress(port)); + + String listenIP = config.getListenAddress(); + if (listenIP == null) { + ss.bind(new InetSocketAddress(port)); + } + else + { + ss.bind(new InetSocketAddress(InetAddress.getByName(listenIP), port)); + } sel = Selector.open(); ssc.register(sel, SelectionKey.OP_ACCEPT); diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index 8d461cb..b7b2620 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -4,6 +4,7 @@ package lia.util.net.copy; import lia.util.net.common.Config; +import lia.util.net.common.MonitoringUtils; import lia.util.net.common.Utils; import lia.util.net.copy.monitoring.FDTSessionMonitoringTask; import lia.util.net.copy.monitoring.lisa.LisaCtrlNotifier; @@ -170,6 +171,10 @@ public FDTSession(short role, int transferPort) throws Exception { monitoringTask.startSession(); } + public int getTransferPort() { + return transferPort; + } + public FDTSession(ControlChannel controlChannel, short role) throws Exception { // it is possible to throw a NPE? super(controlChannel.fdtSessionID()); @@ -550,7 +555,14 @@ private void openSocketForTransferPort(int port) throws IOException { ssc.configureBlocking(false); FDTSession sess = FDTSessionManager.getInstance().getSession(sessionID); ss = ssc.socket(); - ss.bind(new InetSocketAddress(port)); + String listenIP = config.getListenAddress(); + if (listenIP == null) { + ss.bind(new InetSocketAddress(port)); + } + else + { + ss.bind(new InetSocketAddress(InetAddress.getByName(listenIP), port)); + } sel = Selector.open(); ssc.register(sel, SelectionKey.OP_ACCEPT); @@ -709,6 +721,10 @@ protected void internalClose() throws Exception { monitoringService.purge(); monitoringTask.finishSession(); } + if (config.getMonitor().equals(Config.OPENTSDB)) { + MonitoringUtils monUtils = new MonitoringUtils(config, this); + monUtils.monitorFinish(System.currentTimeMillis(), role == CLIENT ? "Readers" : "Writers"); + } } /** diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 57cbe07..34ac8c7 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -170,6 +170,7 @@ private void finalCleanup() { sb.append(" ) final stats:"); sb.append("\n Started: ").append(new Date(startTimeMillis)); sb.append("\n Ended: ").append(endDate); + long period = System.nanoTime() - startTimeNanos; sb.append("\n Transfer period: ") .append(Utils.getETA(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTimeNanos))); sb.append("\n TotalBytes: ").append(getTotalBytes()); @@ -201,6 +202,11 @@ private void finalCleanup() { } else { System.out.println(sb.toString()); } + if (config.getMonitor().equals(Config.OPENTSDB)) { + MonitoringUtils monUtils = new MonitoringUtils(config, this); + monUtils.monitorEndStats(((downCause() == null) && (downMessage() == null)),getTotalBytes(), transportProvider.getUtilBytes(), + startTimeMillis, endDate.getTime(), period, "Writers"); + } } catch (Throwable t) { logger.log(Level.WARNING, "[ FDTWriterSession ] [ finalCleanup ] [ HANDLED ] Exception getting final statistics. Smth went wrong! Cause: ", @@ -492,6 +498,7 @@ public long getSize() { @Override public void handleStartFDTSession(CtrlMsg ctrlMsg) throws Exception { + if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "[ FDTWriterSession ] handleStartFDTSession. Msg: " + ctrlMsg); } @@ -501,6 +508,10 @@ public void handleStartFDTSession(CtrlMsg ctrlMsg) throws Exception { transferPort, config.getSockNum()); } } + if (config.getMonitor().equals(Config.OPENTSDB)) { + MonitoringUtils monUtils = new MonitoringUtils(config, this); + monUtils.monitorStart(System.currentTimeMillis(), "Writers"); + } setCurrentState(TRANSFERING); transportProvider.startTransport(true); diff --git a/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java b/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java index 9c3349f..6334a41 100644 --- a/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java +++ b/src/lia/util/net/copy/monitoring/base/AbstractAccountableMonitoringTask.java @@ -49,7 +49,6 @@ private final void iComputeRate(final Accountable accountable, final Accountable if (accEntry.lastTimeCalled != 0) { computeRate(accEntry, now); - } if (accEntry.debug) { From a81102dd242ddd93c22275830c0b9572bd1899c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 9 Nov 2017 21:36:38 +0200 Subject: [PATCH 42/55] Change log level to FINER --- src/apmon/BkThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apmon/BkThread.java b/src/apmon/BkThread.java index 2c6d735..a8e2235 100644 --- a/src/apmon/BkThread.java +++ b/src/apmon/BkThread.java @@ -457,7 +457,7 @@ void sendSysInfo() { monitor.updateCall(); long crtTime = System.currentTimeMillis(); - logger.info("Sending system monitoring information..."); + logger.log(Level.FINER,"Sending system monitoring information..."); // long intervalLength = crtTime - apm.lastSysInfoSend; apm.lastSysInfoSend = crtTime; From fde0e89868e2b0482bc3172c041b8f3280cfbcc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 9 Nov 2017 21:50:45 +0200 Subject: [PATCH 43/55] Fix NPE while sending metrics to OpenTSDB --- src/lia/util/net/copy/FDTWriterSession.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lia/util/net/copy/FDTWriterSession.java b/src/lia/util/net/copy/FDTWriterSession.java index 34ac8c7..f682771 100644 --- a/src/lia/util/net/copy/FDTWriterSession.java +++ b/src/lia/util/net/copy/FDTWriterSession.java @@ -174,7 +174,9 @@ private void finalCleanup() { sb.append("\n Transfer period: ") .append(Utils.getETA(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTimeNanos))); sb.append("\n TotalBytes: ").append(getTotalBytes()); + long utilBytes = 0; if (transportProvider != null) { + utilBytes = transportProvider.getUtilBytes(); sb.append("\n TotalNetworkBytes: ").append(transportProvider.getUtilBytes()); try { if (!Utils.updateTotalReadCounter(transportProvider.getUtilBytes())) { @@ -204,7 +206,7 @@ private void finalCleanup() { } if (config.getMonitor().equals(Config.OPENTSDB)) { MonitoringUtils monUtils = new MonitoringUtils(config, this); - monUtils.monitorEndStats(((downCause() == null) && (downMessage() == null)),getTotalBytes(), transportProvider.getUtilBytes(), + monUtils.monitorEndStats(((downCause() == null) && (downMessage() == null)), getTotalBytes(), utilBytes, startTimeMillis, endDate.getTime(), period, "Writers"); } } catch (Throwable t) { From 27c5d6a4302dd9567225128dca6ad426f3f6d9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 9 Nov 2017 21:55:46 +0200 Subject: [PATCH 44/55] Remove system output to deduplicate console messages --- src/lia/util/net/copy/monitoring/ConsoleReportingTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java index e34b37f..f8f7b06 100644 --- a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java +++ b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java @@ -157,7 +157,6 @@ private void reportStatus() { if (shouldReport) { logger.info(sb.toString()); - System.out.println(sb.toString()); } } From d49b39c0af7714fe7515299198f4b7e2df55d67e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 9 Nov 2017 22:05:36 +0200 Subject: [PATCH 45/55] Use single HTTPClient instance for FDT --- src/lia/util/net/common/Config.java | 14 ++++++++++++++ src/lia/util/net/common/MonitoringUtils.java | 11 ++++++----- src/lia/util/net/common/Utils.java | 8 ++------ src/lia/util/net/copy/FDT.java | 2 ++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 1dadc69..b9e620a 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -4,6 +4,7 @@ package lia.util.net.common; import lia.util.net.copy.PosixFSFileChannelProviderFactory; +import org.opentsdb.client.HttpClientImpl; import java.io.BufferedReader; import java.io.FileReader; @@ -163,6 +164,7 @@ public class Config { private String sIP; private String dIP; private String opentsdb = null; + private String opentsdbProtocol = System.getProperty("opentsdb.protocol", org.apache.http.HttpHost.DEFAULT_SCHEME_NAME+ "://"); private boolean bComputeMD5 = false; private boolean bRecursive = false; private boolean bCheckUpdate = false; @@ -196,6 +198,7 @@ public class Config { private long consoleReportingTaskDelay = 5; private Map sessionPortMap = new HashMap<>(); private Map> sessionSocketMap = new HashMap<>(); + private HttpClientImpl httpClient = null; /** * @param configMap @@ -549,6 +552,17 @@ public String getMonitor() return APMON; } + public void initOpenTSDBMonitorClient() + { + httpClient = new HttpClientImpl(opentsdbProtocol + getOpentsdb()); + } + + + public HttpClientImpl getOpenTSDBMonitorClient() + { + return httpClient; + } + private static final int getMinMTU() { int retMTU = 1500; diff --git a/src/lia/util/net/common/MonitoringUtils.java b/src/lia/util/net/common/MonitoringUtils.java index 997e85a..ecee934 100644 --- a/src/lia/util/net/common/MonitoringUtils.java +++ b/src/lia/util/net/common/MonitoringUtils.java @@ -4,7 +4,6 @@ import lia.util.net.copy.FDTSession; import org.opentsdb.client.ExpectResponse; import org.opentsdb.client.HttpClient; -import org.opentsdb.client.HttpClientImpl; import org.opentsdb.client.builder.Metric; import org.opentsdb.client.builder.MetricBuilder; import org.opentsdb.client.response.Response; @@ -33,13 +32,13 @@ public class MonitoringUtils { public MonitoringUtils(Config config, FDTSession session) { this.session = session; this.config = config; - this.client = new HttpClientImpl("http://" + config.getOpentsdb()); + this.client = config.getOpenTSDBMonitorClient(); this.hostName = getHostName(); } public MonitoringUtils(Config config) { this.config = config; - this.client = new HttpClientImpl("http://" + config.getOpentsdb()); + this.client = config.getOpenTSDBMonitorClient(); this.hostName = getHostName(); } @@ -77,8 +76,10 @@ private synchronized void sendMetricsToServer(MetricBuilder builder) { } } try { - Response response = client.pushMetrics(builder, ExpectResponse.SUMMARY); - logger.log(Level.FINE, "Response from OpenTSDB server: " + response.toString()); + if (client != null) { + Response response = client.pushMetrics(builder, ExpectResponse.SUMMARY); + logger.log(Level.FINE, "Response from OpenTSDB server: " + response.toString()); + } } catch (IOException e) { logger.log(Level.WARNING, "Failed to send metrics to OpenTSDB server", e); } diff --git a/src/lia/util/net/common/Utils.java b/src/lia/util/net/common/Utils.java index fbe9842..58a12ff 100644 --- a/src/lia/util/net/common/Utils.java +++ b/src/lia/util/net/common/Utils.java @@ -12,12 +12,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.opentsdb.client.ExpectResponse; -import org.opentsdb.client.HttpClient; -import org.opentsdb.client.HttpClientImpl; -import org.opentsdb.client.builder.Metric; -import org.opentsdb.client.builder.MetricBuilder; -import org.opentsdb.client.response.Response; import java.io.*; import java.net.*; @@ -1775,6 +1769,7 @@ public static void initLogger(String level, File logFile, Properties localProps) loggingProps.put("handlers", "java.util.logging.ConsoleHandler"); loggingProps.put("java.util.logging.ConsoleHandler.level", "FINEST"); loggingProps.put("java.util.logging.ConsoleHandler.formatter", "java.util.logging.SimpleFormatter"); + loggingProps.put("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); } if (logFile != null) { @@ -1785,6 +1780,7 @@ public static void initLogger(String level, File logFile, Properties localProps) loggingProps.put("handlers", "java.util.logging.FileHandler,java.util.logging.ConsoleHandler"); loggingProps.put("java.util.logging.ConsoleHandler.level", "FINEST"); loggingProps.put("java.util.logging.ConsoleHandler.formatter", "java.util.logging.SimpleFormatter"); + loggingProps.put("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); loggingProps.put("java.util.logging.FileHandler.level", "FINEST"); loggingProps.put("java.util.logging.FileHandler.formatter", "java.util.logging.SimpleFormatter"); loggingProps.put("java.util.logging.FileHandler.pattern", "" + logFile); diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index 8a5e8be..ec99788 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -105,6 +105,8 @@ public class FDT { } private void initOpenTSDB() throws Exception { + + config.initOpenTSDBMonitorClient(); ApMon apmon = null; long lStart = System.currentTimeMillis(); try { From 88dce1e45ee448be6e146e9495875a7a5a85ffb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 9 Nov 2017 22:25:05 +0200 Subject: [PATCH 46/55] Removed duplicate date from log entry --- src/lia/util/net/copy/monitoring/ConsoleReportingTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java index f8f7b06..d54f323 100644 --- a/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java +++ b/src/lia/util/net/copy/monitoring/ConsoleReportingTask.java @@ -150,7 +150,6 @@ private final boolean reportStatus(final Set currentSessionSet, fina private void reportStatus() { StringBuilder sb = new StringBuilder(8192); - sb.append(dateFormat.format(new Date())).append("\t"); boolean shouldReport = (reportStatus(diskWriterManager.getSessions(), oldWriterSessions, "Net In: ", sb) || reportStatus(diskReaderManager.getSessions(), oldReaderSessions, "Net Out: ", sb)); From 441befdc264c944b23581eb12901f19e188d9bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 14 Nov 2017 22:38:56 +0200 Subject: [PATCH 47/55] Update OpenTSDB client Fixed error message reason printing --- lib/opentsdb/opentsdb-client-2.1.0.jar | Bin 21461 -> 21447 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/opentsdb/opentsdb-client-2.1.0.jar b/lib/opentsdb/opentsdb-client-2.1.0.jar index 48572d9e39e2a9e68d241a90f684786ba4f8d093..a0d2e3067aa9eb7a5e25db367f8dccc79f754bc6 100644 GIT binary patch delta 2738 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 O0LtA_;-8&1Xa5r$zL1ds From 816bcd42c5f7cb1200d7fb6151b8c2322c4df0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Tue, 14 Nov 2017 22:39:33 +0200 Subject: [PATCH 48/55] Implement FDT config sync Among server and client --- src/lia/util/net/common/Config.java | 11 +++++++++++ src/lia/util/net/common/MonitoringUtils.java | 2 +- src/lia/util/net/copy/FDT.java | 4 ++-- src/lia/util/net/copy/FDTSession.java | 19 ++++++++++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index b9e620a..a433ac0 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -557,6 +557,16 @@ public void initOpenTSDBMonitorClient() httpClient = new HttpClientImpl(opentsdbProtocol + getOpentsdb()); } + public String getFDTTag() + { + return Utils.getStringValue(configMap, "-fdtTAG", "DEFAULT_FDT_TAG"); + } + + public void setFDTTag(String fdtTag) + { + configMap.put("-fdtTAG", fdtTag); + } + public HttpClientImpl getOpenTSDBMonitorClient() { @@ -1175,4 +1185,5 @@ public String getOpentsdb() { public void setOpentsdb(String opentsdb) { this.opentsdb = opentsdb; } + } diff --git a/src/lia/util/net/common/MonitoringUtils.java b/src/lia/util/net/common/MonitoringUtils.java index ecee934..548d077 100644 --- a/src/lia/util/net/common/MonitoringUtils.java +++ b/src/lia/util/net/common/MonitoringUtils.java @@ -146,7 +146,7 @@ private void addTags(Metric metric, String clusterName, String destIP) { .addTag("fdtsourceIP", getHostIP()) .addTag("fdtdestIP", destIP == null ? session != null ? session.getRemoteAddress().getHostAddress() : "0.0.0.0" : destIP) .addTag("fdtclusterName", clusterName) - .addTag("fdtcustomFDTTag", Utils.getStringValue(config.getConfigMap(), "-fdtTAG", "DEFAULT_FDT_TAG")); + .addTag("fdtcustomFDTTag", config.getFDTTag()); } public synchronized void shareMetrics(String clusterName, String nodeName, Vector paramNames, Vector paramValues) { diff --git a/src/lia/util/net/copy/FDT.java b/src/lia/util/net/copy/FDT.java index ec99788..f932573 100644 --- a/src/lia/util/net/copy/FDT.java +++ b/src/lia/util/net/copy/FDT.java @@ -57,7 +57,7 @@ public class FDT { initApMon(); break; case Config.OPENTSDB: - initOpenTSDB(); + initOpenTSDB(config); break; default: break; @@ -104,7 +104,7 @@ public class FDT { } } - private void initOpenTSDB() throws Exception { + public static void initOpenTSDB(Config config) throws Exception { config.initOpenTSDBMonitorClient(); ApMon apmon = null; diff --git a/src/lia/util/net/copy/FDTSession.java b/src/lia/util/net/copy/FDTSession.java index b7b2620..94fc655 100644 --- a/src/lia/util/net/copy/FDTSession.java +++ b/src/lia/util/net/copy/FDTSession.java @@ -130,7 +130,7 @@ public FDTSession(short role, int transferPort) throws Exception { if (this.role == CLIENT || this.role == COORDINATOR) { this.controlChannel = new ControlChannel(config.getHostName(), transferPort, sessionID(), this); } - + syncFDTConfig(controlChannel.remoteConf); rateLimit.set(config.getRateLimit()); final long remoteRateLimit = Utils.getLongValue(controlChannel.remoteConf, "-limit", -1); rateLimitDelay.set(config.getRateLimitDelay()); @@ -171,6 +171,23 @@ public FDTSession(short role, int transferPort) throws Exception { monitoringTask.startSession(); } + private void syncFDTConfig(Map remoteConf) { + + if (remoteConf.get("-opentsdb") != null){ + if(config.getFDTTag() != remoteConf.get("-fdtTAG") && remoteConf.get("-fdtTAG") != null) { + config.setFDTTag((String)remoteConf.get("-fdtTAG")); + } + if (config.getOpentsdb() != remoteConf.get("-opentsdb") && remoteConf.get("-opentsdb") != null) { + config.setOpentsdb((String)remoteConf.get("-opentsdb")); + } + try { + FDT.initOpenTSDB(config); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to initOpenTSDB monitor task", e); + } + } + } + public int getTransferPort() { return transferPort; } From aa784473d816f02d847acbcd9802962f9cbb9d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 19:45:37 +0200 Subject: [PATCH 49/55] Add monitoring to OpenTSDB samples doc --- docs/monitoring-opentsdb.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/monitoring-opentsdb.txt diff --git a/docs/monitoring-opentsdb.txt b/docs/monitoring-opentsdb.txt new file mode 100644 index 0000000..9d0bb4e --- /dev/null +++ b/docs/monitoring-opentsdb.txt @@ -0,0 +1,19 @@ +Monitor net test metrics to specified OpenTSDB server: +#SERVER2 +java -jar fdt.jar -opentsdb -nettest +#SERVER1 +java -jar fdt.jar -opentsdb -nettest -c $SERVER2 + + +Monitor net test metrics to specified OpenTSDB server with specific tag: +#SERVER2 +java -jar fdt.jar -opentsdb -nettest -fdtTAG +#SERVER1 +java -jar fdt.jar -opentsdb -nettest -c $SERVER2 -fdtTAG + + +Monitor net test metrics to specified OpenTSDB server and using http proxy server: +#SERVER2 +java -Dhttp.proxyHost= -Dhttp.proxyPort= -jar fdt.jar -opentsdb -nettest +#SERVER1 +java -Dhttp.proxyHost= -Dhttp.proxyPort= -jar fdt.jar -opentsdb -nettest -c $SERVER2 \ No newline at end of file From a4ae0b79a178200263296e194eaabe8b2fae33db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 22:08:15 +0200 Subject: [PATCH 50/55] Add documentation of OpenTSDB monitoring --- docs/doc-examples.md | 2 +- docs/doc-fdt-ddcopy.md | 10 +++++++-- docs/doc-opentsdb.md | 42 +++++++++++++++++++++++++++++++++++++ docs/doc-security.md | 2 +- docs/doc-system-tuning.md | 2 +- docs/doc-user-extensions.md | 2 +- 6 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 docs/doc-opentsdb.md diff --git a/docs/doc-examples.md b/docs/doc-examples.md index 18fbde0..df9964d 100644 --- a/docs/doc-examples.md +++ b/docs/doc-examples.md @@ -1,6 +1,6 @@ [[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] -[[FDT & DDCopy](doc-fdt-ddcopy.md)] [Examples] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [Examples] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] [[FDT Monitoring](doc-opentsdb.md)] ### Examples diff --git a/docs/doc-fdt-ddcopy.md b/docs/doc-fdt-ddcopy.md index f49a01b..5677854 100644 --- a/docs/doc-fdt-ddcopy.md +++ b/docs/doc-fdt-ddcopy.md @@ -1,6 +1,6 @@ [[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] -[FDT & DDCopy] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] +[FDT & DDCopy] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] [[FDT Monitoring](doc-opentsdb.md)] ### FDT **FDT** can be used in one of these seven modes: @@ -112,7 +112,13 @@ Agent can use booth Server and Client options too, because at any time Agent can **-sID \** session ID retrieved from coordinator. -**-d \** The destination directory used to copy session log file. +**-d \** The destination directory used to copy session log file. + +**Options used for FDT monitoring to OpenTSDB:** + +**-opentsdb \** OpenTSDB server and port where FDT will send metrics + +**-fdtTAG \** custom FDT metrics tag ### DDCopy diff --git a/docs/doc-opentsdb.md b/docs/doc-opentsdb.md new file mode 100644 index 0000000..e02151e --- /dev/null +++ b/docs/doc-opentsdb.md @@ -0,0 +1,42 @@ +[[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] + +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] [ FDT Monitoring ] + +### FDT Monitoring +FDT provides self monitoring possibility. FDT can send metrics to the OpenTSDB server and these metrics can be used to draw FDT dashboard using Grafana tool. + +In order to start monitoring and sending metrics to OpenTSDB server user has to specify OpenTSDB server address and port number for FDT using commandline argument *-opentsdb* +Optional parameter is *-fdtTAG* which allows user to specify cistom tag for all metrics from that specific FDT. +If user is using proxy server then additional Java arguments has to be passed to provide proxy host and proxy port. +At this moment no authentication is implemented for proxy and OpenTSDB. + +#### Examples + +#####Monitor net test metrics to specified OpenTSDB server: + +SERVER2 +``` +java -jar fdt.jar -opentsdb -nettest +``` +SERVER1 +``` +java -jar fdt.jar -opentsdb -nettest -c $SERVER2 +``` +#####Monitor net test metrics to specified OpenTSDB server with specific tag: +SERVER2 +``` +java -jar fdt.jar -opentsdb -nettest -fdtTAG +``` +SERVER1 +``` +java -jar fdt.jar -opentsdb -nettest -c $SERVER2 -fdtTAG +``` +#####Monitor net test metrics to specified OpenTSDB server and using http proxy server: +SERVER2 +``` +java -Dhttp.proxyHost= -Dhttp.proxyPort= -jar fdt.jar -opentsdb -nettest +``` +SERVER1 +``` +java -Dhttp.proxyHost= -Dhttp.proxyPort= -jar fdt.jar -opentsdb -nettest -c $SERVER2 +``` diff --git a/docs/doc-security.md b/docs/doc-security.md index 0758ff7..f2943d7 100644 --- a/docs/doc-security.md +++ b/docs/doc-security.md @@ -1,6 +1,6 @@ [[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] -[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [Security] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [Security] [[User's Extensions](doc-user-extensions.md)] [[System Tuning](doc-system-tuning.md)] [[FDT Monitoring](doc-opentsdb.md)] ### FDT Security FDT provides several security schemes to allow sending and receiving files over public networks. diff --git a/docs/doc-system-tuning.md b/docs/doc-system-tuning.md index 70e33e8..ecb1cbd 100644 --- a/docs/doc-system-tuning.md +++ b/docs/doc-system-tuning.md @@ -1,6 +1,6 @@ [[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] -[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [System Tuning] +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [[User's Extensions](doc-user-extensions.md)] [System Tuning] [[FDT Monitoring](doc-opentsdb.md)] ### System Settings diff --git a/docs/doc-user-extensions.md b/docs/doc-user-extensions.md index aa2a6a5..b400f70 100644 --- a/docs/doc-user-extensions.md +++ b/docs/doc-user-extensions.md @@ -1,6 +1,6 @@ [[Home](index.md)] [Documentation] [[Performance Tests](perf-disk-to-disk.md)] -[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [User's Extensions] [[System Tuning](doc-system-tuning.md)] +[[FDT & DDCopy](doc-fdt-ddcopy.md)] [[Examples](doc-examples.md)] [[Security](doc-security.md)] [User's Extensions] [[System Tuning](doc-system-tuning.md)] [[FDT Monitoring](doc-opentsdb.md)] ### User's Extensions From 5439de3ae5e518b8eda7df3a28b0cb6fd990181e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 22:21:56 +0200 Subject: [PATCH 51/55] Fix markup --- docs/doc-opentsdb.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/doc-opentsdb.md b/docs/doc-opentsdb.md index e02151e..8750a3f 100644 --- a/docs/doc-opentsdb.md +++ b/docs/doc-opentsdb.md @@ -12,7 +12,7 @@ At this moment no authentication is implemented for proxy and OpenTSDB. #### Examples -#####Monitor net test metrics to specified OpenTSDB server: +*Monitor net test metrics to specified OpenTSDB server:* SERVER2 ``` @@ -22,7 +22,7 @@ SERVER1 ``` java -jar fdt.jar -opentsdb -nettest -c $SERVER2 ``` -#####Monitor net test metrics to specified OpenTSDB server with specific tag: +*Monitor net test metrics to specified OpenTSDB server with specific tag:* SERVER2 ``` java -jar fdt.jar -opentsdb -nettest -fdtTAG @@ -31,7 +31,7 @@ SERVER1 ``` java -jar fdt.jar -opentsdb -nettest -c $SERVER2 -fdtTAG ``` -#####Monitor net test metrics to specified OpenTSDB server and using http proxy server: +*Monitor net test metrics to specified OpenTSDB server and using http proxy server:* SERVER2 ``` java -Dhttp.proxyHost= -Dhttp.proxyPort= -jar fdt.jar -opentsdb -nettest From e2cc12435dc94db41c714a942a0c7639c2fe6b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 22:48:07 +0200 Subject: [PATCH 52/55] Change version number --- pom.xml | 2 +- src/lia/util/net/common/Config.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index cf24df3..a70dbc4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 fast-data-transfer fdt - 0.26.0-SNAPSHOT + 0.26.1-SNAPSHOT Fast data transfer jar diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index a433ac0..d2caeba 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -47,7 +47,7 @@ public class Config { // all of this are set by the ant script public static final String FDT_MAJOR_VERSION = "0"; public static final String FDT_MINOR_VERSION = "26"; - public static final String FDT_MAINTENANCE_VERSION = "0"; + public static final String FDT_MAINTENANCE_VERSION = "1"; public static final String FDT_FULL_VERSION = FDT_MAJOR_VERSION + "." + FDT_MINOR_VERSION + "." + FDT_MAINTENANCE_VERSION; public static final String FDT_RELEASE_DATE = "2017-08-08"; From 355531425abbb3e7a30071e095d6d1a9d25b4430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 22:50:44 +0200 Subject: [PATCH 53/55] Cleanup --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index a70dbc4..a99959d 100644 --- a/pom.xml +++ b/pom.xml @@ -216,13 +216,11 @@ org.apache.maven.plugins maven-jar-plugin - **/log4j.properties - lia.util.net.copy.FDTMain From 39a3b4d0fe8d407f70d057c57d0080c6dee55047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 23:07:08 +0200 Subject: [PATCH 54/55] Update release date --- src/lia/util/net/common/Config.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lia/util/net/common/Config.java b/src/lia/util/net/common/Config.java index 109ae05..987be8f 100644 --- a/src/lia/util/net/common/Config.java +++ b/src/lia/util/net/common/Config.java @@ -46,7 +46,6 @@ public class Config { public static final String REGEX_REMAP_DELIMITER = "(\\s)+/(\\s)+"; // all of this are set by the ant script public static final String FDT_MAJOR_VERSION = "0"; - public static final String FDT_MINOR_VERSION = "26"; public static final String FDT_MAINTENANCE_VERSION = "1"; public static final String FDT_FULL_VERSION = FDT_MAJOR_VERSION + "." + FDT_MINOR_VERSION + "." From 34fc56c027786315719e2ccd336a2e2065527465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20=C5=A0irvinskas?= Date: Thu, 23 Nov 2017 23:10:43 +0200 Subject: [PATCH 55/55] Remove line --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7ddbe65..d32e103 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,4 @@ 4.0.0 fast-data-transfer

    6`chj6` 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 zaƨ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 BglPZ( literal 0 HcmV?d00001 diff --git a/docs/img/ciena_sc08_1.jpg b/docs/img/ciena_sc08_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14b8a1de49b72babaa3a88c225fd612c7d6dc6af GIT binary patch literal 285021 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⇍f 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�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%0A(qDIzDw9Cp@&UcYj) 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