Full Code of blakey22/shadowsocks-java for AI

master 5a70f18d463e cached
44 files
140.5 KB
31.8k tokens
241 symbols
1 requests
Download .txt
Repository: blakey22/shadowsocks-java
Branch: master
Commit: 5a70f18d463e
Files: 44
Total size: 140.5 KB

Directory structure:
gitextract_fryewo1t/

├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── stfl/
        │           ├── Constant.java
        │           ├── Main.java
        │           ├── MainGui.java
        │           ├── misc/
        │           │   ├── Config.java
        │           │   ├── Log.java
        │           │   ├── Reflection.java
        │           │   ├── UTF8Control.java
        │           │   └── Util.java
        │           ├── network/
        │           │   ├── IServer.java
        │           │   ├── LocalServer.java
        │           │   ├── NioLocalServer.java
        │           │   ├── io/
        │           │   │   └── PipeSocket.java
        │           │   ├── nio/
        │           │   │   ├── ChangeRequest.java
        │           │   │   ├── ISocketHandler.java
        │           │   │   ├── PipeEvent.java
        │           │   │   ├── PipeWorker.java
        │           │   │   ├── RemoteSocketHandler.java
        │           │   │   └── SocketHandlerBase.java
        │           │   └── proxy/
        │           │       ├── AutoProxy.java
        │           │       ├── HttpProxy.java
        │           │       ├── IProxy.java
        │           │       ├── ProxyFactory.java
        │           │       └── Socks5Proxy.java
        │           ├── ss/
        │           │   ├── AesCrypt.java
        │           │   ├── BlowFishCrypt.java
        │           │   ├── CamelliaCrypt.java
        │           │   ├── CryptBase.java
        │           │   ├── CryptFactory.java
        │           │   ├── ICrypt.java
        │           │   ├── SeedCrypt.java
        │           │   └── ShadowSocksKey.java
        │           └── ui/
        │               ├── LogLayoutController.java
        │               ├── MainLayoutController.java
        │               └── TextAreaLogHandler.java
        └── resources/
            ├── META-INF/
            │   └── MANIFEST.MF
            └── resources/
                ├── bundle/
                │   ├── ui_en.properties
                │   ├── ui_zh_CN.properties
                │   └── ui_zh_TW.properties
                └── ui/
                    ├── LogLayout.fxml
                    └── MainLayout.fxml

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# ignore output folders
.idea/
out/
target/

# ignore files
config.json
*.iml



================================================
FILE: LICENSE
================================================
shadowsocks-java is distributed under the following BSD-style license:

Copyright (c) 2015, Blake <stfl0622@gmail.com>
All Rights Reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior
written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

================================================
FILE: README.md
================================================
This project is being discontinued
==================================

Since I am busy at work in the past few years and couldn't find time to maintain it, I believe it's time to achieve it. Thank you to everyone who has used/interested in shadowsocks-java.


shadowsocks-java
================

shadowsocks-java is a pure JAVA client for [shadowsocks](https://github.com/shadowsocks/shadowsocks) project.

Only tested AES encryption.

### Requirements
    * Bouncy Castle v1.5.2 [Release](https://www.bouncycastle.org/)
    * json-simple v1.1.1 [Release](https://code.google.com/p/json-simple/)
    
### Developers
    * Using Non-blocking server
        Config config = new Config("SS_SERVER_IP", "SS_SERVER_PORT", "LOCAL_IP", "LOCAL_PORT", "CIPHER_NAME", "PASSWORD");
        NioLocalServer server = new NioLocalServer(config);
        new Thread(server).start();
        
    * Using blocking server
        Config config = new Config("SS_SERVER_IP", "SS_SERVER_PORT", "LOCAL_IP", "LOCAL_PORT", "CIPHER_NAME", "PASSWORD");
        LocalServer server = new LocalServer(config);
        new Thread(server).start();


================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.stfl</groupId>
    <artifactId>shadowsocks-java</artifactId>
    <version>0.2-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.52</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
            <!-- remove junit dependency since it's not required during runtime -->
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.hamcrest</groupId>
                    <artifactId>hamcrest-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>


================================================
FILE: src/main/java/com/stfl/Constant.java
================================================
package com.stfl;

import java.util.Locale;

public class Constant {
    public static final String PROG_NAME = "shadowsocks-java";
    public static final String VERSION = "0.2";
    public static final int BUFFER_SIZE = 16384;
    public static final String CONF_FILE = "config.json";
    public static final Locale LOCALE = Locale.getDefault();
}


================================================
FILE: src/main/java/com/stfl/Main.java
================================================
package com.stfl;

import com.stfl.misc.Config;
import com.stfl.misc.Util;
import com.stfl.network.LocalServer;
import com.stfl.network.NioLocalServer;
import com.stfl.network.proxy.IProxy;
import com.stfl.network.proxy.ProxyFactory;
import com.stfl.ss.CryptFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;

public class Main {
    private static Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) {
        if (args.length != 0) {
            startCommandLine(args);
        }
        else {
            MainGui.launch(MainGui.class);
        }
    }

    private static void startCommandLine(String[] args) {
        Config config;

        config = parseArgument(args);
        if (config == null) {
            printUsage();
            return;
        }

        Util.saveFile(Constant.CONF_FILE, config.saveToJson());

        try {
            //LocalServer server = new LocalServer(config);
            NioLocalServer server = new NioLocalServer(config);
            Thread t = new Thread(server);
            t.start();
            t.join();
        } catch (Exception e) {
            logger.warning("Unable to start server: " + e.toString());
        }
    }

    private static Config parseArgument(String[] args) {
        Config config = new Config();

        if (args.length == 2) {
            if (args[0].equals("--config")) {
                Path path = Paths.get(args[1]);
                try {
                    String json = new String(Files.readAllBytes(path));
                    config.loadFromJson(json);
                } catch (IOException e) {
                    System.out.println("Unable to read configuration file: " + args[1]);
                    return null;
                }
                return config;
            }
            else {
                return null;
            }
        }

        if (args.length != 8) {
            return null;
        }

        // parse arguments
        for (int i = 0; i < args.length; i+=2) {
            String[] tempArgs;
            if (args[i].equals("--local")) {
                tempArgs = args[i+1].split(":");
                if (tempArgs.length < 2) {
                    System.out.println("Invalid argument: " + args[i]);
                    return null;
                }
                config.setLocalIpAddress(tempArgs[0]);
                config.setLocalPort(Integer.parseInt(tempArgs[1]));
            }
            else if (args[i].equals("--remote")) {
                tempArgs = args[i+1].split(":");
                if (tempArgs.length < 2) {
                    System.out.println("Invalid argument: " + args[i]);
                    return null;
                }
                config.setRemoteIpAddress(tempArgs[0]);
                config.setRemotePort(Integer.parseInt(tempArgs[1]));
            }
            else if (args[i].equals("--cipher")) {
                config.setMethod(args[i+1]);
            }
            else if (args[i].equals("--password")) {
                config.setPassword(args[i + 1]);
            }
            else if (args[i].equals("--proxy")) {
                config.setProxyType(args[i + 1]);
            }
        }

        return config;
    }

    private static void printUsage() {
        System.out.println("Usage: ss --[option] value --[option] value...");
        System.out.println("Option:");
        System.out.println("  --local [IP:PORT]");
        System.out.println("  --remote [IP:PORT]");
        System.out.println("  --cipher [CIPHER_NAME]");
        System.out.println("  --password [PASSWORD]");
        System.out.println("  --config [CONFIG_FILE]");
        System.out.println("  --proxy [TYPE]");
        System.out.println("Support Proxy Type:");
        for (IProxy.TYPE t : ProxyFactory.getSupportedProxyTypes()) {
            System.out.printf("  %s\n", t.toString().toLowerCase());
        }
        System.out.println("Support Ciphers:");
        for (String s : CryptFactory.getSupportedCiphers()) {
            System.out.printf("  %s\n", s);
        }
        System.out.println("Example:");
        System.out.println("  ss --local \"127.0.0.1:1080\" --remote \"[SS_SERVER_IP]:1080\" --cipher \"aes-256-cfb\" --password \"HelloWorld\"");
        System.out.println("  ss --config config.json");
    }
}


================================================
FILE: src/main/java/com/stfl/MainGui.java
================================================
package com.stfl;

import com.stfl.misc.UTF8Control;
import com.stfl.ui.MainLayoutController;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ResourceBundle;
import java.util.logging.Logger;

public class MainGui extends Application {
    private static Logger logger = Logger.getLogger(MainGui.class.getName());
    private Stage primaryStage;
    private Scene rootScene;
    private MainLayoutController controller;
    private TrayIcon trayIcon;

    @Override
    public void start(Stage primaryStage) throws Exception {

        Platform.setImplicitExit(false);
        this.primaryStage = primaryStage;
        this.primaryStage.setTitle("Server Configuration");

        try {
            // Load the root layout from the fxml file
            FXMLLoader mainLayoutLoader = new FXMLLoader(MainGui.class.getResource("/resources/ui/MainLayout.fxml"));
            mainLayoutLoader.setResources(ResourceBundle.getBundle("resources.bundle.ui", Constant.LOCALE, new UTF8Control()));
            Pane rootLayout = mainLayoutLoader.load();

            rootScene = new Scene(rootLayout);
            primaryStage.setScene(rootScene);
            primaryStage.setResizable(false);

            controller = mainLayoutLoader.getController();
            controller.setMainGui(this);

            addToTray();

            primaryStage.getIcons().add(new Image(MainGui.class.getResource("/resources/image/icon.png").toString()));
            primaryStage.show();
        } catch (IOException e) {
            // Exception gets thrown if the fxml file could not be loaded
            e.printStackTrace();
        }
    }


    private void addToTray() {
        // ensure awt is initialized
        java.awt.Toolkit.getDefaultToolkit();

        // make sure system tray is supported
        if (!java.awt.SystemTray.isSupported()) {
            logger.warning("No system tray support!");
        }

        final java.awt.SystemTray tray = java.awt.SystemTray.getSystemTray();
        try {

            java.awt.Image image = ImageIO.read(MainGui.class.getResource("/resources/image/icon.png"));
            trayIcon = new TrayIcon(image);
            trayIcon.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            primaryStage.show();
                        }
                    });
                }
            });

            java.awt.MenuItem openItem = new java.awt.MenuItem("Configuration");
            openItem.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            show();
                        }
                    });
                }
            });

            java.awt.MenuItem exitItem = new java.awt.MenuItem("Exit");
            exitItem.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    controller.closeServer();
                    Platform.exit();
                    tray.remove(trayIcon);
                }
            });

            PopupMenu popup = new PopupMenu();
            popup.add(openItem);
            popup.addSeparator();
            popup.add(exitItem);
            trayIcon.setPopupMenu(popup);
            trayIcon.setToolTip("Not Connected");
            tray.add(trayIcon);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    public void show() {
        primaryStage.show();
    }

    public void hide() {
        primaryStage.hide();
    }

    public void setTooltip(String message) {
        if (trayIcon != null) {
            trayIcon.setToolTip(message);
        }
    }

    public void showNotification(String message) {
        trayIcon.displayMessage(
                "shadowsocks-java",
                message,
                java.awt.TrayIcon.MessageType.INFO
        );
    }
}


================================================
FILE: src/main/java/com/stfl/misc/Config.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.misc;

import com.stfl.network.proxy.IProxy;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.stfl.ss.AesCrypt;

/**
 * Data class for configuration to bring up server
 */
public class Config {
    private String _ipAddr;
    private int _port;
    private String _localIpAddr;
    private int _localPort;
    private String _method;
    private String _password;
    private String _logLevel;
    private IProxy.TYPE _proxyType;

    public Config() {
        loadFromJson("");
    }

    public Config(String ipAddr, int port, String localIpAddr, int localPort, String method, String password) {
        this();
        _ipAddr = ipAddr;
        _port = port;
        _localIpAddr = localIpAddr;
        _localPort = localPort;
        _method = method;
        _password = password;
        _proxyType = IProxy.TYPE.AUTO;
    }

    public Config(String ipAddr, int port, String localIpAddr, int localPort, String method, String password, IProxy.TYPE type) {
        this(ipAddr, port, localIpAddr, localPort, method, password);
        _proxyType = type;
    }

    public void setRemoteIpAddress(String value) {
        _ipAddr = value;
    }

    public String getRemoteIpAddress() {
        return _ipAddr;
    }

    public void setLocalIpAddress(String value) {
        _localIpAddr = value;
    }

    public String getLocalIpAddress() {
        return _localIpAddr;
    }

    public void setRemotePort(int value) {
        _port = value;
    }

    public int getRemotePort() {
        return _port;
    }

    public void setLocalPort(int value) {
        _localPort = value;
    }

    public int getLocalPort() {
        return _localPort;
    }

    public void setProxyType(String value) {
        _proxyType = IProxy.TYPE.AUTO;
        if (value.toLowerCase().equals(IProxy.TYPE.HTTP.toString().toLowerCase())) {
            _proxyType = IProxy.TYPE.HTTP;
        }
        else if (value.toLowerCase().equals(IProxy.TYPE.SOCKS5.toString().toLowerCase())) {
            _proxyType = IProxy.TYPE.SOCKS5;
        }
    }

    public void setProxyType(IProxy.TYPE value) {
        _proxyType = value;
    }
    public IProxy.TYPE getProxyType() {
        return _proxyType;
    }

    public void setMethod(String value) {
        _method = value;
    }

    public String getMethod() {
        return _method;
    }

    public void setPassword(String value) {
        _password = value;
    }

    public String getPassword() {
        return _password;
    }

    public void setLogLevel(String value) {
        _logLevel = value;
        Log.init(getLogLevel());
    }

    public String getLogLevel() {
        return _logLevel;
    }

    public void loadFromJson(String jsonStr) {
        if (jsonStr.length() == 0) {
            jsonStr = "{}";
        }

        JSONObject jObj = (JSONObject)JSONValue.parse(jsonStr);
        _ipAddr = (String)jObj.getOrDefault("remoteIpAddress", "");
        _port = ((Number)jObj.getOrDefault("remotePort", 1080)).intValue();
        _localIpAddr = (String)jObj.getOrDefault("localIpAddress", "127.0.0.1");
        _localPort = ((Number)jObj.getOrDefault("localPort", 1080)).intValue();
        _method = (String)jObj.getOrDefault("method", AesCrypt.CIPHER_AES_256_CFB);
        _password = (String)jObj.getOrDefault("password", "");
        _logLevel = (String)jObj.getOrDefault("logLevel", "INFO");
        setProxyType((String) jObj.getOrDefault("proxyType", IProxy.TYPE.SOCKS5.toString().toLowerCase()));
        setLogLevel(_logLevel);
    }

    public String saveToJson() {
        JSONObject jObj = new JSONObject();
        jObj.put("remoteIpAddress", _ipAddr);
        jObj.put("remotePort", _port);
        jObj.put("localIpAddress", _localIpAddr);
        jObj.put("localPort", _localPort);
        jObj.put("method", _method);
        jObj.put("password", _password);
        jObj.put("proxyType", _proxyType.toString().toLowerCase());
        jObj.put("logLevel", _logLevel);

        return Util.prettyPrintJson(jObj);
    }
}


================================================
FILE: src/main/java/com/stfl/misc/Log.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.misc;

import java.util.Locale;
import java.util.Properties;
import java.util.logging.*;

/**
 * Initialized level of root logger
 */
public class Log {
    private static boolean handlerInit = false;

    public static void init() {
        init(Level.INFO);
    }

    public static void init(Level level) {
        Logger rootLogger = getRootLogger();
        if (handlerInit) {
            rootLogger.setLevel(level);
            for(Handler handler : rootLogger.getHandlers()) {
                handler.setLevel(level);
            }
            return;
        }

        // disable message localization
        Locale.setDefault(Locale.ENGLISH);
        // config log output format
        Properties props = System.getProperties();
        props.setProperty("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tb-%1$td %1$tT [%4$s] %5$s%n");
        // setup root logger
        //Logger rootLogger = getRootLogger();
        rootLogger.setUseParentHandlers(false);
        for(Handler handler : rootLogger.getHandlers()) {
            rootLogger.removeHandler(handler);
        }
        // set log level and format
        rootLogger.setLevel(level);
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(level);
        rootLogger.addHandler(handler);
        handlerInit = true;
    }

    public static void init(String level) {
        Level l = Level.parse(level);
        init(l);
    }

    public static void addHandler(Handler handler) {
        Logger rootLogger = getRootLogger();
        Level logLevel = Level.INFO;
        for (Handler h : rootLogger.getHandlers()) {
            logLevel = h.getLevel();
        }

        handler.setLevel(logLevel);
        rootLogger.addHandler(handler);
    }

    private static Logger getRootLogger() {
        return Logger.getLogger("com.stfl");
    }
}


================================================
FILE: src/main/java/com/stfl/misc/Reflection.java
================================================
package com.stfl.misc;

import java.lang.reflect.Constructor;

public class Reflection {
    public static Object get(String className, Object... args) {
        Object retValue = null;
        try {
            Class c = Class.forName(className);
            if (args.length == 0) {
                retValue = c.newInstance();
            }
            else if ((args.length & 1) == 0) {
                // args should come with pairs, for example
                // String.class, "arg1_value", String.class, "arg2_value"
                Class[] oParam = new Class[args.length / 2];
                for (int arg_i = 0, i = 0; arg_i < args.length; arg_i+=2, i++) {
                    oParam[i] = (Class)args[arg_i];
                }

                Constructor constructor = c.getConstructor(oParam);
                Object[] paramObjs = new Object[args.length / 2];
                for (int arg_i = 1, i = 0; arg_i < args.length; arg_i+=2, i++) {
                    paramObjs[i] = args[arg_i];
                }
                retValue = constructor.newInstance(paramObjs);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return retValue;
    }
}


================================================
FILE: src/main/java/com/stfl/misc/UTF8Control.java
================================================
package com.stfl.misc;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

public class UTF8Control extends ResourceBundle.Control {
    public ResourceBundle newBundle
            (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException
    {
        // below is the original implementation
        String bundleName = toBundleName(baseName, locale);
        String resourceName = toResourceName(bundleName, "properties");
        ResourceBundle bundle = null;
        InputStream stream = null;
        if (reload) {
            URL url = loader.getResource(resourceName);
            if (url != null) {
                URLConnection connection = url.openConnection();
                if (connection != null) {
                    connection.setUseCaches(false);
                    stream = connection.getInputStream();
                }
            }
        } else {
            stream = loader.getResourceAsStream(resourceName);
        }

        if (stream != null) {
            try {
                // load string use UTF-8 encoding
                bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
            } finally {
                stream.close();
            }
        }
        return bundle;
    }
}


================================================
FILE: src/main/java/com/stfl/misc/Util.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.misc;

import com.stfl.network.proxy.Socks5Proxy;
import org.json.simple.JSONObject;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;

/**
 * Helper class
 */
public class Util {
    public static String dumpBytes(byte[] a) {
        StringBuilder sb = new StringBuilder(a.length * 2);
        for(byte b: a)
            sb.append(String.format("%x", b & 0xff));
        return sb.toString();
    }

    public static byte[] randomBytes(int size) {
        byte[] bytes = new byte[size];
        new SecureRandom().nextBytes(bytes);
        return bytes;
    }

    public static String getErrorMessage(Throwable e) {
        Writer writer = new StringWriter();
        PrintWriter pWriter = new PrintWriter(writer);
        e.printStackTrace(pWriter);
        return writer.toString();
    }

    public static String prettyPrintJson(JSONObject jObj) {
        String retValue;
        StringWriter writer = new StringWriter() {
            private final static String indent = "  ";
            private final String LINE_SEP = System.getProperty("line.separator");
            private int indentLevel = 0;

            @Override
            public void write(int c) {
                char ch = (char) c;
                if (ch == '[' || ch == '{') {
                    super.write(c);
                    super.write(LINE_SEP);
                    indentLevel++;
                    writeIndentation();
                }
                else if (ch == ']' || ch == '}') {
                    super.write(LINE_SEP);
                    indentLevel--;
                    writeIndentation();
                    super.write(c);
                }
                else if (ch == ':') {
                    super.write(c);
                    super.write(" ");
                }
                else if (ch == ',') {
                    super.write(c);
                    super.write(LINE_SEP);
                    writeIndentation();
                }
                else {
                    super.write(c);
                }

            }

            private void writeIndentation()
            {
                for (int i = 0; i < indentLevel; i++)
                {
                    super.write(indent);
                }
            }
        };

        try {
            jObj.writeJSONString(writer);
            retValue = writer.toString();
        } catch (IOException e) {
            // something wrong with writer, use the original method
            retValue = jObj.toJSONString();
        }

        return retValue;
    }

    public static String getRequestedHostInfo(byte[] data) {
        String ret = "";
        int port;
        int neededLength;
        switch (data[0]) {
            case Socks5Proxy.ATYP_IP_V4:
                // IP v4 Address
                // 4 bytes of IP, 2 bytes of port
                neededLength = 6;
                if (data.length > neededLength) {
                    port = getPort(data[5], data[6]);
                    ret = String.format("%d.%d.%d.%d:%d", data[1], data[2], data[3], data[4], port);
                }
                break;
            case Socks5Proxy.ATYP_DOMAIN_NAME:
                // domain
                neededLength = data[1];
                if (data.length > neededLength + 3) {
                    port = getPort(data[neededLength + 2], data[neededLength + 3]);
                    String domain = bytesToString(data, 2, neededLength);
                    ret = String.format("%s:%d", domain, port);
                }
                break;
            case Socks5Proxy.ATYP_IP_V6:
                // IP v6 Address
                // 16 bytes of IP, 2 bytes of port
                neededLength = 18;
                if (data.length > neededLength) {
                    port = getPort(data[17], data[18]);
                    ret = String.format("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%d",
                            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
                            data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16],
                            port);
                }
                break;
        }

        return ret;
    }

    public static String bytesToString(byte[] data, int start, int length) {
        String str = "";

        try {
            str = new String(data, start, length, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return str;
    }

    public static byte[] composeSSHeader(String host, int port) {
        // TYPE (1 byte) + LENGTH (1 byte) + HOST (var bytes) + PORT (2 bytes)
        byte[] respData = new byte[host.length() + 4];

        respData[0] = Socks5Proxy.ATYP_DOMAIN_NAME;
        respData[1] = (byte)host.length();
        System.arraycopy(host.getBytes(), 0, respData, 2, host.length());
        respData[host.length() + 2] = (byte)(port >> 8);
        respData[host.length() + 3] = (byte)(port & 0xFF);

        return  respData;
    }

    public static boolean saveFile(String fn, String content) {
        PrintWriter writer;
        try {
            writer = new PrintWriter(fn);
            writer.println(content);
            writer.close();
        } catch (FileNotFoundException e) {
            return false;
        }
        return true;
    }

    public static String getFileContent(String fn) {
        Path path = Paths.get(fn);
        String content = "";
        try {
            content = new String(Files.readAllBytes(path));
        } catch (IOException e) {
            // do nothing
        }

        return content;
    }

    private static short byteToUnsignedByte(byte b) {
        return (short)(b & 0xff);
    }

    private static int getPort(byte b, byte b1) {
        return byteToUnsignedByte(b) << 8 | byteToUnsignedByte(b1);
    }
}


================================================
FILE: src/main/java/com/stfl/network/IServer.java
================================================
package com.stfl.network;

public interface IServer extends Runnable {
    void close();
}


================================================
FILE: src/main/java/com/stfl/network/LocalServer.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidAlgorithmParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import com.stfl.Constant;
import com.stfl.misc.Config;
import com.stfl.misc.Util;
import com.stfl.network.io.PipeSocket;
import com.stfl.ss.CryptFactory;

/**
 * Blocking local server for shadowsocks
 */
public class LocalServer implements IServer {
    private Logger logger = Logger.getLogger(LocalServer.class.getName());
    private Config _config;
    private ServerSocket _serverSocket;
    private Executor _executor;
    private List<PipeSocket> _pipes;

    public LocalServer(Config config) throws IOException, InvalidAlgorithmParameterException {
        if (!CryptFactory.isCipherExisted(config.getMethod())) {
            throw new InvalidAlgorithmParameterException(config.getMethod());
        }
        _config = config;
        _serverSocket = new ServerSocket(config.getLocalPort(), 128);
        _executor = Executors.newCachedThreadPool();
        _pipes = new ArrayList<>();

        // print server info
        logger.info("Shadowsocks-Java v" + Constant.VERSION);
        logger.info(config.getProxyType() + " Proxy Server starts at port: " + config.getLocalPort());
    }

    @Override
    public void run() {
        while (true) {
            try {
                Socket localSocket = _serverSocket.accept();
                PipeSocket pipe = new PipeSocket(_executor, localSocket, _config);
                _pipes.add(pipe);
                _executor.execute(pipe);
            } catch (IOException e) {
                logger.warning(Util.getErrorMessage(e));
            }
        }
    }

    public void close() {
        try {
            for (PipeSocket p : _pipes) {
                p.close();
            }
            _pipes.clear();
            _serverSocket.close();
        } catch (IOException e) {
            logger.warning(Util.getErrorMessage(e));
        }
    }

}


================================================
FILE: src/main/java/com/stfl/network/NioLocalServer.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network;

import com.stfl.Constant;
import com.stfl.misc.Config;
import com.stfl.misc.Util;
import com.stfl.network.nio.*;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.security.InvalidAlgorithmParameterException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

/**
 * Non-blocking local server for shadowsocks
 */
public class NioLocalServer extends SocketHandlerBase {
    private Logger logger = Logger.getLogger(NioLocalServer.class.getName());

    private ServerSocketChannel _serverChannel;
    private RemoteSocketHandler _remoteSocketHandler;
    private ExecutorService _executor;

    public NioLocalServer(Config config) throws IOException, InvalidAlgorithmParameterException {
        super(config);
        _executor = Executors.newCachedThreadPool();

        // init remote socket handler
        _remoteSocketHandler = new RemoteSocketHandler(_config);
        _executor.execute(_remoteSocketHandler);

        // print server info
        logger.info("Shadowsocks-Java v" + Constant.VERSION);
        logger.info("Cipher: " + config.getMethod());
        logger.info(config.getProxyType() + " Proxy Server starts at port: " + config.getLocalPort());
    }

    @Override
    protected Selector initSelector() throws IOException {
        Selector socketSelector = SelectorProvider.provider().openSelector();
        _serverChannel = ServerSocketChannel.open();
        _serverChannel.configureBlocking(false);
        InetSocketAddress isa = new InetSocketAddress(_config.getLocalIpAddress(), _config.getLocalPort());
        _serverChannel.socket().bind(isa);
        _serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);

        return socketSelector;
    }

    @Override
    protected boolean processPendingRequest(ChangeRequest request) {
        switch (request.type) {
            case ChangeRequest.CHANGE_SOCKET_OP:
                SelectionKey key = request.socket.keyFor(_selector);
                if ((key != null) && key.isValid()) {
                    key.interestOps(request.op);
                } else {
                    logger.warning("NioLocalServer::processPendingRequest (drop): " + key + request.socket);
                }
                break;
            case ChangeRequest.CLOSE_CHANNEL:
                cleanUp(request.socket);
                break;
        }
        return true;
    }

    @Override
    protected void processSelect(SelectionKey key) {
        // Handle event
        try {
            if (key.isAcceptable()) {
                accept(key);
            } else if (key.isReadable()) {
                read(key);
            } else if (key.isWritable()) {
                write(key);
            }
        }
        catch (IOException e) {
            cleanUp((SocketChannel)key.channel());
        }
    }

    private void accept(SelectionKey key) throws IOException {
        // local socket established
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(_selector, SelectionKey.OP_READ);

        // prepare local socket write queue
        createWriteBuffer(socketChannel);

        // create pipe between local and remote socket
        PipeWorker pipe = _remoteSocketHandler.createPipe(this, socketChannel, _config.getRemoteIpAddress(), _config.getRemotePort());
        _pipes.put(socketChannel, pipe);
        _executor.execute(pipe);
    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int readCount;
        PipeWorker pipe = _pipes.get(socketChannel);
        byte[] data;

        if (pipe == null) {
            // should not happen
            cleanUp(socketChannel);
            return;
        }

        _readBuffer.clear();
        try {
            readCount = socketChannel.read(_readBuffer);
        } catch (IOException e) {
            cleanUp(socketChannel);
            return;
        }

        if (readCount == -1) {
            cleanUp(socketChannel);
            return;
        }

        data = _readBuffer.array();
        pipe.processData(data, readCount, true);
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();

        List queue = (List) _pendingData.get(socketChannel);
        if (queue != null) {
            synchronized (queue) {
                // Write data
                while (!queue.isEmpty()) {
                    ByteBuffer buf = (ByteBuffer) queue.get(0);
                    socketChannel.write(buf);
                    if (buf.remaining() > 0) {
                        break;
                    }
                    queue.remove(0);
                }

                if (queue.isEmpty()) {
                    key.interestOps(SelectionKey.OP_READ);
                }
            }
        }
        else {
            logger.warning("LocalSocket::write queue = null: " + socketChannel);
            return;
        }
    }

    @Override
    protected void cleanUp(SocketChannel socketChannel) {
        //logger.warning("LocalSocket closed: " + socketChannel);
        super.cleanUp(socketChannel);

        PipeWorker pipe = _pipes.get(socketChannel);
        if (pipe != null) {
            pipe.close();
            _pipes.remove(socketChannel);
            logger.fine("LocalSocket closed: " + pipe.socketInfo);
        }
        else {
            logger.fine("LocalSocket closed (NULL): " + socketChannel);
        }

    }

    @Override
    public void close() {
        super.close();
        _executor.shutdownNow();

        try {
            _serverChannel.close();
            _remoteSocketHandler.close();
        } catch (IOException e) {
            logger.warning(Util.getErrorMessage(e));
        }
        logger.info("Server closed.");
    }
}


================================================
FILE: src/main/java/com/stfl/network/io/PipeSocket.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.io;

import com.stfl.misc.Config;
import com.stfl.misc.Util;
import com.stfl.Constant;
import com.stfl.network.proxy.IProxy;
import com.stfl.network.proxy.ProxyFactory;
import com.stfl.ss.CryptFactory;
import com.stfl.ss.ICrypt;

import java.io.*;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;

/**
 * Pipe local and remote sockets while server is running under blocking mode.
 */
public class PipeSocket implements Runnable {
    private Logger logger = Logger.getLogger(PipeSocket.class.getName());

    private final int TIMEOUT = 10000; // 10s
    private ByteArrayOutputStream _remoteOutStream;
    private ByteArrayOutputStream _localOutStream;
    private Socket _remote;
    private Socket _local;
    private IProxy _proxy;
    private ICrypt _crypt;
    private boolean _isClosed;
    private Executor _executor;
    private Config _config;

    public PipeSocket(Executor executor, Socket socket, Config config) throws IOException {
        _executor = executor;
        _local = socket;
        _local.setSoTimeout(TIMEOUT);
        _config = config;
        _crypt = CryptFactory.get(_config.getMethod(), _config.getPassword());
        _proxy = ProxyFactory.get(_config.getProxyType());
        _remoteOutStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE);
        _localOutStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE);
    }

    @Override
    public void run() {
        try {
            _remote = initRemote(_config);
            _remote.setSoTimeout(TIMEOUT);
        } catch (IOException e) {
            close();
            logger.warning(Util.getErrorMessage(e));
            return;
        }

        _executor.execute(getLocalWorker());
        _executor.execute(getRemoteWorker());
    }

    private Socket initRemote(Config config) throws IOException {
        return new Socket(config.getRemoteIpAddress(), config.getRemotePort());
    }

    private Runnable getLocalWorker() {
        return new Runnable() {
            @Override
            public void run() {
                BufferedInputStream stream;
                byte[] dataBuffer = new byte[Constant.BUFFER_SIZE];
                byte[] buffer;
                int readCount;
                List<byte[]> sendData = null;

                // prepare local stream
                try {
                    stream = new BufferedInputStream(_local.getInputStream());
                } catch (IOException e) {
                    logger.info(e.toString());
                    return;
                }

                // start to process data from local socket
                while (true) {
                    try {
                         // read data
                        readCount = stream.read(dataBuffer);
                        if (readCount == -1) {
                            throw new IOException("Local socket closed (Read)!");
                        }

                        // initialize proxy
                        if (!_proxy.isReady()) {
                            byte[] temp;
                            buffer = new byte[readCount];

                            // dup dataBuffer to use in later
                            System.arraycopy(dataBuffer, 0, buffer, 0, readCount);

                            temp = _proxy.getResponse(buffer);
                            if ((temp != null) && (!_sendLocal(temp, temp.length))) {
                                throw new IOException("Local socket closed (proxy-Write)!");
                            }
                            // packet for remote socket
                            sendData = _proxy.getRemoteResponse(buffer);
                            if (sendData == null) {
                                continue;
                            }
                            logger.info("Connected to: " + Util.getRequestedHostInfo(sendData.get(0)));
                        }
                        else {
                            sendData.clear();
                            sendData.add(dataBuffer);
                        }

                        for (byte[] bytes : sendData) {
                            // send data to remote socket
                            if (!sendRemote(bytes, bytes.length)) {
                                throw new IOException("Remote socket closed (Write)!");
                            }
                        }
                    } catch (SocketTimeoutException e) {
                        continue;
                    } catch (IOException e) {
                        logger.fine(e.toString());
                        break;
                    }
                }
                close();
                logger.fine(String.format("localWorker exit, Local=%s, Remote=%s", _local, _remote));
            }
        };
    }

    private Runnable getRemoteWorker() {
        return new Runnable() {
            @Override
            public void run() {
                BufferedInputStream stream;
                int readCount;
                byte[] dataBuffer = new byte[4096];

                // prepare remote stream
                try {
                    //stream = _remote.getInputStream();
                    stream = new BufferedInputStream (_remote.getInputStream());
                } catch (IOException e) {
                    logger.info(e.toString());
                    return;
                }

                // start to process data from remote socket
                while (true) {
                    try {
                        readCount = stream.read(dataBuffer);
                        if (readCount == -1) {
                            throw new IOException("Remote socket closed (Read)!");
                        }

                        // send data to local socket
                        if (!sendLocal(dataBuffer, readCount)) {
                            throw new IOException("Local socket closed (Write)!");
                        }
                    } catch (SocketTimeoutException e) {
                        continue;
                    } catch (IOException e) {
                        logger.fine(e.toString());
                        break;
                    }

                }
                close();
                logger.fine(String.format("remoteWorker exit, Local=%s, Remote=%s", _local, _remote));
            }
        };
    }

    public void close() {
        if (_isClosed) {
            return;
        }
        _isClosed = true;

        try {
            _local.shutdownInput();
            _local.shutdownOutput();
            _local.close();
        } catch (IOException e) {
            logger.fine("PipeSocket failed to close local socket (I/O exception)!");
        }
        try {
            if (_remote != null) {
                _remote.shutdownInput();
                _remote.shutdownOutput();
                _remote.close();
            }
        } catch (IOException e) {
            logger.fine("PipeSocket failed to close remote socket (I/O exception)!");
        }
    }

    private boolean sendRemote(byte[] data, int length) {
        _crypt.encrypt(data, length, _remoteOutStream);
        byte[] sendData = _remoteOutStream.toByteArray();

        return _sendRemote(sendData, sendData.length);
    }

    private boolean _sendRemote(byte[] data, int length) {
        try {
            if (length > 0) {
                OutputStream outStream = _remote.getOutputStream();
                outStream.write(data, 0, length);
            }
            else {
                logger.info("Nothing to sendRemote!\n");
            }
        } catch (IOException e) {
            logger.info(Util.getErrorMessage(e));
            return false;
        }

        return true;
    }

    private boolean sendLocal(byte[] data, int length) {
        _crypt.decrypt(data, length, _localOutStream);
        byte[] sendData = _localOutStream.toByteArray();

        return _sendLocal(sendData, sendData.length);
    }

    private boolean _sendLocal(byte[] data, int length) {
        try {
            OutputStream outStream = _local.getOutputStream();
            outStream.write(data, 0, length);
        } catch (IOException e) {
            logger.info(Util.getErrorMessage(e));
            return false;
        }
        return true;
    }
}


================================================
FILE: src/main/java/com/stfl/network/nio/ChangeRequest.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.nio;

import java.nio.channels.SocketChannel;

/**
 * Request for nio socket handler
 */
public class ChangeRequest {
    public static final int REGISTER_CHANNEL = 1;
    public static final int CHANGE_SOCKET_OP = 2;
    public static final int CLOSE_CHANNEL = 3;

    public SocketChannel socket;
    public int type;
    public int op;

    public ChangeRequest(SocketChannel socket, int type, int op) {
        this.socket = socket;
        this.type = type;
        this.op = op;
    }

    public ChangeRequest(SocketChannel socket, int type) {
        this(socket, type, 0);
    }
}


================================================
FILE: src/main/java/com/stfl/network/nio/ISocketHandler.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.nio;

/**
 * Interface of socket handler
 */
public interface ISocketHandler {
    void send(ChangeRequest request, byte[] data);
    void send(ChangeRequest request);
}


================================================
FILE: src/main/java/com/stfl/network/nio/PipeEvent.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.nio;

/**
 * pipe event for pipe worker
 */
public class PipeEvent {
    public byte[] data;
    public boolean isEncrypted;

    public PipeEvent() {}

    public PipeEvent(byte[] data, boolean isEncrypted) {
        this.data = data;
        this.isEncrypted = isEncrypted;
    }
}


================================================
FILE: src/main/java/com/stfl/network/nio/PipeWorker.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.nio;

import com.stfl.misc.Config;
import com.stfl.misc.Util;
import com.stfl.Constant;
import com.stfl.network.proxy.IProxy;
import com.stfl.network.proxy.ProxyFactory;
import com.stfl.ss.CryptFactory;
import com.stfl.ss.ICrypt;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;


public class PipeWorker implements Runnable {
    private Logger logger = Logger.getLogger(PipeWorker.class.getName());
    private SocketChannel _localChannel;
    private SocketChannel _remoteChannel;
    private ISocketHandler _localSocketHandler;
    private ISocketHandler _remoteSocketHandler;
    private IProxy _proxy;
    private ICrypt _crypt;
    public String socketInfo;
    private ByteArrayOutputStream _outStream;
    private BlockingQueue _processQueue;
    private volatile boolean requestedClose;

    public PipeWorker(ISocketHandler localHandler, SocketChannel localChannel, ISocketHandler remoteHandler, SocketChannel remoteChannel, Config config) {
        _localChannel = localChannel;
        _remoteChannel = remoteChannel;
        _localSocketHandler = localHandler;
        _remoteSocketHandler = remoteHandler;
        _crypt = CryptFactory.get(config.getMethod(), config.getPassword());
        _proxy = ProxyFactory.get(config.getProxyType());
        _outStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE);
        _processQueue = new LinkedBlockingQueue();
        requestedClose = false;
        socketInfo = String.format("Local: %s, Remote: %s", localChannel, remoteChannel);
    }

    public void close() {
        requestedClose = true;
        processData(null, 0, false);
    }

    public void forceClose() {
        logger.fine("PipeWorker::forceClose " + socketInfo);

        // close socket now!
        try {
            if (_localChannel.isOpen()) {
                _localChannel.close();
            }
            if (_remoteChannel.isOpen()) {
                _remoteChannel.close();
            }
        } catch (IOException e) {
            logger.fine("PipeWorker::forceClose> " + e.toString());
        }

        // follow the standard close steps
        close();
    }

    public void processData(byte[] data, int count, boolean isEncrypted) {
        if (data != null) {
            byte[] dataCopy = new byte[count];
            System.arraycopy(data, 0, dataCopy, 0, count);
            _processQueue.add(new PipeEvent(dataCopy, isEncrypted));
        }
        else {
            _processQueue.add(new PipeEvent());
        }
    }

    @Override
    public void run() {
        PipeEvent event;
        ISocketHandler socketHandler;
        SocketChannel channel;
        List<byte[]> sendData = null;

        while(true) {
            // make sure all the requests in the queue are processed
            if (_processQueue.isEmpty() && requestedClose) {
                logger.fine("PipeWorker closed ("+  _processQueue.size() + "): " + this.socketInfo);
                if (_localChannel.isOpen()) {
                    _localSocketHandler.send(new ChangeRequest(_localChannel, ChangeRequest.CLOSE_CHANNEL));
                }
                if (_remoteChannel.isOpen()) {
                    _remoteSocketHandler.send(new ChangeRequest(_remoteChannel, ChangeRequest.CLOSE_CHANNEL));
                }
                break;
            }

            try {
                event = (PipeEvent)_processQueue.take();

                // if event data is null, it means this is a wake-up call
                // to check if any other thread is requested to close sockets
                if (event.data == null) {
                    continue;
                }

                // process proxy packet if needed
                if (!_proxy.isReady()) {
                    // packet for local socket
                    byte[] temp = _proxy.getResponse(event.data);
                    if (temp != null) {
                        _localSocketHandler.send(new ChangeRequest(_localChannel, ChangeRequest.CHANGE_SOCKET_OP,
                                SelectionKey.OP_WRITE), temp);
                    }
                    // packet for remote socket (ss payload + request)
                    sendData = _proxy.getRemoteResponse(event.data);
                    if (sendData == null) {
                        continue;
                    }
                    // index 0 is always ss payload
                    logger.info("Connected to: " + Util.getRequestedHostInfo(sendData.get(0)));
                    //logger.info("Test: " + Util.bytesToString(temp, 0, temp.length));
                }
                else {
                    sendData.clear();
                    sendData.add(event.data);
                }

                for (byte[] bytes : sendData) {
                    // empty stream for new data
                    _outStream.reset();

                    if (event.isEncrypted) {
                        _crypt.encrypt(bytes, _outStream);
                        channel = _remoteChannel;
                        socketHandler = _remoteSocketHandler;
                    } else {
                        _crypt.decrypt(bytes, _outStream);
                        channel = _localChannel;
                        socketHandler = _localSocketHandler;
                    }

                    // data is ready to send to socket
                    ChangeRequest request = new ChangeRequest(channel, ChangeRequest.CHANGE_SOCKET_OP, SelectionKey.OP_WRITE);
                    socketHandler.send(request, _outStream.toByteArray());
                }
            } catch (InterruptedException e) {
                logger.fine(Util.getErrorMessage(e));
                break;
            }
        }
    }
}


================================================
FILE: src/main/java/com/stfl/network/nio/RemoteSocketHandler.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.nio;

import com.stfl.misc.Config;
import com.stfl.misc.Util;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.security.InvalidAlgorithmParameterException;
import java.util.List;
import java.util.logging.Logger;

/**
 * Handler for processing all IO event for remote sockets
 */
public class RemoteSocketHandler extends SocketHandlerBase {
    private Logger logger = Logger.getLogger(RemoteSocketHandler.class.getName());

    public RemoteSocketHandler(Config config) throws IOException, InvalidAlgorithmParameterException {
        super(config);
    }

    @Override
    protected Selector initSelector() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

    @Override
    protected boolean processPendingRequest(ChangeRequest request) {
        if ((request.type != ChangeRequest.REGISTER_CHANNEL) && request.socket.isConnectionPending()) {
            return false;
        }

        SelectionKey key;
        switch (request.type) {
            case ChangeRequest.CHANGE_SOCKET_OP:
                key = request.socket.keyFor(_selector);
                if ((key != null) && key.isValid()) {
                    key.interestOps(request.op);
                } else {
                    logger.warning("RemoteSocketHandler::processPendingRequest (drop): " + key + request.socket);
                }
                break;
            case ChangeRequest.REGISTER_CHANNEL:
                try {
                    request.socket.register(_selector, request.op);
                } catch (ClosedChannelException e) {
                    // socket get closed by remote
                    logger.warning(e.toString());
                    cleanUp(request.socket);
                }
                break;
            case ChangeRequest.CLOSE_CHANNEL:
                cleanUp(request.socket);
                break;
        }

        return true;
    }

    @Override
    protected void processSelect(SelectionKey key) {
        try {
            if (key.isConnectable()) {
                finishConnection(key);
            } else if (key.isReadable()) {
                read(key);
            } else if (key.isWritable()) {
                write(key);
            }
        } catch (IOException e) {
            cleanUp((SocketChannel) key.channel());
        }
    }

    public PipeWorker createPipe(ISocketHandler localHandler, SocketChannel localChannel, String ipAddress, int port) throws IOException {
        // prepare remote socket
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress(ipAddress, port));

        // create write buffer for specified socket
        createWriteBuffer(socketChannel);

        // create pipe worker for handling encrypt and decrypt
        PipeWorker pipe = new PipeWorker(localHandler, localChannel, this, socketChannel, _config);

        // setup pipe info
        //pipe.setRemoteChannel(socketChannel);
        _pipes.put(socketChannel, pipe);

        synchronized(_pendingRequest) {
            _pendingRequest.add(new ChangeRequest(socketChannel, ChangeRequest.REGISTER_CHANNEL, SelectionKey.OP_CONNECT));
        }

        return pipe;
    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        PipeWorker pipe = _pipes.get(socketChannel);
        if (pipe == null) {
            // should not happen
            cleanUp(socketChannel);
            return;
        }

        // clear read buffer for new data
        _readBuffer.clear();

        // read data
        int readCount;
        try {
            readCount = socketChannel.read(_readBuffer);
        } catch (IOException e) {
            // remote socket closed
            cleanUp(socketChannel);

            return;
        }

        if (readCount == -1) {
            cleanUp(socketChannel);
            return;
        }

        // Handle the response
        pipe.processData(_readBuffer.array(), readCount, false);
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();

        List queue = (List) _pendingData.get(socketChannel);
        if (queue != null) {
            synchronized (queue) {
                // write data to socket
                while (!queue.isEmpty()) {
                    ByteBuffer buf = (ByteBuffer) queue.get(0);
                    socketChannel.write(buf);
                    if (buf.remaining() > 0) {
                        break;
                    }
                    queue.remove(0);
                }

                if (queue.isEmpty()) {
                    key.interestOps(SelectionKey.OP_READ);
                }
            }
        }
        else {
            logger.warning("RemoteSocket::write queue = null: " + socketChannel);
            return;
        }
    }

    private void finishConnection(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();

        try {
            socketChannel.finishConnect();
        } catch (IOException e) {
            logger.warning("RemoteSocketHandler::finishConnection I/O exception: " + e.toString());
            cleanUp(socketChannel);
            return;
        }

        key.interestOps(SelectionKey.OP_WRITE);
    }

    @Override
    protected void cleanUp(SocketChannel socketChannel) {
        super.cleanUp(socketChannel);

        PipeWorker pipe = _pipes.get(socketChannel);
        if (pipe != null) {
            pipe.close();
            _pipes.remove(socketChannel);
            logger.fine("RemoteSocket closed: " + pipe.socketInfo);
        }
        else {
            logger.fine("RemoteSocket closed (NULL): " + socketChannel);
        }
    }
}


================================================
FILE: src/main/java/com/stfl/network/nio/SocketHandlerBase.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.nio;

import com.stfl.misc.Config;
import com.stfl.misc.Util;
import com.stfl.Constant;
import com.stfl.network.IServer;
import com.stfl.ss.CryptFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.InvalidAlgorithmParameterException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;

/**
 * Base class of socket handler for processing all IO event for sockets
 */
public abstract class SocketHandlerBase implements IServer, ISocketHandler {
    private Logger logger = Logger.getLogger(SocketHandlerBase.class.getName());
    protected Selector _selector;
    protected Config _config;
    protected final List _pendingRequest = new LinkedList();
    protected final ConcurrentHashMap _pendingData = new ConcurrentHashMap();
    protected ConcurrentMap<SocketChannel, PipeWorker> _pipes = new ConcurrentHashMap<>();
    protected ByteBuffer _readBuffer = ByteBuffer.allocate(Constant.BUFFER_SIZE);

    protected abstract Selector initSelector() throws IOException;
    protected abstract boolean processPendingRequest(ChangeRequest request);
    protected abstract void processSelect(SelectionKey key);


    public SocketHandlerBase(Config config) throws IOException, InvalidAlgorithmParameterException {
        if (!CryptFactory.isCipherExisted(config.getMethod())) {
            throw new InvalidAlgorithmParameterException(config.getMethod());
        }
        _config = config;
        _selector = initSelector();
    }

    @Override
    public void run() {
        while (true) {
            try {
                synchronized (_pendingRequest) {
                    Iterator changes = _pendingRequest.iterator();
                    while (changes.hasNext()) {
                        ChangeRequest change = (ChangeRequest) changes.next();
                        if (!processPendingRequest(change))
                            break;
                        changes.remove();
                    }
                }

                // wait events from selected channels
                _selector.select();

                Iterator selectedKeys = _selector.selectedKeys().iterator();
                while (selectedKeys.hasNext()) {
                    SelectionKey key = (SelectionKey) selectedKeys.next();
                    selectedKeys.remove();

                    if (!key.isValid()) {
                        continue;
                    }

                    processSelect(key);
                }
            }
            catch (ClosedSelectorException e) {
                break;
            }
            catch (Exception e) {
                logger.warning(Util.getErrorMessage(e));
            }
        }
        logger.fine(this.getClass().getName() + " Closed.");
    }

    protected void createWriteBuffer(SocketChannel socketChannel) {
        List queue = new ArrayList();
        Object put;
        put = _pendingData.putIfAbsent(socketChannel, queue);
        if (put != null) {
            logger.severe("Dup write buffer creation: " + socketChannel);
        }
    }

    protected void cleanUp(SocketChannel socketChannel) {
        try {
            socketChannel.close();
        } catch (IOException e) {
            logger.info(Util.getErrorMessage(e));
        }
        SelectionKey key = socketChannel.keyFor(_selector);
        if (key != null) {
            key.cancel();
        }

        if (_pendingData.containsKey(socketChannel)) {
            _pendingData.remove(socketChannel);
        }
    }

    @Override
    public void send(ChangeRequest request, byte[] data) {
        switch (request.type) {
            case ChangeRequest.CHANGE_SOCKET_OP:
                List queue = (List) _pendingData.get(request.socket);
                if (queue != null) {
                    synchronized (queue) {
                        // in general case, the write queue is always existed, unless, the socket has been shutdown
                        queue.add(ByteBuffer.wrap(data));
                    }
                }
                else {
                    logger.warning(Util.getErrorMessage(new Throwable("Socket is closed! dropping this request")));
                }
                break;
        }

        synchronized (_pendingRequest) {
            _pendingRequest.add(request);
        }

        _selector.wakeup();
    }

    @Override
    public void send(ChangeRequest request) {
        send(request, null);
    }

    public void close() {
        for (PipeWorker p : _pipes.values()) {
            p.forceClose();
        }
        _pipes.clear();
        try {
            _selector.close();
        } catch (IOException e) {
            logger.warning(Util.getErrorMessage(e));
        }
    }
}


================================================
FILE: src/main/java/com/stfl/network/proxy/AutoProxy.java
================================================
package com.stfl.network.proxy;

import com.stfl.misc.Reflection;

import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 * Decides proxy type automatically (as soon as socket established).
 * Proxy class for Socks5 and Http
 */
public class AutoProxy implements IProxy {
    private Logger logger = Logger.getLogger(AutoProxy.class.getName());
    private IProxy _proxy;
    private volatile boolean isInitialized;

    public AutoProxy() {
        isInitialized = false;
    }

    @Override
    public boolean isReady() {
        return (isInitialized && _proxy.isReady());
    }

    @Override
    public TYPE getType() {
        return TYPE.AUTO;
    }

    @Override
    public byte[] getResponse(byte[] data) {
        if (!isInitialized) {
            init(data);
        }
        return _proxy.getResponse(data);
    }

    @Override
    public List<byte[]> getRemoteResponse(byte[] data) {
        if (!isInitialized) {
            init(data);
        }
        return _proxy.getRemoteResponse(data);
    }

    @Override
    public boolean isMine(byte[] data) {
        if (!isInitialized) {
            init(data);
        }
        return _proxy.isMine(data);
    }

    private void init(byte[] data) {
        Object obj;
        IProxy proxy;
        for (Map.Entry<IProxy.TYPE, String> entry : ProxyFactory.proxies.entrySet()) {
            if (entry.getKey() == this.getType()) continue;

            obj = Reflection.get(entry.getValue());
            proxy = (IProxy)obj;
            if (proxy.isMine(data)) {
                logger.fine("ProxyType (Auto): " + proxy.getType());
                _proxy = proxy;
                isInitialized = true;
                return;
            }
        }
        logger.severe("Unable to determine proxy type!");
    }
}


================================================
FILE: src/main/java/com/stfl/network/proxy/HttpProxy.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.proxy;

import com.stfl.Constant;
import com.stfl.misc.Util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Provide local HTTP proxy statue and required response
 */
public class HttpProxy implements IProxy {
    private static final String[] HTTP_METHODS =
            new String[] {"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"};

    private Logger logger = Logger.getLogger(HttpProxy.class.getName());
    private boolean _isReady;
    private boolean _isHttpConnect;
    private Map<String, String> methodCache;

    public HttpProxy() {
        _isReady = false;
        _isHttpConnect = false;
    }

    public TYPE getType() {
        return TYPE.HTTP;
    }

    public boolean isReady() {
        return _isReady;
    }

    public byte[] getResponse(byte[] data) {
        if (methodCache == null) {
            methodCache = getHttpMethod(data);
        }
        setHttpMethod(methodCache);

        if (_isHttpConnect)
            return String.format("HTTP/1.0 200\r\nProxy-agent: %s/%s\r\n\r\n",
                    Constant.PROG_NAME, Constant.VERSION).getBytes();

        return null;
    }

    public List<byte[]> getRemoteResponse(byte[] data) {
        List<byte[]> respData = new ArrayList<>(2);
        String host;
        int port = 80; // HTTP port
        if (methodCache == null) {
            methodCache = getHttpMethod(data);
        }
        String[] hostInfo = methodCache.get("host").split(":");

        // get hostname and port
        host = hostInfo[0];
        if (hostInfo.length > 1) {
            port = Integer.parseInt(hostInfo[1]);
        }

        byte[] ssHeader = Util.composeSSHeader(host, port);
        respData.add(ssHeader);
        if (!_isHttpConnect) {
            byte[] httpHeader = reconstructHttpHeader(methodCache, data);
            respData.add(httpHeader);
        }

        _isReady = true;
        return respData;
    }

    @Override
    public boolean isMine(byte[] data) {
        if (methodCache == null) {
            methodCache = getHttpMethod(data);
        }
        String method = methodCache.get("method");

        if (method != null) {
            for (String s : HTTP_METHODS) {
                if (s.equals(method)) {
                    return true;
                }
            }
        }

        return false;
    }

    private Map<String, String> getHttpMethod(byte[] data) {
        String httpRequest = Util.bytesToString(data, 0, data.length);
        String[] httpHeaders = httpRequest.split("\\r?\\n");
        boolean isHostFound = true;
        //Pattern pattern = Pattern.compile("^([a-zA-Z]*) [hHtTpP]{0,4}[:\\/]{0,3}(\\S[^/ ]*)");
        Pattern pattern = Pattern.compile("^([a-zA-Z]*) [htps]{0,4}[:/]{0,3}(\\S[^/]*)(\\S*) (\\S*)");
        Map<String, String> header = new HashMap<>();
        if (httpHeaders.length > 0) {
            logger.fine("HTTP Header: " + httpHeaders[0]);
            Matcher matcher = pattern.matcher(httpHeaders[0]);
            if (matcher.find()) {
                header.put("method", matcher.group(1));
                if (matcher.group(2).startsWith("/")) {
                    header.put("url", "/");
                    isHostFound = false;
                }
                else {
                    header.put("host", matcher.group(2));
                    header.put("url", matcher.group(3));
                }
                header.put("version", matcher.group(4));
            }
        }

        if (!isHostFound) {
            for (String line : httpHeaders) {
                if (line.toLowerCase().contains("host")) {
                    String info = line.split(":")[1].trim();
                    header.put("host", info);
                    break;
                }
            }
        }
        return header;
    }

    private byte[] reconstructHttpHeader(Map<String, String> method, byte[] data) {
        String httpRequest = Util.bytesToString(data, 0, data.length);
        String[] httpHeaders = httpRequest.split("\\r?\\n");
        StringBuilder sb = new StringBuilder();
        boolean isFirstLine = true;

        //logger.info("original HttpHeader:" + httpRequest);
        for (String line : httpHeaders) {
            if (isFirstLine && _isHttpConnect) {
                sb.append(method.get("method"));
                sb.append(" ");
                sb.append(method.get("host"));
                sb.append(" ");
                sb.append(method.get("version"));
                sb.append("\r\n");
                sb.append("User-Agent: test/0.1\r\n");
                break;
            }
            else if (isFirstLine) {
                sb.append(method.get("method"));
                sb.append(" ");
                sb.append(method.get("url"));
                sb.append(" ");
                sb.append(method.get("version"));
                isFirstLine = false;
            }
            else if (line.toLowerCase().contains("cache-control")) {
                sb.append("Pragma: no-cache\r\n");
                sb.append("Cache-Control: no-cache");
            }
            else if (line.toLowerCase().contains("proxy-connection")) {
                //Proxy-Connection
                String[] fields = line.split(":");
                sb.append("Connection: ");
                sb.append(fields[1].trim());
            }
            else if (line.toLowerCase().contains("if-none-match")) {
                continue;
            }
            else if (line.toLowerCase().contains("if-modified-since")) {
                continue;
            }
            else {
                sb.append(line);
            }
            sb.append("\r\n");
        }

        sb.append("\r\n");
        //logger.info("reconstructHttpHeader:" + sb.toString());
        return sb.toString().getBytes();
    }

    private void setHttpMethod(Map<String, String> header) {
        String method = header.get("method");

        if (method != null) {
            if (method.toUpperCase().equals("CONNECT")) {
                _isHttpConnect = true;
            }
            else {
                _isHttpConnect = false;
            }
        }
    }

}


================================================
FILE: src/main/java/com/stfl/network/proxy/IProxy.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.proxy;

import java.util.List;

public interface IProxy {
    enum TYPE {SOCKS5, HTTP, AUTO}

    boolean isReady();
    TYPE getType();
    byte[] getResponse(byte[] data);
    List<byte[]> getRemoteResponse(byte[] data);
    boolean isMine(byte[] data);
}


================================================
FILE: src/main/java/com/stfl/network/proxy/ProxyFactory.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.proxy;

import com.stfl.misc.Reflection;
import java.util.*;
import java.util.logging.Logger;

/**
 * Proxy factory
 */
public class ProxyFactory {
    public static final Map<IProxy.TYPE, String> proxies = new HashMap<IProxy.TYPE, String>() {{
        put(IProxy.TYPE.HTTP, HttpProxy.class.getName());
        put(IProxy.TYPE.SOCKS5, Socks5Proxy.class.getName());
        put(IProxy.TYPE.AUTO, AutoProxy.class.getName());
    }};
    private static Logger logger = Logger.getLogger(ProxyFactory.class.getName());

    public static boolean isProxyTypeExisted(String name) {
        IProxy.TYPE type = IProxy.TYPE.valueOf(name);
        return (proxies.get(type) != null);
    }

    public static IProxy get(IProxy.TYPE type) {
        try {
            Object obj = Reflection.get(proxies.get(type));
            return (IProxy)obj;

        } catch (Exception e) {
            logger.info(com.stfl.misc.Util.getErrorMessage(e));
        }

        return null;
    }

    public static List<IProxy.TYPE> getSupportedProxyTypes() {
        List sortedKeys = new ArrayList<>(proxies.keySet());
        Collections.sort(sortedKeys);
        return sortedKeys;
    }
}


================================================
FILE: src/main/java/com/stfl/network/proxy/Socks5Proxy.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.network.proxy;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * Provide local socks5 statue and required response
 */
public class Socks5Proxy implements IProxy {
    public final static int ATYP_IP_V4 = 0x1;
    public final static int ATYP_DOMAIN_NAME = 0x3;
    public final static int ATYP_IP_V6 = 0x4;

    private Logger logger = Logger.getLogger(Socks5Proxy.class.getName());
    private enum STAGE {SOCK5_HELLO, SOCKS_ACK, SOCKS_READY}
    private STAGE _stage;

    public Socks5Proxy() {
        _stage = STAGE.SOCK5_HELLO;
    }

    public TYPE getType() {
        return TYPE.SOCKS5;
    }

    public boolean isReady() {
        return (_stage == STAGE.SOCKS_READY);
    }

    public byte[] getResponse(byte[] data) {
        byte[] respData = null;

        switch (_stage) {
            case SOCK5_HELLO:
                if (isMine(data)) {
                    respData = new byte[] {5, 0};
                }
                else {
                    respData = new byte[] {0, 91};
                }
                _stage = STAGE.SOCKS_ACK;
                break;
            case SOCKS_ACK:
                respData = new byte[] {5, 0, 0, 1, 0, 0, 0, 0, 0, 0};
                _stage = STAGE.SOCKS_READY;
                break;
            default:
                // TODO: exception
                break;

        }

        return respData;
    }

    public List<byte[]> getRemoteResponse(byte[] data) {
        List<byte[]> respData = null;
        int dataLength = data.length;

        /*
        There are two stage of establish Sock5:
            1. HELLO (3 bytes)
            2. ACK (3 bytes + dst info)
        as Client sending ACK, it might contain dst info.
        In this case, server needs to send back ACK response to client and start the remote socket right away,
        otherwise, client will wait until timeout.
         */
        if (_stage == STAGE.SOCKS_READY) {
            respData = new ArrayList<>(1);
            // remove socks5 header (partial)
            if (dataLength > 3) {
                dataLength -= 3;
                byte[] temp = new byte[dataLength];
                System.arraycopy(data, 3, temp, 0, dataLength);
                respData.add(temp);
            }
        }

        return respData;
    }

    @Override
    public boolean isMine(byte[] data) {
        if (data[0] == 0x5) {
            return true;
        }

        return false;
    }
}


================================================
FILE: src/main/java/com/stfl/ss/AesCrypt.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.modes.OFBBlockCipher;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.util.HashMap;
import java.util.Map;

/**
 * AES Crypt implementation
 */
public class AesCrypt extends CryptBase {

    public final static String CIPHER_AES_128_CFB = "aes-128-cfb";
    public final static String CIPHER_AES_192_CFB = "aes-192-cfb";
    public final static String CIPHER_AES_256_CFB = "aes-256-cfb";
    public final static String CIPHER_AES_128_OFB = "aes-128-ofb";
    public final static String CIPHER_AES_192_OFB = "aes-192-ofb";
    public final static String CIPHER_AES_256_OFB = "aes-256-ofb";

    public static Map<String, String> getCiphers() {
        Map<String, String> ciphers = new HashMap<>();
        ciphers.put(CIPHER_AES_128_CFB, AesCrypt.class.getName());
        ciphers.put(CIPHER_AES_192_CFB, AesCrypt.class.getName());
        ciphers.put(CIPHER_AES_256_CFB, AesCrypt.class.getName());
        ciphers.put(CIPHER_AES_128_OFB, AesCrypt.class.getName());
        ciphers.put(CIPHER_AES_192_OFB, AesCrypt.class.getName());
        ciphers.put(CIPHER_AES_256_OFB, AesCrypt.class.getName());

        return ciphers;
    }

    public AesCrypt(String name, String password) {
        super(name, password);
    }

    @Override
    public int getKeyLength() {
        if(_name.equals(CIPHER_AES_128_CFB) || _name.equals(CIPHER_AES_128_OFB)) {
            return 16;
        }
        else if (_name.equals(CIPHER_AES_192_CFB) || _name.equals(CIPHER_AES_192_OFB)) {
            return 24;
        }
        else if (_name.equals(CIPHER_AES_256_CFB) || _name.equals(CIPHER_AES_256_OFB)) {
            return 32;
        }

        return 0;
    }

    @Override
    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
        AESFastEngine engine = new AESFastEngine();
        StreamBlockCipher cipher;

        if (_name.equals(CIPHER_AES_128_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_AES_192_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_AES_256_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_AES_128_OFB)) {
            cipher = new OFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_AES_192_OFB)) {
            cipher = new OFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_AES_256_OFB)) {
            cipher = new OFBBlockCipher(engine, getIVLength() * 8);
        }
        else {
            throw new InvalidAlgorithmParameterException(_name);
        }

        return cipher;
    }

    @Override
    public int getIVLength() {
        return 16;
    }

    @Override
    protected SecretKey getKey() {
        return new SecretKeySpec(_ssKey.getEncoded(), "AES");
    }

    @Override
    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }

    @Override
    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }
}


================================================
FILE: src/main/java/com/stfl/ss/BlowFishCrypt.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.engines.BlowfishEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.util.HashMap;
import java.util.Map;

/**
 * Blow fish cipher implementation
 */
public class BlowFishCrypt extends CryptBase {

    public final static String CIPHER_BLOWFISH_CFB = "bf-cfb";

    public static Map<String, String> getCiphers() {
        Map<String, String> ciphers = new HashMap<>();
        ciphers.put(CIPHER_BLOWFISH_CFB, BlowFishCrypt.class.getName());

        return ciphers;
    }

    public BlowFishCrypt(String name, String password) {
        super(name, password);
    }

    @Override
    public int getKeyLength() {
        return 16;
    }

    @Override
    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
        BlowfishEngine engine = new BlowfishEngine();
        StreamBlockCipher cipher;

        if (_name.equals(CIPHER_BLOWFISH_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else {
            throw new InvalidAlgorithmParameterException(_name);
        }

        return cipher;
    }

    @Override
    public int getIVLength() {
        return 8;
    }

    @Override
    protected SecretKey getKey() {
        return new SecretKeySpec(_ssKey.getEncoded(), "AES");
    }

    @Override
    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }

    @Override
    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }
}


================================================
FILE: src/main/java/com/stfl/ss/CamelliaCrypt.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.engines.CamelliaEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.util.HashMap;
import java.util.Map;

/**
 * Camellia cipher implementation
 */
public class CamelliaCrypt extends CryptBase {

    public final static String CIPHER_CAMELLIA_128_CFB = "camellia-128-cfb";
    public final static String CIPHER_CAMELLIA_192_CFB = "camellia-192-cfb";
    public final static String CIPHER_CAMELLIA_256_CFB = "camellia-256-cfb";

    public static Map<String, String> getCiphers() {
        Map<String, String> ciphers = new HashMap<>();
        ciphers.put(CIPHER_CAMELLIA_128_CFB, CamelliaCrypt.class.getName());
        ciphers.put(CIPHER_CAMELLIA_192_CFB, CamelliaCrypt.class.getName());
        ciphers.put(CIPHER_CAMELLIA_256_CFB, CamelliaCrypt.class.getName());

        return ciphers;
    }

    public CamelliaCrypt(String name, String password) {
        super(name, password);
    }

    @Override
    public int getKeyLength() {
        if(_name.equals(CIPHER_CAMELLIA_128_CFB)) {
            return 16;
        }
        else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {
            return 24;
        }
        else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {
            return 32;
        }

        return 0;
    }

    @Override
    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
        CamelliaEngine engine = new CamelliaEngine();
        StreamBlockCipher cipher;

        if (_name.equals(CIPHER_CAMELLIA_128_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else {
            throw new InvalidAlgorithmParameterException(_name);
        }

        return cipher;
    }

    @Override
    public int getIVLength() {
        return 16;
    }

    @Override
    protected SecretKey getKey() {
        return new SecretKeySpec(_ssKey.getEncoded(), "AES");
    }

    @Override
    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }

    @Override
    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }
}


================================================
FILE: src/main/java/com/stfl/ss/CryptBase.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import com.stfl.misc.Util;
import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

import javax.crypto.SecretKey;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

/**
 * Crypt base class implementation
 */
public abstract class CryptBase implements ICrypt {

    protected abstract StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException;
    protected abstract SecretKey getKey();
    protected abstract void _encrypt(byte[] data, ByteArrayOutputStream stream);
    protected abstract void _decrypt(byte[] data, ByteArrayOutputStream stream);

    protected final String _name;
    protected final SecretKey _key;
    protected final ShadowSocksKey _ssKey;
    protected final int _ivLength;
    protected final int _keyLength;
    protected boolean _encryptIVSet;
    protected boolean _decryptIVSet;
    protected byte[] _encryptIV;
    protected byte[] _decryptIV;
    protected final Lock encLock = new ReentrantLock();
    protected final Lock decLock = new ReentrantLock();
    protected StreamBlockCipher encCipher;
    protected StreamBlockCipher decCipher;
    private Logger logger = Logger.getLogger(CryptBase.class.getName());

    public CryptBase(String name, String password) {
        _name = name.toLowerCase();
        _ivLength = getIVLength();
        _keyLength = getKeyLength();
        _ssKey = new ShadowSocksKey(password, _keyLength);
        _key = getKey();
    }

    protected void setIV(byte[] iv, boolean isEncrypt)
    {
        if (_ivLength == 0) {
            return;
        }

        if (isEncrypt)
        {
            _encryptIV = new byte[_ivLength];
            System.arraycopy(iv, 0, _encryptIV, 0, _ivLength);
            try {
                encCipher = getCipher(isEncrypt);
                ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _encryptIV);
                encCipher.init(isEncrypt, parameterIV);
            } catch (InvalidAlgorithmParameterException e) {
                logger.info(e.toString());
            }
        }
        else
        {
            _decryptIV = new byte[_ivLength];
            System.arraycopy(iv, 0, _decryptIV, 0, _ivLength);
            try {
                decCipher = getCipher(isEncrypt);
                ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _decryptIV);
                decCipher.init(isEncrypt, parameterIV);
            } catch (InvalidAlgorithmParameterException e) {
                logger.info(e.toString());
            }
        }
    }

    @Override
    public void encrypt(byte[] data, ByteArrayOutputStream stream) {
        synchronized (encLock) {
            stream.reset();
            if (!_encryptIVSet) {
                _encryptIVSet = true;
                byte[] iv = Util.randomBytes(_ivLength);
                setIV(iv, true);
                try {
                    stream.write(iv);
                } catch (IOException e) {
                    logger.info(e.toString());
                }

            }

            _encrypt(data, stream);
        }
    }

    @Override
    public void encrypt(byte[] data, int length, ByteArrayOutputStream stream) {
        byte[] d = new byte[length];
        System.arraycopy(data, 0, d, 0, length);
        encrypt(d, stream);
    }

    @Override
    public void decrypt(byte[] data, ByteArrayOutputStream stream) {
        byte[] temp;

        synchronized (decLock) {
            stream.reset();
            if (!_decryptIVSet) {
                _decryptIVSet = true;
                setIV(data, false);
                temp = new byte[data.length - _ivLength];
                System.arraycopy(data, _ivLength, temp, 0, data.length - _ivLength);
            } else {
                temp = data;
            }

            _decrypt(temp, stream);
        }
    }

    @Override
    public void decrypt(byte[] data, int length, ByteArrayOutputStream stream) {
        byte[] d = new byte[length];
        System.arraycopy(data, 0, d, 0, length);
        decrypt(d, stream);
    }
}


================================================
FILE: src/main/java/com/stfl/ss/CryptFactory.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import com.stfl.misc.Reflection;
import java.util.*;
import java.util.logging.Logger;

/**
 * Crypt factory
 */
public class CryptFactory {
    private static final Map<String, String> crypts = new HashMap<String, String>() {{
        putAll(AesCrypt.getCiphers());
        putAll(CamelliaCrypt.getCiphers());
        putAll(BlowFishCrypt.getCiphers());
        putAll(SeedCrypt.getCiphers());
        // TODO: other crypts
    }};
    private static Logger logger = Logger.getLogger(CryptFactory.class.getName());

    public static boolean isCipherExisted(String name) {
        return (crypts.get(name) != null);
    }

    public static ICrypt get(String name, String password) {
        try {
            Object obj = Reflection.get(crypts.get(name), String.class, name, String.class, password);
            return (ICrypt)obj;

        } catch (Exception e) {
            logger.info(com.stfl.misc.Util.getErrorMessage(e));
        }

        return null;
    }

    public static List<String> getSupportedCiphers() {
        List sortedKeys = new ArrayList<>(crypts.keySet());
        Collections.sort(sortedKeys);
        return sortedKeys;
    }
}


================================================
FILE: src/main/java/com/stfl/ss/ICrypt.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import java.io.ByteArrayOutputStream;
import java.util.Map;

/**
 * Interface of crypt
 */
public interface ICrypt {
    void encrypt(byte[] data, ByteArrayOutputStream stream);
    void encrypt(byte[] data, int length, ByteArrayOutputStream stream);
    void decrypt(byte[] data, ByteArrayOutputStream stream);
    void decrypt(byte[] data, int length, ByteArrayOutputStream stream);
    int getIVLength();
    int getKeyLength();
}


================================================
FILE: src/main/java/com/stfl/ss/SeedCrypt.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.engines.SEEDEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.util.HashMap;
import java.util.Map;

/**
 * Seed cipher implementation
 */
public class SeedCrypt extends CryptBase {

    public final static String CIPHER_SEED_CFB = "seed-cfb";

    public static Map<String, String> getCiphers() {
        Map<String, String> ciphers = new HashMap<>();
        ciphers.put(CIPHER_SEED_CFB, SeedCrypt.class.getName());

        return ciphers;
    }

    public SeedCrypt(String name, String password) {
        super(name, password);
    }

    @Override
    public int getKeyLength() {
        return 16;
    }

    @Override
    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
        SEEDEngine engine = new SEEDEngine();
        StreamBlockCipher cipher;

        if (_name.equals(CIPHER_SEED_CFB)) {
            cipher = new CFBBlockCipher(engine, getIVLength() * 8);
        }
        else {
            throw new InvalidAlgorithmParameterException(_name);
        }

        return cipher;
    }

    @Override
    public int getIVLength() {
        return 16;
    }

    @Override
    protected SecretKey getKey() {
        return new SecretKeySpec(_ssKey.getEncoded(), "AES");
    }

    @Override
    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }

    @Override
    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }
}


================================================
FILE: src/main/java/com/stfl/ss/ShadowSocksKey.java
================================================
/*
 * Copyright (c) 2015, Blake
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.stfl.ss;

import com.stfl.misc.Util;

import javax.crypto.SecretKey;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.logging.Logger;

/**
 * Shadowsocks key generator
 */
public class ShadowSocksKey implements SecretKey {

    private Logger logger = Logger.getLogger(ShadowSocksKey.class.getName());
    private final static int KEY_LENGTH = 32;
    private byte[] _key;
    private int _length;

    public ShadowSocksKey(String password) {
        _length = KEY_LENGTH;
        _key = init(password);
    }

    public ShadowSocksKey(String password, int length) {
        // TODO: Invalid key length
        _length = length;
        _key = init(password);
    }

    private byte[] init(String password) {
        MessageDigest md = null;
        byte[] keys = new byte[KEY_LENGTH];
        byte[] temp = null;
        byte[] hash = null;
        byte[] passwordBytes = null;
        int i = 0;

        try {
            md = MessageDigest.getInstance("MD5");
            passwordBytes = password.getBytes("UTF-8");
        }
        catch (UnsupportedEncodingException e) {
            logger.info("ShadowSocksKey: Unsupported string encoding");
        }
        catch (Exception e) {
            logger.info(Util.getErrorMessage(e));
            return null;
        }

        while (i < keys.length) {
            if (i == 0) {
                hash = md.digest(passwordBytes);
                temp = new byte[passwordBytes.length+hash.length];
            }
            else {
                System.arraycopy(hash, 0, temp, 0, hash.length);
                System.arraycopy(passwordBytes, 0, temp, hash.length, passwordBytes.length);
                hash = md.digest(temp);
            }
            System.arraycopy(hash, 0, keys, i, hash.length);
            i += hash.length;
        }

        if (_length != KEY_LENGTH) {
            byte[] keysl = new byte[_length];
            System.arraycopy(keys, 0, keysl, 0, _length);
            return keysl;
        }
        return keys;
    }

    @Override
    public String getAlgorithm() {
        return "shadowsocks";
    }

    @Override
    public String getFormat() {
        return "RAW";
    }

    @Override
    public byte[] getEncoded() {
        return _key;
    }
}


================================================
FILE: src/main/java/com/stfl/ui/LogLayoutController.java
================================================
package com.stfl.ui;

import com.stfl.misc.Log;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;

public class LogLayoutController {
    @FXML
    public TextArea txtLog;
    @FXML
    private Button btnClose;
    @FXML
    private Button btnClear;

    private Stage stage;

    @FXML
    private void initialize() {
        TextAreaLogHandler handler = new TextAreaLogHandler();
        handler.setTextArea(txtLog);
        Log.addHandler(handler);
    }

    @FXML
    private void handleClear() {
        txtLog.clear();
    }

    @FXML
    private void handleClose() {
        stage.hide();
    }

    public void setStage(Stage stage) {
        this.stage = stage;
    }
}


================================================
FILE: src/main/java/com/stfl/ui/MainLayoutController.java
================================================
package com.stfl.ui;

import com.stfl.Constant;
import com.stfl.MainGui;
import com.stfl.misc.Config;
import com.stfl.misc.UTF8Control;
import com.stfl.misc.Util;
import com.stfl.network.IServer;
import com.stfl.network.NioLocalServer;
import com.stfl.network.proxy.IProxy;
import com.stfl.network.proxy.ProxyFactory;
import com.stfl.ss.CryptFactory;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.util.ResourceBundle;
import java.util.logging.Logger;


public class MainLayoutController {
    @FXML
    private TextField txtServerIP;
    @FXML
    private TextField txtServerPort;
    @FXML
    private ComboBox cboCipher;
    @FXML
    private TextField txtPassword;
    @FXML
    private TextField txtLocalPort;
    @FXML
    private ComboBox cboProxyType;
    @FXML
    private Button btnStart;
    @FXML
    private Button btnStop;
    @FXML
    private Button btnLog;
    @FXML
    private Button btnClose;

    private Logger logger = Logger.getLogger(MainLayoutController.class.getName());
    private MainGui gui;
    private IServer server;
    private Stage logStage;
    private Config config;

    @FXML
    private void initialize() {
        // set cipher options
        ObservableList<String> ciphers = FXCollections.observableArrayList();
        ciphers.addAll(CryptFactory.getSupportedCiphers());
        cboCipher.setItems(ciphers);

        // set proxy options
        ObservableList<IProxy.TYPE> proxyTypes = FXCollections.observableArrayList();
        proxyTypes.addAll(ProxyFactory.getSupportedProxyTypes());
        cboProxyType.setItems(proxyTypes);

        // prepare configuration
        config = new Config();
        config.loadFromJson(Util.getFileContent(Constant.CONF_FILE));
        txtServerIP.setText(config.getRemoteIpAddress());
        txtServerPort.setText(String.valueOf(config.getRemotePort()));
        txtLocalPort.setText(String.valueOf(config.getLocalPort()));
        txtPassword.setText(config.getPassword());
        cboCipher.setValue(config.getMethod());
        cboProxyType.setValue(config.getProxyType());

        // prepare log window
        Stage stage = new Stage();
        try {
            FXMLLoader logLayoutLoader = new FXMLLoader(MainGui.class.getResource("/resources/ui/LogLayout.fxml"));
            logLayoutLoader.setResources(ResourceBundle.getBundle("resources.bundle.ui", Constant.LOCALE, new UTF8Control()));
            Pane logLayout = logLayoutLoader.load();
            Scene logScene = new Scene(logLayout);
            stage.setTitle("Log");
            stage.setScene(logScene);
            stage.setResizable(false);
            stage.getIcons().add(new Image(MainGui.class.getResource("/resources/image/icon.png").toString()));

            LogLayoutController controller = logLayoutLoader.getController();
            controller.setStage(stage);
            logStage = stage;
        } catch (IOException e) {
            logger.warning("Unable to load ICON: " + e.toString());
        }

        btnStop.setDisable(true);
    }

    @FXML
    private void handleStart() {
        boolean isValidated = false;
        do {
            if (!txtServerIP.getText().matches("[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}")) {
                showAlert(Constant.PROG_NAME, "Invalid IP address", Alert.AlertType.ERROR);
                break;
            }
            String ip = txtServerIP.getText();
            if (!txtServerPort.getText().matches("[0-9]+")) {
                showAlert(Constant.PROG_NAME, "Invalid Port", Alert.AlertType.ERROR);
                break;
            }
            int port = Integer.parseInt(txtServerPort.getText());

            String method = (String) cboCipher.getValue();
            if (txtPassword.getText().length() == 0) {
                showAlert(Constant.PROG_NAME, "Please specified password", Alert.AlertType.ERROR);
                break;
            }
            String password = txtPassword.getText();
            IProxy.TYPE type = (IProxy.TYPE) cboProxyType.getValue();
            if (!txtLocalPort.getText().matches("[0-9]+")) {
                showAlert(Constant.PROG_NAME, "Invalid Port", Alert.AlertType.ERROR);
                break;
            }
            int localPort = Integer.parseInt(txtLocalPort.getText());

            // create config
            config.setRemoteIpAddress(ip);
            config.setRemotePort(port);
            config.setLocalIpAddress("127.0.0.1");
            config.setLocalPort(localPort);
            config.setMethod(method);
            config.setPassword(password);
            config.setProxyType(type);
            Util.saveFile(Constant.CONF_FILE, config.saveToJson());

            isValidated = true;
        } while (false);

        if (!isValidated)
            return;

        // start start
        try {
            server = new NioLocalServer(config);
            Thread t = new Thread(server);
            t.setDaemon(true);
            t.start();
            String message = String.format("(Connected) Server %s:%d", config.getRemoteIpAddress(), config.getRemotePort());
            gui.setTooltip(message);
            gui.showNotification(message);
        } catch (IOException | InvalidAlgorithmParameterException e) {
            logger.warning("Unable to start server: " + e.toString());
        }
        btnStop.setDisable(false);
        btnStart.setDisable(true);
    }

    @FXML
    private void handleStop() {
        if (server != null) {
            server.close();
            String message = String.format("(Disconnected) Server %s:%d", config.getRemoteIpAddress(), config.getRemotePort());
            gui.showNotification(message);
            gui.setTooltip("Not Connected");
        }

        btnStop.setDisable(true);
        btnStart.setDisable(false);
    }

    @FXML
    private void handleLog() {
        logStage.show();
    }

    @FXML
    private void handleClose() {
        gui.hide();
    }

    public void setMainGui(MainGui gui) {
        this.gui = gui;
    }

    public void closeServer() {
        handleStop();
    }

    private boolean validationInput(String pattern, String text) {
        return false;
    }

    private void showAlert(String title, String message, Alert.AlertType type) {
        Alert a = new Alert(type);
        a.setTitle(title);
        a.setHeaderText(type.name());
        a.setResizable(false);
        a.setContentText(message);
        a.showAndWait();
    }
}


================================================
FILE: src/main/java/com/stfl/ui/TextAreaLogHandler.java
================================================
package com.stfl.ui;

import javafx.application.Platform;
import javafx.scene.control.TextArea;

import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;

public class TextAreaLogHandler extends StreamHandler {
    TextArea textArea = null;

    public void setTextArea(TextArea textArea) {
        this.textArea = textArea;
    }

    @Override
    public void publish(LogRecord record) {
        final LogRecord lg = record;
        super.publish(record);
        flush();

        if (textArea != null) {
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    // limited log size to 64k
                    if (textArea.getText().length() > 65535) {
                        textArea.clear();
                    }
                    textArea.appendText(getFormatter().format(lg));
                }
            });
        }
    }
}


================================================
FILE: src/main/resources/META-INF/MANIFEST.MF
================================================
Manifest-Version: 1.0
Class-Path: bcprov-jdk15on-1.52.jar json-simple-1.1.1.jar
Main-Class: com.stfl.Main



================================================
FILE: src/main/resources/resources/bundle/ui_en.properties
================================================
START=Start
STOP=Stop
LOG=Log
CLOSE=Close
CLOSE1=Close
CLEAR=Clear
SERVER_IP=Server IP:
SERVER_PORT=Server Port:
CIPHER=Cipher:
PASSWORD=Password:
LOCAL_PORT=Local Port:
PROXY_TYPE=Proxy Type:


================================================
FILE: src/main/resources/resources/bundle/ui_zh_CN.properties
================================================
START=启动
STOP=停止
LOG=日志
CLOSE=关闭
CLEAR=清除
SERVER_IP=服务器 IP:
SERVER_PORT=服务器端口:
CIPHER=加密方式:
PASSWORD=密码:
LOCAL_PORT=本地端口:
PROXY_TYPE=代理服务器类型:


================================================
FILE: src/main/resources/resources/bundle/ui_zh_TW.properties
================================================
START=啟動
STOP=停止
LOG=日誌
CLOSE=關閉
CLEAR=清除
SERVER_IP=伺服器 IP:
SERVER_PORT=伺服器 Port:
CIPHER=加密方式:
PASSWORD=密碼:
LOCAL_PORT=本地 Port:
PROXY_TYPE=代理伺服器類型:


================================================
FILE: src/main/resources/resources/ui/LogLayout.fxml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="388.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.stfl.ui.LogLayoutController">
   <children>
      <TextArea fx:id="txtLog" editable="false" prefHeight="369.0" prefWidth="600.0" />
      <HBox alignment="TOP_RIGHT" prefHeight="0.0" prefWidth="600.0">
         <children>
            <Button fx:id="btnClear" mnemonicParsing="false" onAction="#handleClear" text="%CLEAR" />
            <Button fx:id="btnClose" mnemonicParsing="false" onAction="#handleClose" text="%CLOSE" />
         </children>
      </HBox>
   </children>
</VBox>


================================================
FILE: src/main/resources/resources/ui/MainLayout.fxml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="270.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.stfl.ui.MainLayoutController">
   <children>
      <Label layoutX="15.0" layoutY="31.0" text="%SERVER_IP" />
      <Label layoutX="15.0" layoutY="61.0" text="%SERVER_PORT" />
      <Label layoutX="15.0" layoutY="90.0" text="%CIPHER" />
      <Label layoutX="15.0" layoutY="118.0" text="%PASSWORD" />
      <Label layoutX="15.0" layoutY="178.0" text="%LOCAL_PORT" />
      <Label layoutX="14.0" layoutY="148.0" text="%PROXY_TYPE" />
      <TextField fx:id="txtServerIP" layoutX="128.0" layoutY="27.0" prefHeight="23.0" prefWidth="150.0" />
      <TextField fx:id="txtServerPort" layoutX="128.0" layoutY="57.0" prefHeight="23.0" prefWidth="150.0" />
      <TextField fx:id="txtLocalPort" layoutX="128.0" layoutY="174.0" prefHeight="23.0" prefWidth="150.0" />
      <ComboBox fx:id="cboCipher" layoutX="128.0" layoutY="86.0" prefWidth="150.0" />
      <ComboBox fx:id="cboProxyType" layoutX="128.0" layoutY="144.0" prefWidth="150.0" />
      <Button fx:id="btnStart" layoutX="13.0" layoutY="220.0" mnemonicParsing="false" onAction="#handleStart" text="%START" />
      <Button fx:id="btnStop" layoutX="56.0" layoutY="220.0" mnemonicParsing="false" onAction="#handleStop" text="%STOP" />
      <Button fx:id="btnClose" layoutX="232.0" layoutY="220.0" mnemonicParsing="false" onAction="#handleClose" text="%CLOSE" />
      <PasswordField fx:id="txtPassword" layoutX="128.0" layoutY="114.0" prefHeight="23.0" prefWidth="150.0" />
      <Button fx:id="btnLog" layoutX="185.0" layoutY="220.0" mnemonicParsing="false" onAction="#handleLog" text="%LOG" />
   </children>
</Pane>
Download .txt
gitextract_fryewo1t/

├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── stfl/
        │           ├── Constant.java
        │           ├── Main.java
        │           ├── MainGui.java
        │           ├── misc/
        │           │   ├── Config.java
        │           │   ├── Log.java
        │           │   ├── Reflection.java
        │           │   ├── UTF8Control.java
        │           │   └── Util.java
        │           ├── network/
        │           │   ├── IServer.java
        │           │   ├── LocalServer.java
        │           │   ├── NioLocalServer.java
        │           │   ├── io/
        │           │   │   └── PipeSocket.java
        │           │   ├── nio/
        │           │   │   ├── ChangeRequest.java
        │           │   │   ├── ISocketHandler.java
        │           │   │   ├── PipeEvent.java
        │           │   │   ├── PipeWorker.java
        │           │   │   ├── RemoteSocketHandler.java
        │           │   │   └── SocketHandlerBase.java
        │           │   └── proxy/
        │           │       ├── AutoProxy.java
        │           │       ├── HttpProxy.java
        │           │       ├── IProxy.java
        │           │       ├── ProxyFactory.java
        │           │       └── Socks5Proxy.java
        │           ├── ss/
        │           │   ├── AesCrypt.java
        │           │   ├── BlowFishCrypt.java
        │           │   ├── CamelliaCrypt.java
        │           │   ├── CryptBase.java
        │           │   ├── CryptFactory.java
        │           │   ├── ICrypt.java
        │           │   ├── SeedCrypt.java
        │           │   └── ShadowSocksKey.java
        │           └── ui/
        │               ├── LogLayoutController.java
        │               ├── MainLayoutController.java
        │               └── TextAreaLogHandler.java
        └── resources/
            ├── META-INF/
            │   └── MANIFEST.MF
            └── resources/
                ├── bundle/
                │   ├── ui_en.properties
                │   ├── ui_zh_CN.properties
                │   └── ui_zh_TW.properties
                └── ui/
                    ├── LogLayout.fxml
                    └── MainLayout.fxml
Download .txt
SYMBOL INDEX (241 symbols across 34 files)

FILE: src/main/java/com/stfl/Constant.java
  class Constant (line 5) | public class Constant {

FILE: src/main/java/com/stfl/Main.java
  class Main (line 16) | public class Main {
    method main (line 19) | public static void main(String[] args) {
    method startCommandLine (line 28) | private static void startCommandLine(String[] args) {
    method parseArgument (line 50) | private static Config parseArgument(String[] args) {
    method printUsage (line 109) | private static void printUsage() {

FILE: src/main/java/com/stfl/MainGui.java
  class MainGui (line 21) | public class MainGui extends Application {
    method start (line 28) | @Override
    method addToTray (line 59) | private void addToTray() {
    method show (line 122) | public void show() {
    method hide (line 126) | public void hide() {
    method setTooltip (line 130) | public void setTooltip(String message) {
    method showNotification (line 136) | public void showNotification(String message) {

FILE: src/main/java/com/stfl/misc/Config.java
  class Config (line 42) | public class Config {
    method Config (line 52) | public Config() {
    method Config (line 56) | public Config(String ipAddr, int port, String localIpAddr, int localPo...
    method Config (line 67) | public Config(String ipAddr, int port, String localIpAddr, int localPo...
    method setRemoteIpAddress (line 72) | public void setRemoteIpAddress(String value) {
    method getRemoteIpAddress (line 76) | public String getRemoteIpAddress() {
    method setLocalIpAddress (line 80) | public void setLocalIpAddress(String value) {
    method getLocalIpAddress (line 84) | public String getLocalIpAddress() {
    method setRemotePort (line 88) | public void setRemotePort(int value) {
    method getRemotePort (line 92) | public int getRemotePort() {
    method setLocalPort (line 96) | public void setLocalPort(int value) {
    method getLocalPort (line 100) | public int getLocalPort() {
    method setProxyType (line 104) | public void setProxyType(String value) {
    method setProxyType (line 114) | public void setProxyType(IProxy.TYPE value) {
    method getProxyType (line 117) | public IProxy.TYPE getProxyType() {
    method setMethod (line 121) | public void setMethod(String value) {
    method getMethod (line 125) | public String getMethod() {
    method setPassword (line 129) | public void setPassword(String value) {
    method getPassword (line 133) | public String getPassword() {
    method setLogLevel (line 137) | public void setLogLevel(String value) {
    method getLogLevel (line 142) | public String getLogLevel() {
    method loadFromJson (line 146) | public void loadFromJson(String jsonStr) {
    method saveToJson (line 163) | public String saveToJson() {

FILE: src/main/java/com/stfl/misc/Log.java
  class Log (line 41) | public class Log {
    method init (line 44) | public static void init() {
    method init (line 48) | public static void init(Level level) {
    method init (line 77) | public static void init(String level) {
    method addHandler (line 82) | public static void addHandler(Handler handler) {
    method getRootLogger (line 93) | private static Logger getRootLogger() {

FILE: src/main/java/com/stfl/misc/Reflection.java
  class Reflection (line 5) | public class Reflection {
    method get (line 6) | public static Object get(String className, Object... args) {

FILE: src/main/java/com/stfl/misc/UTF8Control.java
  class UTF8Control (line 12) | public class UTF8Control extends ResourceBundle.Control {
    method newBundle (line 13) | public ResourceBundle newBundle

FILE: src/main/java/com/stfl/misc/Util.java
  class Util (line 46) | public class Util {
    method dumpBytes (line 47) | public static String dumpBytes(byte[] a) {
    method randomBytes (line 54) | public static byte[] randomBytes(int size) {
    method getErrorMessage (line 60) | public static String getErrorMessage(Throwable e) {
    method prettyPrintJson (line 67) | public static String prettyPrintJson(JSONObject jObj) {
    method getRequestedHostInfo (line 124) | public static String getRequestedHostInfo(byte[] data) {
    method bytesToString (line 164) | public static String bytesToString(byte[] data, int start, int length) {
    method composeSSHeader (line 176) | public static byte[] composeSSHeader(String host, int port) {
    method saveFile (line 189) | public static boolean saveFile(String fn, String content) {
    method getFileContent (line 201) | public static String getFileContent(String fn) {
    method byteToUnsignedByte (line 213) | private static short byteToUnsignedByte(byte b) {
    method getPort (line 217) | private static int getPort(byte b, byte b1) {

FILE: src/main/java/com/stfl/network/IServer.java
  type IServer (line 3) | public interface IServer extends Runnable {
    method close (line 4) | void close();

FILE: src/main/java/com/stfl/network/LocalServer.java
  class LocalServer (line 53) | public class LocalServer implements IServer {
    method LocalServer (line 60) | public LocalServer(Config config) throws IOException, InvalidAlgorithm...
    method run (line 74) | @Override
    method close (line 88) | public void close() {

FILE: src/main/java/com/stfl/network/NioLocalServer.java
  class NioLocalServer (line 56) | public class NioLocalServer extends SocketHandlerBase {
    method NioLocalServer (line 63) | public NioLocalServer(Config config) throws IOException, InvalidAlgori...
    method initSelector (line 77) | @Override
    method processPendingRequest (line 89) | @Override
    method processSelect (line 107) | @Override
    method accept (line 124) | private void accept(SelectionKey key) throws IOException {
    method read (line 140) | private void read(SelectionKey key) throws IOException {
    method write (line 169) | private void write(SelectionKey key) throws IOException {
    method cleanUp (line 196) | @Override
    method close (line 213) | @Override

FILE: src/main/java/com/stfl/network/io/PipeSocket.java
  class PipeSocket (line 52) | public class PipeSocket implements Runnable {
    method PipeSocket (line 66) | public PipeSocket(Executor executor, Socket socket, Config config) thr...
    method run (line 77) | @Override
    method initRemote (line 92) | private Socket initRemote(Config config) throws IOException {
    method getLocalWorker (line 96) | private Runnable getLocalWorker() {
    method getRemoteWorker (line 166) | private Runnable getRemoteWorker() {
    method close (line 209) | public void close() {
    method sendRemote (line 233) | private boolean sendRemote(byte[] data, int length) {
    method _sendRemote (line 240) | private boolean _sendRemote(byte[] data, int length) {
    method sendLocal (line 257) | private boolean sendLocal(byte[] data, int length) {
    method _sendLocal (line 264) | private boolean _sendLocal(byte[] data, int length) {

FILE: src/main/java/com/stfl/network/nio/ChangeRequest.java
  class ChangeRequest (line 39) | public class ChangeRequest {
    method ChangeRequest (line 48) | public ChangeRequest(SocketChannel socket, int type, int op) {
    method ChangeRequest (line 54) | public ChangeRequest(SocketChannel socket, int type) {

FILE: src/main/java/com/stfl/network/nio/ISocketHandler.java
  type ISocketHandler (line 37) | public interface ISocketHandler {
    method send (line 38) | void send(ChangeRequest request, byte[] data);
    method send (line 39) | void send(ChangeRequest request);

FILE: src/main/java/com/stfl/network/nio/PipeEvent.java
  class PipeEvent (line 37) | public class PipeEvent {
    method PipeEvent (line 41) | public PipeEvent() {}
    method PipeEvent (line 43) | public PipeEvent(byte[] data, boolean isEncrypted) {

FILE: src/main/java/com/stfl/network/nio/PipeWorker.java
  class PipeWorker (line 52) | public class PipeWorker implements Runnable {
    method PipeWorker (line 65) | public PipeWorker(ISocketHandler localHandler, SocketChannel localChan...
    method close (line 78) | public void close() {
    method forceClose (line 83) | public void forceClose() {
    method processData (line 102) | public void processData(byte[] data, int count, boolean isEncrypted) {
    method run (line 113) | @Override

FILE: src/main/java/com/stfl/network/nio/RemoteSocketHandler.java
  class RemoteSocketHandler (line 52) | public class RemoteSocketHandler extends SocketHandlerBase {
    method RemoteSocketHandler (line 55) | public RemoteSocketHandler(Config config) throws IOException, InvalidA...
    method initSelector (line 59) | @Override
    method processPendingRequest (line 64) | @Override
    method processSelect (line 97) | @Override
    method createPipe (line 112) | public PipeWorker createPipe(ISocketHandler localHandler, SocketChanne...
    method read (line 135) | private void read(SelectionKey key) throws IOException {
    method write (line 167) | private void write(SelectionKey key) throws IOException {
    method finishConnection (line 194) | private void finishConnection(SelectionKey key) throws IOException {
    method cleanUp (line 208) | @Override

FILE: src/main/java/com/stfl/network/nio/SocketHandlerBase.java
  class SocketHandlerBase (line 55) | public abstract class SocketHandlerBase implements IServer, ISocketHandl...
    method initSelector (line 64) | protected abstract Selector initSelector() throws IOException;
    method processPendingRequest (line 65) | protected abstract boolean processPendingRequest(ChangeRequest request);
    method processSelect (line 66) | protected abstract void processSelect(SelectionKey key);
    method SocketHandlerBase (line 69) | public SocketHandlerBase(Config config) throws IOException, InvalidAlg...
    method run (line 77) | @Override
    method createWriteBuffer (line 116) | protected void createWriteBuffer(SocketChannel socketChannel) {
    method cleanUp (line 125) | protected void cleanUp(SocketChannel socketChannel) {
    method send (line 141) | @Override
    method send (line 165) | @Override
    method close (line 170) | public void close() {

FILE: src/main/java/com/stfl/network/proxy/AutoProxy.java
  class AutoProxy (line 13) | public class AutoProxy implements IProxy {
    method AutoProxy (line 18) | public AutoProxy() {
    method isReady (line 22) | @Override
    method getType (line 27) | @Override
    method getResponse (line 32) | @Override
    method getRemoteResponse (line 40) | @Override
    method isMine (line 48) | @Override
    method init (line 56) | private void init(byte[] data) {

FILE: src/main/java/com/stfl/network/proxy/HttpProxy.java
  class HttpProxy (line 48) | public class HttpProxy implements IProxy {
    method HttpProxy (line 57) | public HttpProxy() {
    method getType (line 62) | public TYPE getType() {
    method isReady (line 66) | public boolean isReady() {
    method getResponse (line 70) | public byte[] getResponse(byte[] data) {
    method getRemoteResponse (line 83) | public List<byte[]> getRemoteResponse(byte[] data) {
    method isMine (line 109) | @Override
    method getHttpMethod (line 127) | private Map<String, String> getHttpMethod(byte[] data) {
    method reconstructHttpHeader (line 163) | private byte[] reconstructHttpHeader(Map<String, String> method, byte[...
    method setHttpMethod (line 216) | private void setHttpMethod(Map<String, String> header) {

FILE: src/main/java/com/stfl/network/proxy/IProxy.java
  type IProxy (line 36) | public interface IProxy {
    type TYPE (line 37) | enum TYPE {SOCKS5, HTTP, AUTO}
    method isReady (line 39) | boolean isReady();
    method getType (line 40) | TYPE getType();
    method getResponse (line 41) | byte[] getResponse(byte[] data);
    method getRemoteResponse (line 42) | List<byte[]> getRemoteResponse(byte[] data);
    method isMine (line 43) | boolean isMine(byte[] data);

FILE: src/main/java/com/stfl/network/proxy/ProxyFactory.java
  class ProxyFactory (line 41) | public class ProxyFactory {
    method isProxyTypeExisted (line 49) | public static boolean isProxyTypeExisted(String name) {
    method get (line 54) | public static IProxy get(IProxy.TYPE type) {
    method getSupportedProxyTypes (line 66) | public static List<IProxy.TYPE> getSupportedProxyTypes() {

FILE: src/main/java/com/stfl/network/proxy/Socks5Proxy.java
  class Socks5Proxy (line 41) | public class Socks5Proxy implements IProxy {
    type STAGE (line 47) | private enum STAGE {SOCK5_HELLO, SOCKS_ACK, SOCKS_READY}
    method Socks5Proxy (line 50) | public Socks5Proxy() {
    method getType (line 54) | public TYPE getType() {
    method isReady (line 58) | public boolean isReady() {
    method getResponse (line 62) | public byte[] getResponse(byte[] data) {
    method getRemoteResponse (line 88) | public List<byte[]> getRemoteResponse(byte[] data) {
    method isMine (line 114) | @Override

FILE: src/main/java/com/stfl/ss/AesCrypt.java
  class AesCrypt (line 50) | public class AesCrypt extends CryptBase {
    method getCiphers (line 59) | public static Map<String, String> getCiphers() {
    method AesCrypt (line 71) | public AesCrypt(String name, String password) {
    method getKeyLength (line 75) | @Override
    method getCipher (line 90) | @Override
    method getIVLength (line 120) | @Override
    method getKey (line 125) | @Override
    method _encrypt (line 130) | @Override
    method _decrypt (line 139) | @Override

FILE: src/main/java/com/stfl/ss/BlowFishCrypt.java
  class BlowFishCrypt (line 48) | public class BlowFishCrypt extends CryptBase {
    method getCiphers (line 52) | public static Map<String, String> getCiphers() {
    method BlowFishCrypt (line 59) | public BlowFishCrypt(String name, String password) {
    method getKeyLength (line 63) | @Override
    method getCipher (line 68) | @Override
    method getIVLength (line 83) | @Override
    method getKey (line 88) | @Override
    method _encrypt (line 93) | @Override
    method _decrypt (line 102) | @Override

FILE: src/main/java/com/stfl/ss/CamelliaCrypt.java
  class CamelliaCrypt (line 48) | public class CamelliaCrypt extends CryptBase {
    method getCiphers (line 54) | public static Map<String, String> getCiphers() {
    method CamelliaCrypt (line 63) | public CamelliaCrypt(String name, String password) {
    method getKeyLength (line 67) | @Override
    method getCipher (line 82) | @Override
    method getIVLength (line 103) | @Override
    method getKey (line 108) | @Override
    method _encrypt (line 113) | @Override
    method _decrypt (line 122) | @Override

FILE: src/main/java/com/stfl/ss/CryptBase.java
  class CryptBase (line 50) | public abstract class CryptBase implements ICrypt {
    method getCipher (line 52) | protected abstract StreamBlockCipher getCipher(boolean isEncrypted) th...
    method getKey (line 53) | protected abstract SecretKey getKey();
    method _encrypt (line 54) | protected abstract void _encrypt(byte[] data, ByteArrayOutputStream st...
    method _decrypt (line 55) | protected abstract void _decrypt(byte[] data, ByteArrayOutputStream st...
    method CryptBase (line 72) | public CryptBase(String name, String password) {
    method setIV (line 80) | protected void setIV(byte[] iv, boolean isEncrypt)
    method encrypt (line 112) | @Override
    method encrypt (line 132) | @Override
    method decrypt (line 139) | @Override
    method decrypt (line 158) | @Override

FILE: src/main/java/com/stfl/ss/CryptFactory.java
  class CryptFactory (line 41) | public class CryptFactory {
    method isCipherExisted (line 51) | public static boolean isCipherExisted(String name) {
    method get (line 55) | public static ICrypt get(String name, String password) {
    method getSupportedCiphers (line 67) | public static List<String> getSupportedCiphers() {

FILE: src/main/java/com/stfl/ss/ICrypt.java
  type ICrypt (line 40) | public interface ICrypt {
    method encrypt (line 41) | void encrypt(byte[] data, ByteArrayOutputStream stream);
    method encrypt (line 42) | void encrypt(byte[] data, int length, ByteArrayOutputStream stream);
    method decrypt (line 43) | void decrypt(byte[] data, ByteArrayOutputStream stream);
    method decrypt (line 44) | void decrypt(byte[] data, int length, ByteArrayOutputStream stream);
    method getIVLength (line 45) | int getIVLength();
    method getKeyLength (line 46) | int getKeyLength();

FILE: src/main/java/com/stfl/ss/SeedCrypt.java
  class SeedCrypt (line 48) | public class SeedCrypt extends CryptBase {
    method getCiphers (line 52) | public static Map<String, String> getCiphers() {
    method SeedCrypt (line 59) | public SeedCrypt(String name, String password) {
    method getKeyLength (line 63) | @Override
    method getCipher (line 68) | @Override
    method getIVLength (line 83) | @Override
    method getKey (line 88) | @Override
    method _encrypt (line 93) | @Override
    method _decrypt (line 102) | @Override

FILE: src/main/java/com/stfl/ss/ShadowSocksKey.java
  class ShadowSocksKey (line 44) | public class ShadowSocksKey implements SecretKey {
    method ShadowSocksKey (line 51) | public ShadowSocksKey(String password) {
    method ShadowSocksKey (line 56) | public ShadowSocksKey(String password, int length) {
    method init (line 62) | private byte[] init(String password) {
    method getAlgorithm (line 104) | @Override
    method getFormat (line 109) | @Override
    method getEncoded (line 114) | @Override

FILE: src/main/java/com/stfl/ui/LogLayoutController.java
  class LogLayoutController (line 9) | public class LogLayoutController {
    method initialize (line 19) | @FXML
    method handleClear (line 26) | @FXML
    method handleClose (line 31) | @FXML
    method setStage (line 36) | public void setStage(Stage stage) {

FILE: src/main/java/com/stfl/ui/MainLayoutController.java
  class MainLayoutController (line 29) | public class MainLayoutController {
    method initialize (line 57) | @FXML
    method handleStart (line 101) | @FXML
    method handleStop (line 161) | @FXML
    method handleLog (line 174) | @FXML
    method handleClose (line 179) | @FXML
    method setMainGui (line 184) | public void setMainGui(MainGui gui) {
    method closeServer (line 188) | public void closeServer() {
    method validationInput (line 192) | private boolean validationInput(String pattern, String text) {
    method showAlert (line 196) | private void showAlert(String title, String message, Alert.AlertType t...

FILE: src/main/java/com/stfl/ui/TextAreaLogHandler.java
  class TextAreaLogHandler (line 9) | public class TextAreaLogHandler extends StreamHandler {
    method setTextArea (line 12) | public void setTextArea(TextArea textArea) {
    method publish (line 16) | @Override
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (152K chars).
[
  {
    "path": ".gitignore",
    "chars": 79,
    "preview": "# ignore output folders\n.idea/\nout/\ntarget/\n\n# ignore files\nconfig.json\n*.iml\n\n"
  },
  {
    "path": "LICENSE",
    "chars": 1520,
    "preview": "shadowsocks-java is distributed under the following BSD-style license:\n\nCopyright (c) 2015, Blake <stfl0622@gmail.com>\nA"
  },
  {
    "path": "README.md",
    "chars": 1116,
    "preview": "This project is being discontinued\n==================================\n\nSince I am busy at work in the past few years and"
  },
  {
    "path": "pom.xml",
    "chars": 1815,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www"
  },
  {
    "path": "src/main/java/com/stfl/Constant.java",
    "chars": 350,
    "preview": "package com.stfl;\n\nimport java.util.Locale;\n\npublic class Constant {\n    public static final String PROG_NAME = \"shadows"
  },
  {
    "path": "src/main/java/com/stfl/Main.java",
    "chars": 4435,
    "preview": "package com.stfl;\n\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.network.LocalServer;\nimport c"
  },
  {
    "path": "src/main/java/com/stfl/MainGui.java",
    "chars": 4663,
    "preview": "package com.stfl;\n\nimport com.stfl.misc.UTF8Control;\nimport com.stfl.ui.MainLayoutController;\nimport javafx.application."
  },
  {
    "path": "src/main/java/com/stfl/misc/Config.java",
    "chars": 5566,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/misc/Log.java",
    "chars": 3380,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/misc/Reflection.java",
    "chars": 1203,
    "preview": "package com.stfl.misc;\n\nimport java.lang.reflect.Constructor;\n\npublic class Reflection {\n    public static Object get(St"
  },
  {
    "path": "src/main/java/com/stfl/misc/UTF8Control.java",
    "chars": 1545,
    "preview": "package com.stfl.misc;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport"
  },
  {
    "path": "src/main/java/com/stfl/misc/Util.java",
    "chars": 7485,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/IServer.java",
    "chars": 91,
    "preview": "package com.stfl.network;\n\npublic interface IServer extends Runnable {\n    void close();\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/LocalServer.java",
    "chars": 3657,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/NioLocalServer.java",
    "chars": 7857,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/io/PipeSocket.java",
    "chars": 9942,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/ChangeRequest.java",
    "chars": 2130,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/ISocketHandler.java",
    "chars": 1710,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/PipeEvent.java",
    "chars": 1824,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/PipeWorker.java",
    "chars": 7467,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/RemoteSocketHandler.java",
    "chars": 7664,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/SocketHandlerBase.java",
    "chars": 6506,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/AutoProxy.java",
    "chars": 1813,
    "preview": "package com.stfl.network.proxy;\n\nimport com.stfl.misc.Reflection;\n\nimport java.util.List;\nimport java.util.Map;\nimport j"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/HttpProxy.java",
    "chars": 7849,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/IProxy.java",
    "chars": 1798,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/ProxyFactory.java",
    "chars": 2708,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/Socks5Proxy.java",
    "chars": 4002,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/AesCrypt.java",
    "chars": 5473,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/BlowFishCrypt.java",
    "chars": 3707,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/CamelliaCrypt.java",
    "chars": 4576,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/CryptBase.java",
    "chars": 5918,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/CryptFactory.java",
    "chars": 2694,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/ICrypt.java",
    "chars": 1971,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/SeedCrypt.java",
    "chars": 3669,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ss/ShadowSocksKey.java",
    "chars": 3821,
    "preview": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or"
  },
  {
    "path": "src/main/java/com/stfl/ui/LogLayoutController.java",
    "chars": 757,
    "preview": "package com.stfl.ui;\n\nimport com.stfl.misc.Log;\nimport javafx.fxml.FXML;\nimport javafx.scene.control.Button;\nimport java"
  },
  {
    "path": "src/main/java/com/stfl/ui/MainLayoutController.java",
    "chars": 6780,
    "preview": "package com.stfl.ui;\n\nimport com.stfl.Constant;\nimport com.stfl.MainGui;\nimport com.stfl.misc.Config;\nimport com.stfl.mi"
  },
  {
    "path": "src/main/java/com/stfl/ui/TextAreaLogHandler.java",
    "chars": 930,
    "preview": "package com.stfl.ui;\n\nimport javafx.application.Platform;\nimport javafx.scene.control.TextArea;\n\nimport java.util.loggin"
  },
  {
    "path": "src/main/resources/META-INF/MANIFEST.MF",
    "chars": 107,
    "preview": "Manifest-Version: 1.0\nClass-Path: bcprov-jdk15on-1.52.jar json-simple-1.1.1.jar\nMain-Class: com.stfl.Main\n\n"
  },
  {
    "path": "src/main/resources/resources/bundle/ui_en.properties",
    "chars": 193,
    "preview": "START=Start\nSTOP=Stop\nLOG=Log\nCLOSE=Close\nCLOSE1=Close\nCLEAR=Clear\nSERVER_IP=Server IP:\nSERVER_PORT=Server Port:\nCIPHER="
  },
  {
    "path": "src/main/resources/resources/bundle/ui_zh_CN.properties",
    "chars": 142,
    "preview": "START=启动\nSTOP=停止\nLOG=日志\nCLOSE=关闭\nCLEAR=清除\nSERVER_IP=服务器 IP:\nSERVER_PORT=服务器端口:\nCIPHER=加密方式:\nPASSWORD=密码:\nLOCAL_PORT=本地端口"
  },
  {
    "path": "src/main/resources/resources/bundle/ui_zh_TW.properties",
    "chars": 148,
    "preview": "START=啟動\nSTOP=停止\nLOG=日誌\nCLOSE=關閉\nCLEAR=清除\nSERVER_IP=伺服器 IP:\nSERVER_PORT=伺服器 Port:\nCIPHER=加密方式:\nPASSWORD=密碼:\nLOCAL_PORT=本"
  },
  {
    "path": "src/main/resources/resources/ui/LogLayout.fxml",
    "chars": 861,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.lang.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.l"
  },
  {
    "path": "src/main/resources/resources/ui/MainLayout.fxml",
    "chars": 1924,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.l"
  }
]

About this extraction

This page contains the full source code of the blakey22/shadowsocks-java GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (140.5 KB), approximately 31.8k tokens, and a symbol index with 241 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!