[
  {
    "path": ".gitignore",
    "content": "# ignore output folders\n.idea/\nout/\ntarget/\n\n# ignore files\nconfig.json\n*.iml\n\n"
  },
  {
    "path": "LICENSE",
    "content": "shadowsocks-java is distributed under the following BSD-style license:\n\nCopyright (c) 2015, Blake <stfl0622@gmail.com>\nAll Rights Reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation\nand/or other materials provided with the distribution.\n\n3. The name of the author may not be used to endorse or promote\nproducts derived from this software without specific prior\nwritten permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE."
  },
  {
    "path": "README.md",
    "content": "This project is being discontinued\n==================================\n\nSince 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.\n\n\nshadowsocks-java\n================\n\nshadowsocks-java is a pure JAVA client for [shadowsocks](https://github.com/shadowsocks/shadowsocks) project.\n\nOnly tested AES encryption.\n\n### Requirements\n    * Bouncy Castle v1.5.2 [Release](https://www.bouncycastle.org/)\n    * json-simple v1.1.1 [Release](https://code.google.com/p/json-simple/)\n    \n### Developers\n    * Using Non-blocking server\n        Config config = new Config(\"SS_SERVER_IP\", \"SS_SERVER_PORT\", \"LOCAL_IP\", \"LOCAL_PORT\", \"CIPHER_NAME\", \"PASSWORD\");\n        NioLocalServer server = new NioLocalServer(config);\n        new Thread(server).start();\n        \n    * Using blocking server\n        Config config = new Config(\"SS_SERVER_IP\", \"SS_SERVER_PORT\", \"LOCAL_IP\", \"LOCAL_PORT\", \"CIPHER_NAME\", \"PASSWORD\");\n        LocalServer server = new LocalServer(config);\n        new Thread(server).start();\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.stfl</groupId>\n    <artifactId>shadowsocks-java</artifactId>\n    <version>0.2-SNAPSHOT</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.0</version>\n                <configuration>\n                    <source>1.7</source>\n                    <target>1.7</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.bouncycastle</groupId>\n            <artifactId>bcprov-jdk15on</artifactId>\n            <version>1.52</version>\n        </dependency>\n        <dependency>\n            <groupId>com.googlecode.json-simple</groupId>\n            <artifactId>json-simple</artifactId>\n            <version>1.1.1</version>\n            <!-- remove junit dependency since it's not required during runtime -->\n            <exclusions>\n                <exclusion>\n                    <groupId>junit</groupId>\n                    <artifactId>junit</artifactId>\n                </exclusion>\n                <exclusion>\n                    <groupId>org.hamcrest</groupId>\n                    <artifactId>hamcrest-core</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "src/main/java/com/stfl/Constant.java",
    "content": "package com.stfl;\n\nimport java.util.Locale;\n\npublic class Constant {\n    public static final String PROG_NAME = \"shadowsocks-java\";\n    public static final String VERSION = \"0.2\";\n    public static final int BUFFER_SIZE = 16384;\n    public static final String CONF_FILE = \"config.json\";\n    public static final Locale LOCALE = Locale.getDefault();\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/Main.java",
    "content": "package com.stfl;\n\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.network.LocalServer;\nimport com.stfl.network.NioLocalServer;\nimport com.stfl.network.proxy.IProxy;\nimport com.stfl.network.proxy.ProxyFactory;\nimport com.stfl.ss.CryptFactory;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.logging.Logger;\n\npublic class Main {\n    private static Logger logger = Logger.getLogger(Main.class.getName());\n\n    public static void main(String[] args) {\n        if (args.length != 0) {\n            startCommandLine(args);\n        }\n        else {\n            MainGui.launch(MainGui.class);\n        }\n    }\n\n    private static void startCommandLine(String[] args) {\n        Config config;\n\n        config = parseArgument(args);\n        if (config == null) {\n            printUsage();\n            return;\n        }\n\n        Util.saveFile(Constant.CONF_FILE, config.saveToJson());\n\n        try {\n            //LocalServer server = new LocalServer(config);\n            NioLocalServer server = new NioLocalServer(config);\n            Thread t = new Thread(server);\n            t.start();\n            t.join();\n        } catch (Exception e) {\n            logger.warning(\"Unable to start server: \" + e.toString());\n        }\n    }\n\n    private static Config parseArgument(String[] args) {\n        Config config = new Config();\n\n        if (args.length == 2) {\n            if (args[0].equals(\"--config\")) {\n                Path path = Paths.get(args[1]);\n                try {\n                    String json = new String(Files.readAllBytes(path));\n                    config.loadFromJson(json);\n                } catch (IOException e) {\n                    System.out.println(\"Unable to read configuration file: \" + args[1]);\n                    return null;\n                }\n                return config;\n            }\n            else {\n                return null;\n            }\n        }\n\n        if (args.length != 8) {\n            return null;\n        }\n\n        // parse arguments\n        for (int i = 0; i < args.length; i+=2) {\n            String[] tempArgs;\n            if (args[i].equals(\"--local\")) {\n                tempArgs = args[i+1].split(\":\");\n                if (tempArgs.length < 2) {\n                    System.out.println(\"Invalid argument: \" + args[i]);\n                    return null;\n                }\n                config.setLocalIpAddress(tempArgs[0]);\n                config.setLocalPort(Integer.parseInt(tempArgs[1]));\n            }\n            else if (args[i].equals(\"--remote\")) {\n                tempArgs = args[i+1].split(\":\");\n                if (tempArgs.length < 2) {\n                    System.out.println(\"Invalid argument: \" + args[i]);\n                    return null;\n                }\n                config.setRemoteIpAddress(tempArgs[0]);\n                config.setRemotePort(Integer.parseInt(tempArgs[1]));\n            }\n            else if (args[i].equals(\"--cipher\")) {\n                config.setMethod(args[i+1]);\n            }\n            else if (args[i].equals(\"--password\")) {\n                config.setPassword(args[i + 1]);\n            }\n            else if (args[i].equals(\"--proxy\")) {\n                config.setProxyType(args[i + 1]);\n            }\n        }\n\n        return config;\n    }\n\n    private static void printUsage() {\n        System.out.println(\"Usage: ss --[option] value --[option] value...\");\n        System.out.println(\"Option:\");\n        System.out.println(\"  --local [IP:PORT]\");\n        System.out.println(\"  --remote [IP:PORT]\");\n        System.out.println(\"  --cipher [CIPHER_NAME]\");\n        System.out.println(\"  --password [PASSWORD]\");\n        System.out.println(\"  --config [CONFIG_FILE]\");\n        System.out.println(\"  --proxy [TYPE]\");\n        System.out.println(\"Support Proxy Type:\");\n        for (IProxy.TYPE t : ProxyFactory.getSupportedProxyTypes()) {\n            System.out.printf(\"  %s\\n\", t.toString().toLowerCase());\n        }\n        System.out.println(\"Support Ciphers:\");\n        for (String s : CryptFactory.getSupportedCiphers()) {\n            System.out.printf(\"  %s\\n\", s);\n        }\n        System.out.println(\"Example:\");\n        System.out.println(\"  ss --local \\\"127.0.0.1:1080\\\" --remote \\\"[SS_SERVER_IP]:1080\\\" --cipher \\\"aes-256-cfb\\\" --password \\\"HelloWorld\\\"\");\n        System.out.println(\"  ss --config config.json\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/MainGui.java",
    "content": "package com.stfl;\n\nimport com.stfl.misc.UTF8Control;\nimport com.stfl.ui.MainLayoutController;\nimport javafx.application.Application;\nimport javafx.application.Platform;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.Scene;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.Pane;\nimport javafx.stage.Stage;\n\nimport javax.imageio.ImageIO;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.io.IOException;\nimport java.util.ResourceBundle;\nimport java.util.logging.Logger;\n\npublic class MainGui extends Application {\n    private static Logger logger = Logger.getLogger(MainGui.class.getName());\n    private Stage primaryStage;\n    private Scene rootScene;\n    private MainLayoutController controller;\n    private TrayIcon trayIcon;\n\n    @Override\n    public void start(Stage primaryStage) throws Exception {\n\n        Platform.setImplicitExit(false);\n        this.primaryStage = primaryStage;\n        this.primaryStage.setTitle(\"Server Configuration\");\n\n        try {\n            // Load the root layout from the fxml file\n            FXMLLoader mainLayoutLoader = new FXMLLoader(MainGui.class.getResource(\"/resources/ui/MainLayout.fxml\"));\n            mainLayoutLoader.setResources(ResourceBundle.getBundle(\"resources.bundle.ui\", Constant.LOCALE, new UTF8Control()));\n            Pane rootLayout = mainLayoutLoader.load();\n\n            rootScene = new Scene(rootLayout);\n            primaryStage.setScene(rootScene);\n            primaryStage.setResizable(false);\n\n            controller = mainLayoutLoader.getController();\n            controller.setMainGui(this);\n\n            addToTray();\n\n            primaryStage.getIcons().add(new Image(MainGui.class.getResource(\"/resources/image/icon.png\").toString()));\n            primaryStage.show();\n        } catch (IOException e) {\n            // Exception gets thrown if the fxml file could not be loaded\n            e.printStackTrace();\n        }\n    }\n\n\n    private void addToTray() {\n        // ensure awt is initialized\n        java.awt.Toolkit.getDefaultToolkit();\n\n        // make sure system tray is supported\n        if (!java.awt.SystemTray.isSupported()) {\n            logger.warning(\"No system tray support!\");\n        }\n\n        final java.awt.SystemTray tray = java.awt.SystemTray.getSystemTray();\n        try {\n\n            java.awt.Image image = ImageIO.read(MainGui.class.getResource(\"/resources/image/icon.png\"));\n            trayIcon = new TrayIcon(image);\n            trayIcon.addActionListener(new ActionListener() {\n                @Override\n                public void actionPerformed(ActionEvent e) {\n                    Platform.runLater(new Runnable() {\n                        @Override\n                        public void run() {\n                            primaryStage.show();\n                        }\n                    });\n                }\n            });\n\n            java.awt.MenuItem openItem = new java.awt.MenuItem(\"Configuration\");\n            openItem.addActionListener(new ActionListener() {\n                @Override\n                public void actionPerformed(ActionEvent e) {\n                    Platform.runLater(new Runnable() {\n                        @Override\n                        public void run() {\n                            show();\n                        }\n                    });\n                }\n            });\n\n            java.awt.MenuItem exitItem = new java.awt.MenuItem(\"Exit\");\n            exitItem.addActionListener(new ActionListener() {\n                @Override\n                public void actionPerformed(ActionEvent e) {\n                    controller.closeServer();\n                    Platform.exit();\n                    tray.remove(trayIcon);\n                }\n            });\n\n            PopupMenu popup = new PopupMenu();\n            popup.add(openItem);\n            popup.addSeparator();\n            popup.add(exitItem);\n            trayIcon.setPopupMenu(popup);\n            trayIcon.setToolTip(\"Not Connected\");\n            tray.add(trayIcon);\n        } catch (IOException e) {\n            e.printStackTrace();\n        } catch (AWTException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void show() {\n        primaryStage.show();\n    }\n\n    public void hide() {\n        primaryStage.hide();\n    }\n\n    public void setTooltip(String message) {\n        if (trayIcon != null) {\n            trayIcon.setToolTip(message);\n        }\n    }\n\n    public void showNotification(String message) {\n        trayIcon.displayMessage(\n                \"shadowsocks-java\",\n                message,\n                java.awt.TrayIcon.MessageType.INFO\n        );\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/misc/Config.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.misc;\n\nimport com.stfl.network.proxy.IProxy;\nimport org.json.simple.JSONObject;\nimport org.json.simple.JSONValue;\nimport com.stfl.ss.AesCrypt;\n\n/**\n * Data class for configuration to bring up server\n */\npublic class Config {\n    private String _ipAddr;\n    private int _port;\n    private String _localIpAddr;\n    private int _localPort;\n    private String _method;\n    private String _password;\n    private String _logLevel;\n    private IProxy.TYPE _proxyType;\n\n    public Config() {\n        loadFromJson(\"\");\n    }\n\n    public Config(String ipAddr, int port, String localIpAddr, int localPort, String method, String password) {\n        this();\n        _ipAddr = ipAddr;\n        _port = port;\n        _localIpAddr = localIpAddr;\n        _localPort = localPort;\n        _method = method;\n        _password = password;\n        _proxyType = IProxy.TYPE.AUTO;\n    }\n\n    public Config(String ipAddr, int port, String localIpAddr, int localPort, String method, String password, IProxy.TYPE type) {\n        this(ipAddr, port, localIpAddr, localPort, method, password);\n        _proxyType = type;\n    }\n\n    public void setRemoteIpAddress(String value) {\n        _ipAddr = value;\n    }\n\n    public String getRemoteIpAddress() {\n        return _ipAddr;\n    }\n\n    public void setLocalIpAddress(String value) {\n        _localIpAddr = value;\n    }\n\n    public String getLocalIpAddress() {\n        return _localIpAddr;\n    }\n\n    public void setRemotePort(int value) {\n        _port = value;\n    }\n\n    public int getRemotePort() {\n        return _port;\n    }\n\n    public void setLocalPort(int value) {\n        _localPort = value;\n    }\n\n    public int getLocalPort() {\n        return _localPort;\n    }\n\n    public void setProxyType(String value) {\n        _proxyType = IProxy.TYPE.AUTO;\n        if (value.toLowerCase().equals(IProxy.TYPE.HTTP.toString().toLowerCase())) {\n            _proxyType = IProxy.TYPE.HTTP;\n        }\n        else if (value.toLowerCase().equals(IProxy.TYPE.SOCKS5.toString().toLowerCase())) {\n            _proxyType = IProxy.TYPE.SOCKS5;\n        }\n    }\n\n    public void setProxyType(IProxy.TYPE value) {\n        _proxyType = value;\n    }\n    public IProxy.TYPE getProxyType() {\n        return _proxyType;\n    }\n\n    public void setMethod(String value) {\n        _method = value;\n    }\n\n    public String getMethod() {\n        return _method;\n    }\n\n    public void setPassword(String value) {\n        _password = value;\n    }\n\n    public String getPassword() {\n        return _password;\n    }\n\n    public void setLogLevel(String value) {\n        _logLevel = value;\n        Log.init(getLogLevel());\n    }\n\n    public String getLogLevel() {\n        return _logLevel;\n    }\n\n    public void loadFromJson(String jsonStr) {\n        if (jsonStr.length() == 0) {\n            jsonStr = \"{}\";\n        }\n\n        JSONObject jObj = (JSONObject)JSONValue.parse(jsonStr);\n        _ipAddr = (String)jObj.getOrDefault(\"remoteIpAddress\", \"\");\n        _port = ((Number)jObj.getOrDefault(\"remotePort\", 1080)).intValue();\n        _localIpAddr = (String)jObj.getOrDefault(\"localIpAddress\", \"127.0.0.1\");\n        _localPort = ((Number)jObj.getOrDefault(\"localPort\", 1080)).intValue();\n        _method = (String)jObj.getOrDefault(\"method\", AesCrypt.CIPHER_AES_256_CFB);\n        _password = (String)jObj.getOrDefault(\"password\", \"\");\n        _logLevel = (String)jObj.getOrDefault(\"logLevel\", \"INFO\");\n        setProxyType((String) jObj.getOrDefault(\"proxyType\", IProxy.TYPE.SOCKS5.toString().toLowerCase()));\n        setLogLevel(_logLevel);\n    }\n\n    public String saveToJson() {\n        JSONObject jObj = new JSONObject();\n        jObj.put(\"remoteIpAddress\", _ipAddr);\n        jObj.put(\"remotePort\", _port);\n        jObj.put(\"localIpAddress\", _localIpAddr);\n        jObj.put(\"localPort\", _localPort);\n        jObj.put(\"method\", _method);\n        jObj.put(\"password\", _password);\n        jObj.put(\"proxyType\", _proxyType.toString().toLowerCase());\n        jObj.put(\"logLevel\", _logLevel);\n\n        return Util.prettyPrintJson(jObj);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/misc/Log.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.misc;\n\nimport java.util.Locale;\nimport java.util.Properties;\nimport java.util.logging.*;\n\n/**\n * Initialized level of root logger\n */\npublic class Log {\n    private static boolean handlerInit = false;\n\n    public static void init() {\n        init(Level.INFO);\n    }\n\n    public static void init(Level level) {\n        Logger rootLogger = getRootLogger();\n        if (handlerInit) {\n            rootLogger.setLevel(level);\n            for(Handler handler : rootLogger.getHandlers()) {\n                handler.setLevel(level);\n            }\n            return;\n        }\n\n        // disable message localization\n        Locale.setDefault(Locale.ENGLISH);\n        // config log output format\n        Properties props = System.getProperties();\n        props.setProperty(\"java.util.logging.SimpleFormatter.format\", \"%1$tY-%1$tb-%1$td %1$tT [%4$s] %5$s%n\");\n        // setup root logger\n        //Logger rootLogger = getRootLogger();\n        rootLogger.setUseParentHandlers(false);\n        for(Handler handler : rootLogger.getHandlers()) {\n            rootLogger.removeHandler(handler);\n        }\n        // set log level and format\n        rootLogger.setLevel(level);\n        ConsoleHandler handler = new ConsoleHandler();\n        handler.setLevel(level);\n        rootLogger.addHandler(handler);\n        handlerInit = true;\n    }\n\n    public static void init(String level) {\n        Level l = Level.parse(level);\n        init(l);\n    }\n\n    public static void addHandler(Handler handler) {\n        Logger rootLogger = getRootLogger();\n        Level logLevel = Level.INFO;\n        for (Handler h : rootLogger.getHandlers()) {\n            logLevel = h.getLevel();\n        }\n\n        handler.setLevel(logLevel);\n        rootLogger.addHandler(handler);\n    }\n\n    private static Logger getRootLogger() {\n        return Logger.getLogger(\"com.stfl\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/misc/Reflection.java",
    "content": "package com.stfl.misc;\n\nimport java.lang.reflect.Constructor;\n\npublic class Reflection {\n    public static Object get(String className, Object... args) {\n        Object retValue = null;\n        try {\n            Class c = Class.forName(className);\n            if (args.length == 0) {\n                retValue = c.newInstance();\n            }\n            else if ((args.length & 1) == 0) {\n                // args should come with pairs, for example\n                // String.class, \"arg1_value\", String.class, \"arg2_value\"\n                Class[] oParam = new Class[args.length / 2];\n                for (int arg_i = 0, i = 0; arg_i < args.length; arg_i+=2, i++) {\n                    oParam[i] = (Class)args[arg_i];\n                }\n\n                Constructor constructor = c.getConstructor(oParam);\n                Object[] paramObjs = new Object[args.length / 2];\n                for (int arg_i = 1, i = 0; arg_i < args.length; arg_i+=2, i++) {\n                    paramObjs[i] = args[arg_i];\n                }\n                retValue = constructor.newInstance(paramObjs);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return retValue;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/misc/UTF8Control.java",
    "content": "package com.stfl.misc;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.Locale;\nimport java.util.PropertyResourceBundle;\nimport java.util.ResourceBundle;\n\npublic class UTF8Control extends ResourceBundle.Control {\n    public ResourceBundle newBundle\n            (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)\n            throws IllegalAccessException, InstantiationException, IOException\n    {\n        // below is the original implementation\n        String bundleName = toBundleName(baseName, locale);\n        String resourceName = toResourceName(bundleName, \"properties\");\n        ResourceBundle bundle = null;\n        InputStream stream = null;\n        if (reload) {\n            URL url = loader.getResource(resourceName);\n            if (url != null) {\n                URLConnection connection = url.openConnection();\n                if (connection != null) {\n                    connection.setUseCaches(false);\n                    stream = connection.getInputStream();\n                }\n            }\n        } else {\n            stream = loader.getResourceAsStream(resourceName);\n        }\n\n        if (stream != null) {\n            try {\n                // load string use UTF-8 encoding\n                bundle = new PropertyResourceBundle(new InputStreamReader(stream, \"UTF-8\"));\n            } finally {\n                stream.close();\n            }\n        }\n        return bundle;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/misc/Util.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.misc;\n\nimport com.stfl.network.proxy.Socks5Proxy;\nimport org.json.simple.JSONObject;\n\nimport java.io.*;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.SecureRandom;\n\n/**\n * Helper class\n */\npublic class Util {\n    public static String dumpBytes(byte[] a) {\n        StringBuilder sb = new StringBuilder(a.length * 2);\n        for(byte b: a)\n            sb.append(String.format(\"%x\", b & 0xff));\n        return sb.toString();\n    }\n\n    public static byte[] randomBytes(int size) {\n        byte[] bytes = new byte[size];\n        new SecureRandom().nextBytes(bytes);\n        return bytes;\n    }\n\n    public static String getErrorMessage(Throwable e) {\n        Writer writer = new StringWriter();\n        PrintWriter pWriter = new PrintWriter(writer);\n        e.printStackTrace(pWriter);\n        return writer.toString();\n    }\n\n    public static String prettyPrintJson(JSONObject jObj) {\n        String retValue;\n        StringWriter writer = new StringWriter() {\n            private final static String indent = \"  \";\n            private final String LINE_SEP = System.getProperty(\"line.separator\");\n            private int indentLevel = 0;\n\n            @Override\n            public void write(int c) {\n                char ch = (char) c;\n                if (ch == '[' || ch == '{') {\n                    super.write(c);\n                    super.write(LINE_SEP);\n                    indentLevel++;\n                    writeIndentation();\n                }\n                else if (ch == ']' || ch == '}') {\n                    super.write(LINE_SEP);\n                    indentLevel--;\n                    writeIndentation();\n                    super.write(c);\n                }\n                else if (ch == ':') {\n                    super.write(c);\n                    super.write(\" \");\n                }\n                else if (ch == ',') {\n                    super.write(c);\n                    super.write(LINE_SEP);\n                    writeIndentation();\n                }\n                else {\n                    super.write(c);\n                }\n\n            }\n\n            private void writeIndentation()\n            {\n                for (int i = 0; i < indentLevel; i++)\n                {\n                    super.write(indent);\n                }\n            }\n        };\n\n        try {\n            jObj.writeJSONString(writer);\n            retValue = writer.toString();\n        } catch (IOException e) {\n            // something wrong with writer, use the original method\n            retValue = jObj.toJSONString();\n        }\n\n        return retValue;\n    }\n\n    public static String getRequestedHostInfo(byte[] data) {\n        String ret = \"\";\n        int port;\n        int neededLength;\n        switch (data[0]) {\n            case Socks5Proxy.ATYP_IP_V4:\n                // IP v4 Address\n                // 4 bytes of IP, 2 bytes of port\n                neededLength = 6;\n                if (data.length > neededLength) {\n                    port = getPort(data[5], data[6]);\n                    ret = String.format(\"%d.%d.%d.%d:%d\", data[1], data[2], data[3], data[4], port);\n                }\n                break;\n            case Socks5Proxy.ATYP_DOMAIN_NAME:\n                // domain\n                neededLength = data[1];\n                if (data.length > neededLength + 3) {\n                    port = getPort(data[neededLength + 2], data[neededLength + 3]);\n                    String domain = bytesToString(data, 2, neededLength);\n                    ret = String.format(\"%s:%d\", domain, port);\n                }\n                break;\n            case Socks5Proxy.ATYP_IP_V6:\n                // IP v6 Address\n                // 16 bytes of IP, 2 bytes of port\n                neededLength = 18;\n                if (data.length > neededLength) {\n                    port = getPort(data[17], data[18]);\n                    ret = String.format(\"%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%d\",\n                            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],\n                            data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16],\n                            port);\n                }\n                break;\n        }\n\n        return ret;\n    }\n\n    public static String bytesToString(byte[] data, int start, int length) {\n        String str = \"\";\n\n        try {\n            str = new String(data, start, length, \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n        }\n\n        return str;\n    }\n\n    public static byte[] composeSSHeader(String host, int port) {\n        // TYPE (1 byte) + LENGTH (1 byte) + HOST (var bytes) + PORT (2 bytes)\n        byte[] respData = new byte[host.length() + 4];\n\n        respData[0] = Socks5Proxy.ATYP_DOMAIN_NAME;\n        respData[1] = (byte)host.length();\n        System.arraycopy(host.getBytes(), 0, respData, 2, host.length());\n        respData[host.length() + 2] = (byte)(port >> 8);\n        respData[host.length() + 3] = (byte)(port & 0xFF);\n\n        return  respData;\n    }\n\n    public static boolean saveFile(String fn, String content) {\n        PrintWriter writer;\n        try {\n            writer = new PrintWriter(fn);\n            writer.println(content);\n            writer.close();\n        } catch (FileNotFoundException e) {\n            return false;\n        }\n        return true;\n    }\n\n    public static String getFileContent(String fn) {\n        Path path = Paths.get(fn);\n        String content = \"\";\n        try {\n            content = new String(Files.readAllBytes(path));\n        } catch (IOException e) {\n            // do nothing\n        }\n\n        return content;\n    }\n\n    private static short byteToUnsignedByte(byte b) {\n        return (short)(b & 0xff);\n    }\n\n    private static int getPort(byte b, byte b1) {\n        return byteToUnsignedByte(b) << 8 | byteToUnsignedByte(b1);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/IServer.java",
    "content": "package com.stfl.network;\n\npublic interface IServer extends Runnable {\n    void close();\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/LocalServer.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\nimport java.util.logging.Logger;\n\nimport com.stfl.Constant;\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.network.io.PipeSocket;\nimport com.stfl.ss.CryptFactory;\n\n/**\n * Blocking local server for shadowsocks\n */\npublic class LocalServer implements IServer {\n    private Logger logger = Logger.getLogger(LocalServer.class.getName());\n    private Config _config;\n    private ServerSocket _serverSocket;\n    private Executor _executor;\n    private List<PipeSocket> _pipes;\n\n    public LocalServer(Config config) throws IOException, InvalidAlgorithmParameterException {\n        if (!CryptFactory.isCipherExisted(config.getMethod())) {\n            throw new InvalidAlgorithmParameterException(config.getMethod());\n        }\n        _config = config;\n        _serverSocket = new ServerSocket(config.getLocalPort(), 128);\n        _executor = Executors.newCachedThreadPool();\n        _pipes = new ArrayList<>();\n\n        // print server info\n        logger.info(\"Shadowsocks-Java v\" + Constant.VERSION);\n        logger.info(config.getProxyType() + \" Proxy Server starts at port: \" + config.getLocalPort());\n    }\n\n    @Override\n    public void run() {\n        while (true) {\n            try {\n                Socket localSocket = _serverSocket.accept();\n                PipeSocket pipe = new PipeSocket(_executor, localSocket, _config);\n                _pipes.add(pipe);\n                _executor.execute(pipe);\n            } catch (IOException e) {\n                logger.warning(Util.getErrorMessage(e));\n            }\n        }\n    }\n\n    public void close() {\n        try {\n            for (PipeSocket p : _pipes) {\n                p.close();\n            }\n            _pipes.clear();\n            _serverSocket.close();\n        } catch (IOException e) {\n            logger.warning(Util.getErrorMessage(e));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/NioLocalServer.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network;\n\nimport com.stfl.Constant;\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.network.nio.*;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.ServerSocketChannel;\nimport java.nio.channels.SocketChannel;\nimport java.nio.channels.spi.SelectorProvider;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.*;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.logging.Logger;\n\n/**\n * Non-blocking local server for shadowsocks\n */\npublic class NioLocalServer extends SocketHandlerBase {\n    private Logger logger = Logger.getLogger(NioLocalServer.class.getName());\n\n    private ServerSocketChannel _serverChannel;\n    private RemoteSocketHandler _remoteSocketHandler;\n    private ExecutorService _executor;\n\n    public NioLocalServer(Config config) throws IOException, InvalidAlgorithmParameterException {\n        super(config);\n        _executor = Executors.newCachedThreadPool();\n\n        // init remote socket handler\n        _remoteSocketHandler = new RemoteSocketHandler(_config);\n        _executor.execute(_remoteSocketHandler);\n\n        // print server info\n        logger.info(\"Shadowsocks-Java v\" + Constant.VERSION);\n        logger.info(\"Cipher: \" + config.getMethod());\n        logger.info(config.getProxyType() + \" Proxy Server starts at port: \" + config.getLocalPort());\n    }\n\n    @Override\n    protected Selector initSelector() throws IOException {\n        Selector socketSelector = SelectorProvider.provider().openSelector();\n        _serverChannel = ServerSocketChannel.open();\n        _serverChannel.configureBlocking(false);\n        InetSocketAddress isa = new InetSocketAddress(_config.getLocalIpAddress(), _config.getLocalPort());\n        _serverChannel.socket().bind(isa);\n        _serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);\n\n        return socketSelector;\n    }\n\n    @Override\n    protected boolean processPendingRequest(ChangeRequest request) {\n        switch (request.type) {\n            case ChangeRequest.CHANGE_SOCKET_OP:\n                SelectionKey key = request.socket.keyFor(_selector);\n                if ((key != null) && key.isValid()) {\n                    key.interestOps(request.op);\n                } else {\n                    logger.warning(\"NioLocalServer::processPendingRequest (drop): \" + key + request.socket);\n                }\n                break;\n            case ChangeRequest.CLOSE_CHANNEL:\n                cleanUp(request.socket);\n                break;\n        }\n        return true;\n    }\n\n    @Override\n    protected void processSelect(SelectionKey key) {\n        // Handle event\n        try {\n            if (key.isAcceptable()) {\n                accept(key);\n            } else if (key.isReadable()) {\n                read(key);\n            } else if (key.isWritable()) {\n                write(key);\n            }\n        }\n        catch (IOException e) {\n            cleanUp((SocketChannel)key.channel());\n        }\n    }\n\n    private void accept(SelectionKey key) throws IOException {\n        // local socket established\n        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();\n        SocketChannel socketChannel = serverSocketChannel.accept();\n        socketChannel.configureBlocking(false);\n        socketChannel.register(_selector, SelectionKey.OP_READ);\n\n        // prepare local socket write queue\n        createWriteBuffer(socketChannel);\n\n        // create pipe between local and remote socket\n        PipeWorker pipe = _remoteSocketHandler.createPipe(this, socketChannel, _config.getRemoteIpAddress(), _config.getRemotePort());\n        _pipes.put(socketChannel, pipe);\n        _executor.execute(pipe);\n    }\n\n    private void read(SelectionKey key) throws IOException {\n        SocketChannel socketChannel = (SocketChannel) key.channel();\n        int readCount;\n        PipeWorker pipe = _pipes.get(socketChannel);\n        byte[] data;\n\n        if (pipe == null) {\n            // should not happen\n            cleanUp(socketChannel);\n            return;\n        }\n\n        _readBuffer.clear();\n        try {\n            readCount = socketChannel.read(_readBuffer);\n        } catch (IOException e) {\n            cleanUp(socketChannel);\n            return;\n        }\n\n        if (readCount == -1) {\n            cleanUp(socketChannel);\n            return;\n        }\n\n        data = _readBuffer.array();\n        pipe.processData(data, readCount, true);\n    }\n\n    private void write(SelectionKey key) throws IOException {\n        SocketChannel socketChannel = (SocketChannel) key.channel();\n\n        List queue = (List) _pendingData.get(socketChannel);\n        if (queue != null) {\n            synchronized (queue) {\n                // Write data\n                while (!queue.isEmpty()) {\n                    ByteBuffer buf = (ByteBuffer) queue.get(0);\n                    socketChannel.write(buf);\n                    if (buf.remaining() > 0) {\n                        break;\n                    }\n                    queue.remove(0);\n                }\n\n                if (queue.isEmpty()) {\n                    key.interestOps(SelectionKey.OP_READ);\n                }\n            }\n        }\n        else {\n            logger.warning(\"LocalSocket::write queue = null: \" + socketChannel);\n            return;\n        }\n    }\n\n    @Override\n    protected void cleanUp(SocketChannel socketChannel) {\n        //logger.warning(\"LocalSocket closed: \" + socketChannel);\n        super.cleanUp(socketChannel);\n\n        PipeWorker pipe = _pipes.get(socketChannel);\n        if (pipe != null) {\n            pipe.close();\n            _pipes.remove(socketChannel);\n            logger.fine(\"LocalSocket closed: \" + pipe.socketInfo);\n        }\n        else {\n            logger.fine(\"LocalSocket closed (NULL): \" + socketChannel);\n        }\n\n    }\n\n    @Override\n    public void close() {\n        super.close();\n        _executor.shutdownNow();\n\n        try {\n            _serverChannel.close();\n            _remoteSocketHandler.close();\n        } catch (IOException e) {\n            logger.warning(Util.getErrorMessage(e));\n        }\n        logger.info(\"Server closed.\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/io/PipeSocket.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.io;\n\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.Constant;\nimport com.stfl.network.proxy.IProxy;\nimport com.stfl.network.proxy.ProxyFactory;\nimport com.stfl.ss.CryptFactory;\nimport com.stfl.ss.ICrypt;\n\nimport java.io.*;\nimport java.net.Socket;\nimport java.net.SocketTimeoutException;\nimport java.util.List;\nimport java.util.concurrent.Executor;\nimport java.util.logging.Logger;\n\n/**\n * Pipe local and remote sockets while server is running under blocking mode.\n */\npublic class PipeSocket implements Runnable {\n    private Logger logger = Logger.getLogger(PipeSocket.class.getName());\n\n    private final int TIMEOUT = 10000; // 10s\n    private ByteArrayOutputStream _remoteOutStream;\n    private ByteArrayOutputStream _localOutStream;\n    private Socket _remote;\n    private Socket _local;\n    private IProxy _proxy;\n    private ICrypt _crypt;\n    private boolean _isClosed;\n    private Executor _executor;\n    private Config _config;\n\n    public PipeSocket(Executor executor, Socket socket, Config config) throws IOException {\n        _executor = executor;\n        _local = socket;\n        _local.setSoTimeout(TIMEOUT);\n        _config = config;\n        _crypt = CryptFactory.get(_config.getMethod(), _config.getPassword());\n        _proxy = ProxyFactory.get(_config.getProxyType());\n        _remoteOutStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE);\n        _localOutStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE);\n    }\n\n    @Override\n    public void run() {\n        try {\n            _remote = initRemote(_config);\n            _remote.setSoTimeout(TIMEOUT);\n        } catch (IOException e) {\n            close();\n            logger.warning(Util.getErrorMessage(e));\n            return;\n        }\n\n        _executor.execute(getLocalWorker());\n        _executor.execute(getRemoteWorker());\n    }\n\n    private Socket initRemote(Config config) throws IOException {\n        return new Socket(config.getRemoteIpAddress(), config.getRemotePort());\n    }\n\n    private Runnable getLocalWorker() {\n        return new Runnable() {\n            @Override\n            public void run() {\n                BufferedInputStream stream;\n                byte[] dataBuffer = new byte[Constant.BUFFER_SIZE];\n                byte[] buffer;\n                int readCount;\n                List<byte[]> sendData = null;\n\n                // prepare local stream\n                try {\n                    stream = new BufferedInputStream(_local.getInputStream());\n                } catch (IOException e) {\n                    logger.info(e.toString());\n                    return;\n                }\n\n                // start to process data from local socket\n                while (true) {\n                    try {\n                         // read data\n                        readCount = stream.read(dataBuffer);\n                        if (readCount == -1) {\n                            throw new IOException(\"Local socket closed (Read)!\");\n                        }\n\n                        // initialize proxy\n                        if (!_proxy.isReady()) {\n                            byte[] temp;\n                            buffer = new byte[readCount];\n\n                            // dup dataBuffer to use in later\n                            System.arraycopy(dataBuffer, 0, buffer, 0, readCount);\n\n                            temp = _proxy.getResponse(buffer);\n                            if ((temp != null) && (!_sendLocal(temp, temp.length))) {\n                                throw new IOException(\"Local socket closed (proxy-Write)!\");\n                            }\n                            // packet for remote socket\n                            sendData = _proxy.getRemoteResponse(buffer);\n                            if (sendData == null) {\n                                continue;\n                            }\n                            logger.info(\"Connected to: \" + Util.getRequestedHostInfo(sendData.get(0)));\n                        }\n                        else {\n                            sendData.clear();\n                            sendData.add(dataBuffer);\n                        }\n\n                        for (byte[] bytes : sendData) {\n                            // send data to remote socket\n                            if (!sendRemote(bytes, bytes.length)) {\n                                throw new IOException(\"Remote socket closed (Write)!\");\n                            }\n                        }\n                    } catch (SocketTimeoutException e) {\n                        continue;\n                    } catch (IOException e) {\n                        logger.fine(e.toString());\n                        break;\n                    }\n                }\n                close();\n                logger.fine(String.format(\"localWorker exit, Local=%s, Remote=%s\", _local, _remote));\n            }\n        };\n    }\n\n    private Runnable getRemoteWorker() {\n        return new Runnable() {\n            @Override\n            public void run() {\n                BufferedInputStream stream;\n                int readCount;\n                byte[] dataBuffer = new byte[4096];\n\n                // prepare remote stream\n                try {\n                    //stream = _remote.getInputStream();\n                    stream = new BufferedInputStream (_remote.getInputStream());\n                } catch (IOException e) {\n                    logger.info(e.toString());\n                    return;\n                }\n\n                // start to process data from remote socket\n                while (true) {\n                    try {\n                        readCount = stream.read(dataBuffer);\n                        if (readCount == -1) {\n                            throw new IOException(\"Remote socket closed (Read)!\");\n                        }\n\n                        // send data to local socket\n                        if (!sendLocal(dataBuffer, readCount)) {\n                            throw new IOException(\"Local socket closed (Write)!\");\n                        }\n                    } catch (SocketTimeoutException e) {\n                        continue;\n                    } catch (IOException e) {\n                        logger.fine(e.toString());\n                        break;\n                    }\n\n                }\n                close();\n                logger.fine(String.format(\"remoteWorker exit, Local=%s, Remote=%s\", _local, _remote));\n            }\n        };\n    }\n\n    public void close() {\n        if (_isClosed) {\n            return;\n        }\n        _isClosed = true;\n\n        try {\n            _local.shutdownInput();\n            _local.shutdownOutput();\n            _local.close();\n        } catch (IOException e) {\n            logger.fine(\"PipeSocket failed to close local socket (I/O exception)!\");\n        }\n        try {\n            if (_remote != null) {\n                _remote.shutdownInput();\n                _remote.shutdownOutput();\n                _remote.close();\n            }\n        } catch (IOException e) {\n            logger.fine(\"PipeSocket failed to close remote socket (I/O exception)!\");\n        }\n    }\n\n    private boolean sendRemote(byte[] data, int length) {\n        _crypt.encrypt(data, length, _remoteOutStream);\n        byte[] sendData = _remoteOutStream.toByteArray();\n\n        return _sendRemote(sendData, sendData.length);\n    }\n\n    private boolean _sendRemote(byte[] data, int length) {\n        try {\n            if (length > 0) {\n                OutputStream outStream = _remote.getOutputStream();\n                outStream.write(data, 0, length);\n            }\n            else {\n                logger.info(\"Nothing to sendRemote!\\n\");\n            }\n        } catch (IOException e) {\n            logger.info(Util.getErrorMessage(e));\n            return false;\n        }\n\n        return true;\n    }\n\n    private boolean sendLocal(byte[] data, int length) {\n        _crypt.decrypt(data, length, _localOutStream);\n        byte[] sendData = _localOutStream.toByteArray();\n\n        return _sendLocal(sendData, sendData.length);\n    }\n\n    private boolean _sendLocal(byte[] data, int length) {\n        try {\n            OutputStream outStream = _local.getOutputStream();\n            outStream.write(data, 0, length);\n        } catch (IOException e) {\n            logger.info(Util.getErrorMessage(e));\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/ChangeRequest.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.nio;\n\nimport java.nio.channels.SocketChannel;\n\n/**\n * Request for nio socket handler\n */\npublic class ChangeRequest {\n    public static final int REGISTER_CHANNEL = 1;\n    public static final int CHANGE_SOCKET_OP = 2;\n    public static final int CLOSE_CHANNEL = 3;\n\n    public SocketChannel socket;\n    public int type;\n    public int op;\n\n    public ChangeRequest(SocketChannel socket, int type, int op) {\n        this.socket = socket;\n        this.type = type;\n        this.op = op;\n    }\n\n    public ChangeRequest(SocketChannel socket, int type) {\n        this(socket, type, 0);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/ISocketHandler.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.nio;\n\n/**\n * Interface of socket handler\n */\npublic interface ISocketHandler {\n    void send(ChangeRequest request, byte[] data);\n    void send(ChangeRequest request);\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/PipeEvent.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.nio;\n\n/**\n * pipe event for pipe worker\n */\npublic class PipeEvent {\n    public byte[] data;\n    public boolean isEncrypted;\n\n    public PipeEvent() {}\n\n    public PipeEvent(byte[] data, boolean isEncrypted) {\n        this.data = data;\n        this.isEncrypted = isEncrypted;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/PipeWorker.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.nio;\n\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.Constant;\nimport com.stfl.network.proxy.IProxy;\nimport com.stfl.network.proxy.ProxyFactory;\nimport com.stfl.ss.CryptFactory;\nimport com.stfl.ss.ICrypt;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.SocketChannel;\nimport java.util.List;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.logging.Logger;\n\n\npublic class PipeWorker implements Runnable {\n    private Logger logger = Logger.getLogger(PipeWorker.class.getName());\n    private SocketChannel _localChannel;\n    private SocketChannel _remoteChannel;\n    private ISocketHandler _localSocketHandler;\n    private ISocketHandler _remoteSocketHandler;\n    private IProxy _proxy;\n    private ICrypt _crypt;\n    public String socketInfo;\n    private ByteArrayOutputStream _outStream;\n    private BlockingQueue _processQueue;\n    private volatile boolean requestedClose;\n\n    public PipeWorker(ISocketHandler localHandler, SocketChannel localChannel, ISocketHandler remoteHandler, SocketChannel remoteChannel, Config config) {\n        _localChannel = localChannel;\n        _remoteChannel = remoteChannel;\n        _localSocketHandler = localHandler;\n        _remoteSocketHandler = remoteHandler;\n        _crypt = CryptFactory.get(config.getMethod(), config.getPassword());\n        _proxy = ProxyFactory.get(config.getProxyType());\n        _outStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE);\n        _processQueue = new LinkedBlockingQueue();\n        requestedClose = false;\n        socketInfo = String.format(\"Local: %s, Remote: %s\", localChannel, remoteChannel);\n    }\n\n    public void close() {\n        requestedClose = true;\n        processData(null, 0, false);\n    }\n\n    public void forceClose() {\n        logger.fine(\"PipeWorker::forceClose \" + socketInfo);\n\n        // close socket now!\n        try {\n            if (_localChannel.isOpen()) {\n                _localChannel.close();\n            }\n            if (_remoteChannel.isOpen()) {\n                _remoteChannel.close();\n            }\n        } catch (IOException e) {\n            logger.fine(\"PipeWorker::forceClose> \" + e.toString());\n        }\n\n        // follow the standard close steps\n        close();\n    }\n\n    public void processData(byte[] data, int count, boolean isEncrypted) {\n        if (data != null) {\n            byte[] dataCopy = new byte[count];\n            System.arraycopy(data, 0, dataCopy, 0, count);\n            _processQueue.add(new PipeEvent(dataCopy, isEncrypted));\n        }\n        else {\n            _processQueue.add(new PipeEvent());\n        }\n    }\n\n    @Override\n    public void run() {\n        PipeEvent event;\n        ISocketHandler socketHandler;\n        SocketChannel channel;\n        List<byte[]> sendData = null;\n\n        while(true) {\n            // make sure all the requests in the queue are processed\n            if (_processQueue.isEmpty() && requestedClose) {\n                logger.fine(\"PipeWorker closed (\"+  _processQueue.size() + \"): \" + this.socketInfo);\n                if (_localChannel.isOpen()) {\n                    _localSocketHandler.send(new ChangeRequest(_localChannel, ChangeRequest.CLOSE_CHANNEL));\n                }\n                if (_remoteChannel.isOpen()) {\n                    _remoteSocketHandler.send(new ChangeRequest(_remoteChannel, ChangeRequest.CLOSE_CHANNEL));\n                }\n                break;\n            }\n\n            try {\n                event = (PipeEvent)_processQueue.take();\n\n                // if event data is null, it means this is a wake-up call\n                // to check if any other thread is requested to close sockets\n                if (event.data == null) {\n                    continue;\n                }\n\n                // process proxy packet if needed\n                if (!_proxy.isReady()) {\n                    // packet for local socket\n                    byte[] temp = _proxy.getResponse(event.data);\n                    if (temp != null) {\n                        _localSocketHandler.send(new ChangeRequest(_localChannel, ChangeRequest.CHANGE_SOCKET_OP,\n                                SelectionKey.OP_WRITE), temp);\n                    }\n                    // packet for remote socket (ss payload + request)\n                    sendData = _proxy.getRemoteResponse(event.data);\n                    if (sendData == null) {\n                        continue;\n                    }\n                    // index 0 is always ss payload\n                    logger.info(\"Connected to: \" + Util.getRequestedHostInfo(sendData.get(0)));\n                    //logger.info(\"Test: \" + Util.bytesToString(temp, 0, temp.length));\n                }\n                else {\n                    sendData.clear();\n                    sendData.add(event.data);\n                }\n\n                for (byte[] bytes : sendData) {\n                    // empty stream for new data\n                    _outStream.reset();\n\n                    if (event.isEncrypted) {\n                        _crypt.encrypt(bytes, _outStream);\n                        channel = _remoteChannel;\n                        socketHandler = _remoteSocketHandler;\n                    } else {\n                        _crypt.decrypt(bytes, _outStream);\n                        channel = _localChannel;\n                        socketHandler = _localSocketHandler;\n                    }\n\n                    // data is ready to send to socket\n                    ChangeRequest request = new ChangeRequest(channel, ChangeRequest.CHANGE_SOCKET_OP, SelectionKey.OP_WRITE);\n                    socketHandler.send(request, _outStream.toByteArray());\n                }\n            } catch (InterruptedException e) {\n                logger.fine(Util.getErrorMessage(e));\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/RemoteSocketHandler.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.nio;\n\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ClosedChannelException;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SocketChannel;\nimport java.nio.channels.spi.SelectorProvider;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.List;\nimport java.util.logging.Logger;\n\n/**\n * Handler for processing all IO event for remote sockets\n */\npublic class RemoteSocketHandler extends SocketHandlerBase {\n    private Logger logger = Logger.getLogger(RemoteSocketHandler.class.getName());\n\n    public RemoteSocketHandler(Config config) throws IOException, InvalidAlgorithmParameterException {\n        super(config);\n    }\n\n    @Override\n    protected Selector initSelector() throws IOException {\n        return SelectorProvider.provider().openSelector();\n    }\n\n    @Override\n    protected boolean processPendingRequest(ChangeRequest request) {\n        if ((request.type != ChangeRequest.REGISTER_CHANNEL) && request.socket.isConnectionPending()) {\n            return false;\n        }\n\n        SelectionKey key;\n        switch (request.type) {\n            case ChangeRequest.CHANGE_SOCKET_OP:\n                key = request.socket.keyFor(_selector);\n                if ((key != null) && key.isValid()) {\n                    key.interestOps(request.op);\n                } else {\n                    logger.warning(\"RemoteSocketHandler::processPendingRequest (drop): \" + key + request.socket);\n                }\n                break;\n            case ChangeRequest.REGISTER_CHANNEL:\n                try {\n                    request.socket.register(_selector, request.op);\n                } catch (ClosedChannelException e) {\n                    // socket get closed by remote\n                    logger.warning(e.toString());\n                    cleanUp(request.socket);\n                }\n                break;\n            case ChangeRequest.CLOSE_CHANNEL:\n                cleanUp(request.socket);\n                break;\n        }\n\n        return true;\n    }\n\n    @Override\n    protected void processSelect(SelectionKey key) {\n        try {\n            if (key.isConnectable()) {\n                finishConnection(key);\n            } else if (key.isReadable()) {\n                read(key);\n            } else if (key.isWritable()) {\n                write(key);\n            }\n        } catch (IOException e) {\n            cleanUp((SocketChannel) key.channel());\n        }\n    }\n\n    public PipeWorker createPipe(ISocketHandler localHandler, SocketChannel localChannel, String ipAddress, int port) throws IOException {\n        // prepare remote socket\n        SocketChannel socketChannel = SocketChannel.open();\n        socketChannel.configureBlocking(false);\n        socketChannel.connect(new InetSocketAddress(ipAddress, port));\n\n        // create write buffer for specified socket\n        createWriteBuffer(socketChannel);\n\n        // create pipe worker for handling encrypt and decrypt\n        PipeWorker pipe = new PipeWorker(localHandler, localChannel, this, socketChannel, _config);\n\n        // setup pipe info\n        //pipe.setRemoteChannel(socketChannel);\n        _pipes.put(socketChannel, pipe);\n\n        synchronized(_pendingRequest) {\n            _pendingRequest.add(new ChangeRequest(socketChannel, ChangeRequest.REGISTER_CHANNEL, SelectionKey.OP_CONNECT));\n        }\n\n        return pipe;\n    }\n\n    private void read(SelectionKey key) throws IOException {\n        SocketChannel socketChannel = (SocketChannel) key.channel();\n        PipeWorker pipe = _pipes.get(socketChannel);\n        if (pipe == null) {\n            // should not happen\n            cleanUp(socketChannel);\n            return;\n        }\n\n        // clear read buffer for new data\n        _readBuffer.clear();\n\n        // read data\n        int readCount;\n        try {\n            readCount = socketChannel.read(_readBuffer);\n        } catch (IOException e) {\n            // remote socket closed\n            cleanUp(socketChannel);\n\n            return;\n        }\n\n        if (readCount == -1) {\n            cleanUp(socketChannel);\n            return;\n        }\n\n        // Handle the response\n        pipe.processData(_readBuffer.array(), readCount, false);\n    }\n\n    private void write(SelectionKey key) throws IOException {\n        SocketChannel socketChannel = (SocketChannel) key.channel();\n\n        List queue = (List) _pendingData.get(socketChannel);\n        if (queue != null) {\n            synchronized (queue) {\n                // write data to socket\n                while (!queue.isEmpty()) {\n                    ByteBuffer buf = (ByteBuffer) queue.get(0);\n                    socketChannel.write(buf);\n                    if (buf.remaining() > 0) {\n                        break;\n                    }\n                    queue.remove(0);\n                }\n\n                if (queue.isEmpty()) {\n                    key.interestOps(SelectionKey.OP_READ);\n                }\n            }\n        }\n        else {\n            logger.warning(\"RemoteSocket::write queue = null: \" + socketChannel);\n            return;\n        }\n    }\n\n    private void finishConnection(SelectionKey key) throws IOException {\n        SocketChannel socketChannel = (SocketChannel) key.channel();\n\n        try {\n            socketChannel.finishConnect();\n        } catch (IOException e) {\n            logger.warning(\"RemoteSocketHandler::finishConnection I/O exception: \" + e.toString());\n            cleanUp(socketChannel);\n            return;\n        }\n\n        key.interestOps(SelectionKey.OP_WRITE);\n    }\n\n    @Override\n    protected void cleanUp(SocketChannel socketChannel) {\n        super.cleanUp(socketChannel);\n\n        PipeWorker pipe = _pipes.get(socketChannel);\n        if (pipe != null) {\n            pipe.close();\n            _pipes.remove(socketChannel);\n            logger.fine(\"RemoteSocket closed: \" + pipe.socketInfo);\n        }\n        else {\n            logger.fine(\"RemoteSocket closed (NULL): \" + socketChannel);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/nio/SocketHandlerBase.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.nio;\n\nimport com.stfl.misc.Config;\nimport com.stfl.misc.Util;\nimport com.stfl.Constant;\nimport com.stfl.network.IServer;\nimport com.stfl.ss.CryptFactory;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ClosedSelectorException;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SocketChannel;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.logging.Logger;\n\n/**\n * Base class of socket handler for processing all IO event for sockets\n */\npublic abstract class SocketHandlerBase implements IServer, ISocketHandler {\n    private Logger logger = Logger.getLogger(SocketHandlerBase.class.getName());\n    protected Selector _selector;\n    protected Config _config;\n    protected final List _pendingRequest = new LinkedList();\n    protected final ConcurrentHashMap _pendingData = new ConcurrentHashMap();\n    protected ConcurrentMap<SocketChannel, PipeWorker> _pipes = new ConcurrentHashMap<>();\n    protected ByteBuffer _readBuffer = ByteBuffer.allocate(Constant.BUFFER_SIZE);\n\n    protected abstract Selector initSelector() throws IOException;\n    protected abstract boolean processPendingRequest(ChangeRequest request);\n    protected abstract void processSelect(SelectionKey key);\n\n\n    public SocketHandlerBase(Config config) throws IOException, InvalidAlgorithmParameterException {\n        if (!CryptFactory.isCipherExisted(config.getMethod())) {\n            throw new InvalidAlgorithmParameterException(config.getMethod());\n        }\n        _config = config;\n        _selector = initSelector();\n    }\n\n    @Override\n    public void run() {\n        while (true) {\n            try {\n                synchronized (_pendingRequest) {\n                    Iterator changes = _pendingRequest.iterator();\n                    while (changes.hasNext()) {\n                        ChangeRequest change = (ChangeRequest) changes.next();\n                        if (!processPendingRequest(change))\n                            break;\n                        changes.remove();\n                    }\n                }\n\n                // wait events from selected channels\n                _selector.select();\n\n                Iterator selectedKeys = _selector.selectedKeys().iterator();\n                while (selectedKeys.hasNext()) {\n                    SelectionKey key = (SelectionKey) selectedKeys.next();\n                    selectedKeys.remove();\n\n                    if (!key.isValid()) {\n                        continue;\n                    }\n\n                    processSelect(key);\n                }\n            }\n            catch (ClosedSelectorException e) {\n                break;\n            }\n            catch (Exception e) {\n                logger.warning(Util.getErrorMessage(e));\n            }\n        }\n        logger.fine(this.getClass().getName() + \" Closed.\");\n    }\n\n    protected void createWriteBuffer(SocketChannel socketChannel) {\n        List queue = new ArrayList();\n        Object put;\n        put = _pendingData.putIfAbsent(socketChannel, queue);\n        if (put != null) {\n            logger.severe(\"Dup write buffer creation: \" + socketChannel);\n        }\n    }\n\n    protected void cleanUp(SocketChannel socketChannel) {\n        try {\n            socketChannel.close();\n        } catch (IOException e) {\n            logger.info(Util.getErrorMessage(e));\n        }\n        SelectionKey key = socketChannel.keyFor(_selector);\n        if (key != null) {\n            key.cancel();\n        }\n\n        if (_pendingData.containsKey(socketChannel)) {\n            _pendingData.remove(socketChannel);\n        }\n    }\n\n    @Override\n    public void send(ChangeRequest request, byte[] data) {\n        switch (request.type) {\n            case ChangeRequest.CHANGE_SOCKET_OP:\n                List queue = (List) _pendingData.get(request.socket);\n                if (queue != null) {\n                    synchronized (queue) {\n                        // in general case, the write queue is always existed, unless, the socket has been shutdown\n                        queue.add(ByteBuffer.wrap(data));\n                    }\n                }\n                else {\n                    logger.warning(Util.getErrorMessage(new Throwable(\"Socket is closed! dropping this request\")));\n                }\n                break;\n        }\n\n        synchronized (_pendingRequest) {\n            _pendingRequest.add(request);\n        }\n\n        _selector.wakeup();\n    }\n\n    @Override\n    public void send(ChangeRequest request) {\n        send(request, null);\n    }\n\n    public void close() {\n        for (PipeWorker p : _pipes.values()) {\n            p.forceClose();\n        }\n        _pipes.clear();\n        try {\n            _selector.close();\n        } catch (IOException e) {\n            logger.warning(Util.getErrorMessage(e));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/AutoProxy.java",
    "content": "package com.stfl.network.proxy;\n\nimport com.stfl.misc.Reflection;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.logging.Logger;\n\n/**\n * Decides proxy type automatically (as soon as socket established).\n * Proxy class for Socks5 and Http\n */\npublic class AutoProxy implements IProxy {\n    private Logger logger = Logger.getLogger(AutoProxy.class.getName());\n    private IProxy _proxy;\n    private volatile boolean isInitialized;\n\n    public AutoProxy() {\n        isInitialized = false;\n    }\n\n    @Override\n    public boolean isReady() {\n        return (isInitialized && _proxy.isReady());\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.AUTO;\n    }\n\n    @Override\n    public byte[] getResponse(byte[] data) {\n        if (!isInitialized) {\n            init(data);\n        }\n        return _proxy.getResponse(data);\n    }\n\n    @Override\n    public List<byte[]> getRemoteResponse(byte[] data) {\n        if (!isInitialized) {\n            init(data);\n        }\n        return _proxy.getRemoteResponse(data);\n    }\n\n    @Override\n    public boolean isMine(byte[] data) {\n        if (!isInitialized) {\n            init(data);\n        }\n        return _proxy.isMine(data);\n    }\n\n    private void init(byte[] data) {\n        Object obj;\n        IProxy proxy;\n        for (Map.Entry<IProxy.TYPE, String> entry : ProxyFactory.proxies.entrySet()) {\n            if (entry.getKey() == this.getType()) continue;\n\n            obj = Reflection.get(entry.getValue());\n            proxy = (IProxy)obj;\n            if (proxy.isMine(data)) {\n                logger.fine(\"ProxyType (Auto): \" + proxy.getType());\n                _proxy = proxy;\n                isInitialized = true;\n                return;\n            }\n        }\n        logger.severe(\"Unable to determine proxy type!\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/HttpProxy.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.proxy;\n\nimport com.stfl.Constant;\nimport com.stfl.misc.Util;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Provide local HTTP proxy statue and required response\n */\npublic class HttpProxy implements IProxy {\n    private static final String[] HTTP_METHODS =\n            new String[] {\"OPTIONS\", \"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"TRACE\", \"CONNECT\"};\n\n    private Logger logger = Logger.getLogger(HttpProxy.class.getName());\n    private boolean _isReady;\n    private boolean _isHttpConnect;\n    private Map<String, String> methodCache;\n\n    public HttpProxy() {\n        _isReady = false;\n        _isHttpConnect = false;\n    }\n\n    public TYPE getType() {\n        return TYPE.HTTP;\n    }\n\n    public boolean isReady() {\n        return _isReady;\n    }\n\n    public byte[] getResponse(byte[] data) {\n        if (methodCache == null) {\n            methodCache = getHttpMethod(data);\n        }\n        setHttpMethod(methodCache);\n\n        if (_isHttpConnect)\n            return String.format(\"HTTP/1.0 200\\r\\nProxy-agent: %s/%s\\r\\n\\r\\n\",\n                    Constant.PROG_NAME, Constant.VERSION).getBytes();\n\n        return null;\n    }\n\n    public List<byte[]> getRemoteResponse(byte[] data) {\n        List<byte[]> respData = new ArrayList<>(2);\n        String host;\n        int port = 80; // HTTP port\n        if (methodCache == null) {\n            methodCache = getHttpMethod(data);\n        }\n        String[] hostInfo = methodCache.get(\"host\").split(\":\");\n\n        // get hostname and port\n        host = hostInfo[0];\n        if (hostInfo.length > 1) {\n            port = Integer.parseInt(hostInfo[1]);\n        }\n\n        byte[] ssHeader = Util.composeSSHeader(host, port);\n        respData.add(ssHeader);\n        if (!_isHttpConnect) {\n            byte[] httpHeader = reconstructHttpHeader(methodCache, data);\n            respData.add(httpHeader);\n        }\n\n        _isReady = true;\n        return respData;\n    }\n\n    @Override\n    public boolean isMine(byte[] data) {\n        if (methodCache == null) {\n            methodCache = getHttpMethod(data);\n        }\n        String method = methodCache.get(\"method\");\n\n        if (method != null) {\n            for (String s : HTTP_METHODS) {\n                if (s.equals(method)) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    private Map<String, String> getHttpMethod(byte[] data) {\n        String httpRequest = Util.bytesToString(data, 0, data.length);\n        String[] httpHeaders = httpRequest.split(\"\\\\r?\\\\n\");\n        boolean isHostFound = true;\n        //Pattern pattern = Pattern.compile(\"^([a-zA-Z]*) [hHtTpP]{0,4}[:\\\\/]{0,3}(\\\\S[^/ ]*)\");\n        Pattern pattern = Pattern.compile(\"^([a-zA-Z]*) [htps]{0,4}[:/]{0,3}(\\\\S[^/]*)(\\\\S*) (\\\\S*)\");\n        Map<String, String> header = new HashMap<>();\n        if (httpHeaders.length > 0) {\n            logger.fine(\"HTTP Header: \" + httpHeaders[0]);\n            Matcher matcher = pattern.matcher(httpHeaders[0]);\n            if (matcher.find()) {\n                header.put(\"method\", matcher.group(1));\n                if (matcher.group(2).startsWith(\"/\")) {\n                    header.put(\"url\", \"/\");\n                    isHostFound = false;\n                }\n                else {\n                    header.put(\"host\", matcher.group(2));\n                    header.put(\"url\", matcher.group(3));\n                }\n                header.put(\"version\", matcher.group(4));\n            }\n        }\n\n        if (!isHostFound) {\n            for (String line : httpHeaders) {\n                if (line.toLowerCase().contains(\"host\")) {\n                    String info = line.split(\":\")[1].trim();\n                    header.put(\"host\", info);\n                    break;\n                }\n            }\n        }\n        return header;\n    }\n\n    private byte[] reconstructHttpHeader(Map<String, String> method, byte[] data) {\n        String httpRequest = Util.bytesToString(data, 0, data.length);\n        String[] httpHeaders = httpRequest.split(\"\\\\r?\\\\n\");\n        StringBuilder sb = new StringBuilder();\n        boolean isFirstLine = true;\n\n        //logger.info(\"original HttpHeader:\" + httpRequest);\n        for (String line : httpHeaders) {\n            if (isFirstLine && _isHttpConnect) {\n                sb.append(method.get(\"method\"));\n                sb.append(\" \");\n                sb.append(method.get(\"host\"));\n                sb.append(\" \");\n                sb.append(method.get(\"version\"));\n                sb.append(\"\\r\\n\");\n                sb.append(\"User-Agent: test/0.1\\r\\n\");\n                break;\n            }\n            else if (isFirstLine) {\n                sb.append(method.get(\"method\"));\n                sb.append(\" \");\n                sb.append(method.get(\"url\"));\n                sb.append(\" \");\n                sb.append(method.get(\"version\"));\n                isFirstLine = false;\n            }\n            else if (line.toLowerCase().contains(\"cache-control\")) {\n                sb.append(\"Pragma: no-cache\\r\\n\");\n                sb.append(\"Cache-Control: no-cache\");\n            }\n            else if (line.toLowerCase().contains(\"proxy-connection\")) {\n                //Proxy-Connection\n                String[] fields = line.split(\":\");\n                sb.append(\"Connection: \");\n                sb.append(fields[1].trim());\n            }\n            else if (line.toLowerCase().contains(\"if-none-match\")) {\n                continue;\n            }\n            else if (line.toLowerCase().contains(\"if-modified-since\")) {\n                continue;\n            }\n            else {\n                sb.append(line);\n            }\n            sb.append(\"\\r\\n\");\n        }\n\n        sb.append(\"\\r\\n\");\n        //logger.info(\"reconstructHttpHeader:\" + sb.toString());\n        return sb.toString().getBytes();\n    }\n\n    private void setHttpMethod(Map<String, String> header) {\n        String method = header.get(\"method\");\n\n        if (method != null) {\n            if (method.toUpperCase().equals(\"CONNECT\")) {\n                _isHttpConnect = true;\n            }\n            else {\n                _isHttpConnect = false;\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/IProxy.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.proxy;\n\nimport java.util.List;\n\npublic interface IProxy {\n    enum TYPE {SOCKS5, HTTP, AUTO}\n\n    boolean isReady();\n    TYPE getType();\n    byte[] getResponse(byte[] data);\n    List<byte[]> getRemoteResponse(byte[] data);\n    boolean isMine(byte[] data);\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/ProxyFactory.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.proxy;\n\nimport com.stfl.misc.Reflection;\nimport java.util.*;\nimport java.util.logging.Logger;\n\n/**\n * Proxy factory\n */\npublic class ProxyFactory {\n    public static final Map<IProxy.TYPE, String> proxies = new HashMap<IProxy.TYPE, String>() {{\n        put(IProxy.TYPE.HTTP, HttpProxy.class.getName());\n        put(IProxy.TYPE.SOCKS5, Socks5Proxy.class.getName());\n        put(IProxy.TYPE.AUTO, AutoProxy.class.getName());\n    }};\n    private static Logger logger = Logger.getLogger(ProxyFactory.class.getName());\n\n    public static boolean isProxyTypeExisted(String name) {\n        IProxy.TYPE type = IProxy.TYPE.valueOf(name);\n        return (proxies.get(type) != null);\n    }\n\n    public static IProxy get(IProxy.TYPE type) {\n        try {\n            Object obj = Reflection.get(proxies.get(type));\n            return (IProxy)obj;\n\n        } catch (Exception e) {\n            logger.info(com.stfl.misc.Util.getErrorMessage(e));\n        }\n\n        return null;\n    }\n\n    public static List<IProxy.TYPE> getSupportedProxyTypes() {\n        List sortedKeys = new ArrayList<>(proxies.keySet());\n        Collections.sort(sortedKeys);\n        return sortedKeys;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/network/proxy/Socks5Proxy.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.network.proxy;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Logger;\n\n/**\n * Provide local socks5 statue and required response\n */\npublic class Socks5Proxy implements IProxy {\n    public final static int ATYP_IP_V4 = 0x1;\n    public final static int ATYP_DOMAIN_NAME = 0x3;\n    public final static int ATYP_IP_V6 = 0x4;\n\n    private Logger logger = Logger.getLogger(Socks5Proxy.class.getName());\n    private enum STAGE {SOCK5_HELLO, SOCKS_ACK, SOCKS_READY}\n    private STAGE _stage;\n\n    public Socks5Proxy() {\n        _stage = STAGE.SOCK5_HELLO;\n    }\n\n    public TYPE getType() {\n        return TYPE.SOCKS5;\n    }\n\n    public boolean isReady() {\n        return (_stage == STAGE.SOCKS_READY);\n    }\n\n    public byte[] getResponse(byte[] data) {\n        byte[] respData = null;\n\n        switch (_stage) {\n            case SOCK5_HELLO:\n                if (isMine(data)) {\n                    respData = new byte[] {5, 0};\n                }\n                else {\n                    respData = new byte[] {0, 91};\n                }\n                _stage = STAGE.SOCKS_ACK;\n                break;\n            case SOCKS_ACK:\n                respData = new byte[] {5, 0, 0, 1, 0, 0, 0, 0, 0, 0};\n                _stage = STAGE.SOCKS_READY;\n                break;\n            default:\n                // TODO: exception\n                break;\n\n        }\n\n        return respData;\n    }\n\n    public List<byte[]> getRemoteResponse(byte[] data) {\n        List<byte[]> respData = null;\n        int dataLength = data.length;\n\n        /*\n        There are two stage of establish Sock5:\n            1. HELLO (3 bytes)\n            2. ACK (3 bytes + dst info)\n        as Client sending ACK, it might contain dst info.\n        In this case, server needs to send back ACK response to client and start the remote socket right away,\n        otherwise, client will wait until timeout.\n         */\n        if (_stage == STAGE.SOCKS_READY) {\n            respData = new ArrayList<>(1);\n            // remove socks5 header (partial)\n            if (dataLength > 3) {\n                dataLength -= 3;\n                byte[] temp = new byte[dataLength];\n                System.arraycopy(data, 3, temp, 0, dataLength);\n                respData.add(temp);\n            }\n        }\n\n        return respData;\n    }\n\n    @Override\n    public boolean isMine(byte[] data) {\n        if (data[0] == 0x5) {\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/AesCrypt.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport org.bouncycastle.crypto.StreamBlockCipher;\nimport org.bouncycastle.crypto.engines.AESEngine;\nimport org.bouncycastle.crypto.engines.AESFastEngine;\nimport org.bouncycastle.crypto.modes.CFBBlockCipher;\nimport org.bouncycastle.crypto.modes.OFBBlockCipher;\n\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.io.ByteArrayOutputStream;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * AES Crypt implementation\n */\npublic class AesCrypt extends CryptBase {\n\n    public final static String CIPHER_AES_128_CFB = \"aes-128-cfb\";\n    public final static String CIPHER_AES_192_CFB = \"aes-192-cfb\";\n    public final static String CIPHER_AES_256_CFB = \"aes-256-cfb\";\n    public final static String CIPHER_AES_128_OFB = \"aes-128-ofb\";\n    public final static String CIPHER_AES_192_OFB = \"aes-192-ofb\";\n    public final static String CIPHER_AES_256_OFB = \"aes-256-ofb\";\n\n    public static Map<String, String> getCiphers() {\n        Map<String, String> ciphers = new HashMap<>();\n        ciphers.put(CIPHER_AES_128_CFB, AesCrypt.class.getName());\n        ciphers.put(CIPHER_AES_192_CFB, AesCrypt.class.getName());\n        ciphers.put(CIPHER_AES_256_CFB, AesCrypt.class.getName());\n        ciphers.put(CIPHER_AES_128_OFB, AesCrypt.class.getName());\n        ciphers.put(CIPHER_AES_192_OFB, AesCrypt.class.getName());\n        ciphers.put(CIPHER_AES_256_OFB, AesCrypt.class.getName());\n\n        return ciphers;\n    }\n\n    public AesCrypt(String name, String password) {\n        super(name, password);\n    }\n\n    @Override\n    public int getKeyLength() {\n        if(_name.equals(CIPHER_AES_128_CFB) || _name.equals(CIPHER_AES_128_OFB)) {\n            return 16;\n        }\n        else if (_name.equals(CIPHER_AES_192_CFB) || _name.equals(CIPHER_AES_192_OFB)) {\n            return 24;\n        }\n        else if (_name.equals(CIPHER_AES_256_CFB) || _name.equals(CIPHER_AES_256_OFB)) {\n            return 32;\n        }\n\n        return 0;\n    }\n\n    @Override\n    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {\n        AESFastEngine engine = new AESFastEngine();\n        StreamBlockCipher cipher;\n\n        if (_name.equals(CIPHER_AES_128_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_AES_192_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_AES_256_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_AES_128_OFB)) {\n            cipher = new OFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_AES_192_OFB)) {\n            cipher = new OFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_AES_256_OFB)) {\n            cipher = new OFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else {\n            throw new InvalidAlgorithmParameterException(_name);\n        }\n\n        return cipher;\n    }\n\n    @Override\n    public int getIVLength() {\n        return 16;\n    }\n\n    @Override\n    protected SecretKey getKey() {\n        return new SecretKeySpec(_ssKey.getEncoded(), \"AES\");\n    }\n\n    @Override\n    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n\n    @Override\n    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/BlowFishCrypt.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport org.bouncycastle.crypto.StreamBlockCipher;\nimport org.bouncycastle.crypto.engines.BlowfishEngine;\nimport org.bouncycastle.crypto.modes.CFBBlockCipher;\n\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.io.ByteArrayOutputStream;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Blow fish cipher implementation\n */\npublic class BlowFishCrypt extends CryptBase {\n\n    public final static String CIPHER_BLOWFISH_CFB = \"bf-cfb\";\n\n    public static Map<String, String> getCiphers() {\n        Map<String, String> ciphers = new HashMap<>();\n        ciphers.put(CIPHER_BLOWFISH_CFB, BlowFishCrypt.class.getName());\n\n        return ciphers;\n    }\n\n    public BlowFishCrypt(String name, String password) {\n        super(name, password);\n    }\n\n    @Override\n    public int getKeyLength() {\n        return 16;\n    }\n\n    @Override\n    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {\n        BlowfishEngine engine = new BlowfishEngine();\n        StreamBlockCipher cipher;\n\n        if (_name.equals(CIPHER_BLOWFISH_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else {\n            throw new InvalidAlgorithmParameterException(_name);\n        }\n\n        return cipher;\n    }\n\n    @Override\n    public int getIVLength() {\n        return 8;\n    }\n\n    @Override\n    protected SecretKey getKey() {\n        return new SecretKeySpec(_ssKey.getEncoded(), \"AES\");\n    }\n\n    @Override\n    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n\n    @Override\n    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/CamelliaCrypt.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport org.bouncycastle.crypto.StreamBlockCipher;\nimport org.bouncycastle.crypto.engines.CamelliaEngine;\nimport org.bouncycastle.crypto.modes.CFBBlockCipher;\n\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.io.ByteArrayOutputStream;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Camellia cipher implementation\n */\npublic class CamelliaCrypt extends CryptBase {\n\n    public final static String CIPHER_CAMELLIA_128_CFB = \"camellia-128-cfb\";\n    public final static String CIPHER_CAMELLIA_192_CFB = \"camellia-192-cfb\";\n    public final static String CIPHER_CAMELLIA_256_CFB = \"camellia-256-cfb\";\n\n    public static Map<String, String> getCiphers() {\n        Map<String, String> ciphers = new HashMap<>();\n        ciphers.put(CIPHER_CAMELLIA_128_CFB, CamelliaCrypt.class.getName());\n        ciphers.put(CIPHER_CAMELLIA_192_CFB, CamelliaCrypt.class.getName());\n        ciphers.put(CIPHER_CAMELLIA_256_CFB, CamelliaCrypt.class.getName());\n\n        return ciphers;\n    }\n\n    public CamelliaCrypt(String name, String password) {\n        super(name, password);\n    }\n\n    @Override\n    public int getKeyLength() {\n        if(_name.equals(CIPHER_CAMELLIA_128_CFB)) {\n            return 16;\n        }\n        else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {\n            return 24;\n        }\n        else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {\n            return 32;\n        }\n\n        return 0;\n    }\n\n    @Override\n    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {\n        CamelliaEngine engine = new CamelliaEngine();\n        StreamBlockCipher cipher;\n\n        if (_name.equals(CIPHER_CAMELLIA_128_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else {\n            throw new InvalidAlgorithmParameterException(_name);\n        }\n\n        return cipher;\n    }\n\n    @Override\n    public int getIVLength() {\n        return 16;\n    }\n\n    @Override\n    protected SecretKey getKey() {\n        return new SecretKeySpec(_ssKey.getEncoded(), \"AES\");\n    }\n\n    @Override\n    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n\n    @Override\n    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/CryptBase.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport com.stfl.misc.Util;\nimport org.bouncycastle.crypto.StreamBlockCipher;\nimport org.bouncycastle.crypto.params.KeyParameter;\nimport org.bouncycastle.crypto.params.ParametersWithIV;\n\nimport javax.crypto.SecretKey;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.logging.Logger;\n\n/**\n * Crypt base class implementation\n */\npublic abstract class CryptBase implements ICrypt {\n\n    protected abstract StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException;\n    protected abstract SecretKey getKey();\n    protected abstract void _encrypt(byte[] data, ByteArrayOutputStream stream);\n    protected abstract void _decrypt(byte[] data, ByteArrayOutputStream stream);\n\n    protected final String _name;\n    protected final SecretKey _key;\n    protected final ShadowSocksKey _ssKey;\n    protected final int _ivLength;\n    protected final int _keyLength;\n    protected boolean _encryptIVSet;\n    protected boolean _decryptIVSet;\n    protected byte[] _encryptIV;\n    protected byte[] _decryptIV;\n    protected final Lock encLock = new ReentrantLock();\n    protected final Lock decLock = new ReentrantLock();\n    protected StreamBlockCipher encCipher;\n    protected StreamBlockCipher decCipher;\n    private Logger logger = Logger.getLogger(CryptBase.class.getName());\n\n    public CryptBase(String name, String password) {\n        _name = name.toLowerCase();\n        _ivLength = getIVLength();\n        _keyLength = getKeyLength();\n        _ssKey = new ShadowSocksKey(password, _keyLength);\n        _key = getKey();\n    }\n\n    protected void setIV(byte[] iv, boolean isEncrypt)\n    {\n        if (_ivLength == 0) {\n            return;\n        }\n\n        if (isEncrypt)\n        {\n            _encryptIV = new byte[_ivLength];\n            System.arraycopy(iv, 0, _encryptIV, 0, _ivLength);\n            try {\n                encCipher = getCipher(isEncrypt);\n                ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _encryptIV);\n                encCipher.init(isEncrypt, parameterIV);\n            } catch (InvalidAlgorithmParameterException e) {\n                logger.info(e.toString());\n            }\n        }\n        else\n        {\n            _decryptIV = new byte[_ivLength];\n            System.arraycopy(iv, 0, _decryptIV, 0, _ivLength);\n            try {\n                decCipher = getCipher(isEncrypt);\n                ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _decryptIV);\n                decCipher.init(isEncrypt, parameterIV);\n            } catch (InvalidAlgorithmParameterException e) {\n                logger.info(e.toString());\n            }\n        }\n    }\n\n    @Override\n    public void encrypt(byte[] data, ByteArrayOutputStream stream) {\n        synchronized (encLock) {\n            stream.reset();\n            if (!_encryptIVSet) {\n                _encryptIVSet = true;\n                byte[] iv = Util.randomBytes(_ivLength);\n                setIV(iv, true);\n                try {\n                    stream.write(iv);\n                } catch (IOException e) {\n                    logger.info(e.toString());\n                }\n\n            }\n\n            _encrypt(data, stream);\n        }\n    }\n\n    @Override\n    public void encrypt(byte[] data, int length, ByteArrayOutputStream stream) {\n        byte[] d = new byte[length];\n        System.arraycopy(data, 0, d, 0, length);\n        encrypt(d, stream);\n    }\n\n    @Override\n    public void decrypt(byte[] data, ByteArrayOutputStream stream) {\n        byte[] temp;\n\n        synchronized (decLock) {\n            stream.reset();\n            if (!_decryptIVSet) {\n                _decryptIVSet = true;\n                setIV(data, false);\n                temp = new byte[data.length - _ivLength];\n                System.arraycopy(data, _ivLength, temp, 0, data.length - _ivLength);\n            } else {\n                temp = data;\n            }\n\n            _decrypt(temp, stream);\n        }\n    }\n\n    @Override\n    public void decrypt(byte[] data, int length, ByteArrayOutputStream stream) {\n        byte[] d = new byte[length];\n        System.arraycopy(data, 0, d, 0, length);\n        decrypt(d, stream);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/CryptFactory.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport com.stfl.misc.Reflection;\nimport java.util.*;\nimport java.util.logging.Logger;\n\n/**\n * Crypt factory\n */\npublic class CryptFactory {\n    private static final Map<String, String> crypts = new HashMap<String, String>() {{\n        putAll(AesCrypt.getCiphers());\n        putAll(CamelliaCrypt.getCiphers());\n        putAll(BlowFishCrypt.getCiphers());\n        putAll(SeedCrypt.getCiphers());\n        // TODO: other crypts\n    }};\n    private static Logger logger = Logger.getLogger(CryptFactory.class.getName());\n\n    public static boolean isCipherExisted(String name) {\n        return (crypts.get(name) != null);\n    }\n\n    public static ICrypt get(String name, String password) {\n        try {\n            Object obj = Reflection.get(crypts.get(name), String.class, name, String.class, password);\n            return (ICrypt)obj;\n\n        } catch (Exception e) {\n            logger.info(com.stfl.misc.Util.getErrorMessage(e));\n        }\n\n        return null;\n    }\n\n    public static List<String> getSupportedCiphers() {\n        List sortedKeys = new ArrayList<>(crypts.keySet());\n        Collections.sort(sortedKeys);\n        return sortedKeys;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/ICrypt.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport java.io.ByteArrayOutputStream;\nimport java.util.Map;\n\n/**\n * Interface of crypt\n */\npublic interface ICrypt {\n    void encrypt(byte[] data, ByteArrayOutputStream stream);\n    void encrypt(byte[] data, int length, ByteArrayOutputStream stream);\n    void decrypt(byte[] data, ByteArrayOutputStream stream);\n    void decrypt(byte[] data, int length, ByteArrayOutputStream stream);\n    int getIVLength();\n    int getKeyLength();\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/SeedCrypt.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport org.bouncycastle.crypto.StreamBlockCipher;\nimport org.bouncycastle.crypto.engines.SEEDEngine;\nimport org.bouncycastle.crypto.modes.CFBBlockCipher;\n\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.io.ByteArrayOutputStream;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Seed cipher implementation\n */\npublic class SeedCrypt extends CryptBase {\n\n    public final static String CIPHER_SEED_CFB = \"seed-cfb\";\n\n    public static Map<String, String> getCiphers() {\n        Map<String, String> ciphers = new HashMap<>();\n        ciphers.put(CIPHER_SEED_CFB, SeedCrypt.class.getName());\n\n        return ciphers;\n    }\n\n    public SeedCrypt(String name, String password) {\n        super(name, password);\n    }\n\n    @Override\n    public int getKeyLength() {\n        return 16;\n    }\n\n    @Override\n    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {\n        SEEDEngine engine = new SEEDEngine();\n        StreamBlockCipher cipher;\n\n        if (_name.equals(CIPHER_SEED_CFB)) {\n            cipher = new CFBBlockCipher(engine, getIVLength() * 8);\n        }\n        else {\n            throw new InvalidAlgorithmParameterException(_name);\n        }\n\n        return cipher;\n    }\n\n    @Override\n    public int getIVLength() {\n        return 16;\n    }\n\n    @Override\n    protected SecretKey getKey() {\n        return new SecretKeySpec(_ssKey.getEncoded(), \"AES\");\n    }\n\n    @Override\n    protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n\n    @Override\n    protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {\n        int noBytesProcessed;\n        byte[] buffer = new byte[data.length];\n\n        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);\n        stream.write(buffer, 0, noBytesProcessed);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ss/ShadowSocksKey.java",
    "content": "/*\n * Copyright (c) 2015, Blake\n * All Rights Reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. The name of the author may not be used to endorse or promote\n * products derived from this software without specific prior\n * written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\npackage com.stfl.ss;\n\nimport com.stfl.misc.Util;\n\nimport javax.crypto.SecretKey;\nimport java.io.UnsupportedEncodingException;\nimport java.security.MessageDigest;\nimport java.util.logging.Logger;\n\n/**\n * Shadowsocks key generator\n */\npublic class ShadowSocksKey implements SecretKey {\n\n    private Logger logger = Logger.getLogger(ShadowSocksKey.class.getName());\n    private final static int KEY_LENGTH = 32;\n    private byte[] _key;\n    private int _length;\n\n    public ShadowSocksKey(String password) {\n        _length = KEY_LENGTH;\n        _key = init(password);\n    }\n\n    public ShadowSocksKey(String password, int length) {\n        // TODO: Invalid key length\n        _length = length;\n        _key = init(password);\n    }\n\n    private byte[] init(String password) {\n        MessageDigest md = null;\n        byte[] keys = new byte[KEY_LENGTH];\n        byte[] temp = null;\n        byte[] hash = null;\n        byte[] passwordBytes = null;\n        int i = 0;\n\n        try {\n            md = MessageDigest.getInstance(\"MD5\");\n            passwordBytes = password.getBytes(\"UTF-8\");\n        }\n        catch (UnsupportedEncodingException e) {\n            logger.info(\"ShadowSocksKey: Unsupported string encoding\");\n        }\n        catch (Exception e) {\n            logger.info(Util.getErrorMessage(e));\n            return null;\n        }\n\n        while (i < keys.length) {\n            if (i == 0) {\n                hash = md.digest(passwordBytes);\n                temp = new byte[passwordBytes.length+hash.length];\n            }\n            else {\n                System.arraycopy(hash, 0, temp, 0, hash.length);\n                System.arraycopy(passwordBytes, 0, temp, hash.length, passwordBytes.length);\n                hash = md.digest(temp);\n            }\n            System.arraycopy(hash, 0, keys, i, hash.length);\n            i += hash.length;\n        }\n\n        if (_length != KEY_LENGTH) {\n            byte[] keysl = new byte[_length];\n            System.arraycopy(keys, 0, keysl, 0, _length);\n            return keysl;\n        }\n        return keys;\n    }\n\n    @Override\n    public String getAlgorithm() {\n        return \"shadowsocks\";\n    }\n\n    @Override\n    public String getFormat() {\n        return \"RAW\";\n    }\n\n    @Override\n    public byte[] getEncoded() {\n        return _key;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ui/LogLayoutController.java",
    "content": "package com.stfl.ui;\n\nimport com.stfl.misc.Log;\nimport javafx.fxml.FXML;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.TextArea;\nimport javafx.stage.Stage;\n\npublic class LogLayoutController {\n    @FXML\n    public TextArea txtLog;\n    @FXML\n    private Button btnClose;\n    @FXML\n    private Button btnClear;\n\n    private Stage stage;\n\n    @FXML\n    private void initialize() {\n        TextAreaLogHandler handler = new TextAreaLogHandler();\n        handler.setTextArea(txtLog);\n        Log.addHandler(handler);\n    }\n\n    @FXML\n    private void handleClear() {\n        txtLog.clear();\n    }\n\n    @FXML\n    private void handleClose() {\n        stage.hide();\n    }\n\n    public void setStage(Stage stage) {\n        this.stage = stage;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ui/MainLayoutController.java",
    "content": "package com.stfl.ui;\n\nimport com.stfl.Constant;\nimport com.stfl.MainGui;\nimport com.stfl.misc.Config;\nimport com.stfl.misc.UTF8Control;\nimport com.stfl.misc.Util;\nimport com.stfl.network.IServer;\nimport com.stfl.network.NioLocalServer;\nimport com.stfl.network.proxy.IProxy;\nimport com.stfl.network.proxy.ProxyFactory;\nimport com.stfl.ss.CryptFactory;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.Scene;\nimport javafx.scene.control.*;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.Pane;\nimport javafx.stage.Stage;\n\nimport java.io.IOException;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.util.ResourceBundle;\nimport java.util.logging.Logger;\n\n\npublic class MainLayoutController {\n    @FXML\n    private TextField txtServerIP;\n    @FXML\n    private TextField txtServerPort;\n    @FXML\n    private ComboBox cboCipher;\n    @FXML\n    private TextField txtPassword;\n    @FXML\n    private TextField txtLocalPort;\n    @FXML\n    private ComboBox cboProxyType;\n    @FXML\n    private Button btnStart;\n    @FXML\n    private Button btnStop;\n    @FXML\n    private Button btnLog;\n    @FXML\n    private Button btnClose;\n\n    private Logger logger = Logger.getLogger(MainLayoutController.class.getName());\n    private MainGui gui;\n    private IServer server;\n    private Stage logStage;\n    private Config config;\n\n    @FXML\n    private void initialize() {\n        // set cipher options\n        ObservableList<String> ciphers = FXCollections.observableArrayList();\n        ciphers.addAll(CryptFactory.getSupportedCiphers());\n        cboCipher.setItems(ciphers);\n\n        // set proxy options\n        ObservableList<IProxy.TYPE> proxyTypes = FXCollections.observableArrayList();\n        proxyTypes.addAll(ProxyFactory.getSupportedProxyTypes());\n        cboProxyType.setItems(proxyTypes);\n\n        // prepare configuration\n        config = new Config();\n        config.loadFromJson(Util.getFileContent(Constant.CONF_FILE));\n        txtServerIP.setText(config.getRemoteIpAddress());\n        txtServerPort.setText(String.valueOf(config.getRemotePort()));\n        txtLocalPort.setText(String.valueOf(config.getLocalPort()));\n        txtPassword.setText(config.getPassword());\n        cboCipher.setValue(config.getMethod());\n        cboProxyType.setValue(config.getProxyType());\n\n        // prepare log window\n        Stage stage = new Stage();\n        try {\n            FXMLLoader logLayoutLoader = new FXMLLoader(MainGui.class.getResource(\"/resources/ui/LogLayout.fxml\"));\n            logLayoutLoader.setResources(ResourceBundle.getBundle(\"resources.bundle.ui\", Constant.LOCALE, new UTF8Control()));\n            Pane logLayout = logLayoutLoader.load();\n            Scene logScene = new Scene(logLayout);\n            stage.setTitle(\"Log\");\n            stage.setScene(logScene);\n            stage.setResizable(false);\n            stage.getIcons().add(new Image(MainGui.class.getResource(\"/resources/image/icon.png\").toString()));\n\n            LogLayoutController controller = logLayoutLoader.getController();\n            controller.setStage(stage);\n            logStage = stage;\n        } catch (IOException e) {\n            logger.warning(\"Unable to load ICON: \" + e.toString());\n        }\n\n        btnStop.setDisable(true);\n    }\n\n    @FXML\n    private void handleStart() {\n        boolean isValidated = false;\n        do {\n            if (!txtServerIP.getText().matches(\"[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}\")) {\n                showAlert(Constant.PROG_NAME, \"Invalid IP address\", Alert.AlertType.ERROR);\n                break;\n            }\n            String ip = txtServerIP.getText();\n            if (!txtServerPort.getText().matches(\"[0-9]+\")) {\n                showAlert(Constant.PROG_NAME, \"Invalid Port\", Alert.AlertType.ERROR);\n                break;\n            }\n            int port = Integer.parseInt(txtServerPort.getText());\n\n            String method = (String) cboCipher.getValue();\n            if (txtPassword.getText().length() == 0) {\n                showAlert(Constant.PROG_NAME, \"Please specified password\", Alert.AlertType.ERROR);\n                break;\n            }\n            String password = txtPassword.getText();\n            IProxy.TYPE type = (IProxy.TYPE) cboProxyType.getValue();\n            if (!txtLocalPort.getText().matches(\"[0-9]+\")) {\n                showAlert(Constant.PROG_NAME, \"Invalid Port\", Alert.AlertType.ERROR);\n                break;\n            }\n            int localPort = Integer.parseInt(txtLocalPort.getText());\n\n            // create config\n            config.setRemoteIpAddress(ip);\n            config.setRemotePort(port);\n            config.setLocalIpAddress(\"127.0.0.1\");\n            config.setLocalPort(localPort);\n            config.setMethod(method);\n            config.setPassword(password);\n            config.setProxyType(type);\n            Util.saveFile(Constant.CONF_FILE, config.saveToJson());\n\n            isValidated = true;\n        } while (false);\n\n        if (!isValidated)\n            return;\n\n        // start start\n        try {\n            server = new NioLocalServer(config);\n            Thread t = new Thread(server);\n            t.setDaemon(true);\n            t.start();\n            String message = String.format(\"(Connected) Server %s:%d\", config.getRemoteIpAddress(), config.getRemotePort());\n            gui.setTooltip(message);\n            gui.showNotification(message);\n        } catch (IOException | InvalidAlgorithmParameterException e) {\n            logger.warning(\"Unable to start server: \" + e.toString());\n        }\n        btnStop.setDisable(false);\n        btnStart.setDisable(true);\n    }\n\n    @FXML\n    private void handleStop() {\n        if (server != null) {\n            server.close();\n            String message = String.format(\"(Disconnected) Server %s:%d\", config.getRemoteIpAddress(), config.getRemotePort());\n            gui.showNotification(message);\n            gui.setTooltip(\"Not Connected\");\n        }\n\n        btnStop.setDisable(true);\n        btnStart.setDisable(false);\n    }\n\n    @FXML\n    private void handleLog() {\n        logStage.show();\n    }\n\n    @FXML\n    private void handleClose() {\n        gui.hide();\n    }\n\n    public void setMainGui(MainGui gui) {\n        this.gui = gui;\n    }\n\n    public void closeServer() {\n        handleStop();\n    }\n\n    private boolean validationInput(String pattern, String text) {\n        return false;\n    }\n\n    private void showAlert(String title, String message, Alert.AlertType type) {\n        Alert a = new Alert(type);\n        a.setTitle(title);\n        a.setHeaderText(type.name());\n        a.setResizable(false);\n        a.setContentText(message);\n        a.showAndWait();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/stfl/ui/TextAreaLogHandler.java",
    "content": "package com.stfl.ui;\n\nimport javafx.application.Platform;\nimport javafx.scene.control.TextArea;\n\nimport java.util.logging.LogRecord;\nimport java.util.logging.StreamHandler;\n\npublic class TextAreaLogHandler extends StreamHandler {\n    TextArea textArea = null;\n\n    public void setTextArea(TextArea textArea) {\n        this.textArea = textArea;\n    }\n\n    @Override\n    public void publish(LogRecord record) {\n        final LogRecord lg = record;\n        super.publish(record);\n        flush();\n\n        if (textArea != null) {\n            Platform.runLater(new Runnable() {\n                @Override\n                public void run() {\n                    // limited log size to 64k\n                    if (textArea.getText().length() > 65535) {\n                        textArea.clear();\n                    }\n                    textArea.appendText(getFormatter().format(lg));\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/MANIFEST.MF",
    "content": "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",
    "content": "START=Start\nSTOP=Stop\nLOG=Log\nCLOSE=Close\nCLOSE1=Close\nCLEAR=Clear\nSERVER_IP=Server IP:\nSERVER_PORT=Server Port:\nCIPHER=Cipher:\nPASSWORD=Password:\nLOCAL_PORT=Local Port:\nPROXY_TYPE=Proxy Type:\n"
  },
  {
    "path": "src/main/resources/resources/bundle/ui_zh_CN.properties",
    "content": "START=启动\nSTOP=停止\nLOG=日志\nCLOSE=关闭\nCLEAR=清除\nSERVER_IP=服务器 IP:\nSERVER_PORT=服务器端口:\nCIPHER=加密方式:\nPASSWORD=密码:\nLOCAL_PORT=本地端口:\nPROXY_TYPE=代理服务器类型:\n"
  },
  {
    "path": "src/main/resources/resources/bundle/ui_zh_TW.properties",
    "content": "START=啟動\nSTOP=停止\nLOG=日誌\nCLOSE=關閉\nCLEAR=清除\nSERVER_IP=伺服器 IP:\nSERVER_PORT=伺服器 Port:\nCIPHER=加密方式:\nPASSWORD=密碼:\nLOCAL_PORT=本地 Port:\nPROXY_TYPE=代理伺服器類型:\n"
  },
  {
    "path": "src/main/resources/resources/ui/LogLayout.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.lang.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.layout.*?>\n\n<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\">\n   <children>\n      <TextArea fx:id=\"txtLog\" editable=\"false\" prefHeight=\"369.0\" prefWidth=\"600.0\" />\n      <HBox alignment=\"TOP_RIGHT\" prefHeight=\"0.0\" prefWidth=\"600.0\">\n         <children>\n            <Button fx:id=\"btnClear\" mnemonicParsing=\"false\" onAction=\"#handleClear\" text=\"%CLEAR\" />\n            <Button fx:id=\"btnClose\" mnemonicParsing=\"false\" onAction=\"#handleClose\" text=\"%CLOSE\" />\n         </children>\n      </HBox>\n   </children>\n</VBox>\n"
  },
  {
    "path": "src/main/resources/resources/ui/MainLayout.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<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\">\n   <children>\n      <Label layoutX=\"15.0\" layoutY=\"31.0\" text=\"%SERVER_IP\" />\n      <Label layoutX=\"15.0\" layoutY=\"61.0\" text=\"%SERVER_PORT\" />\n      <Label layoutX=\"15.0\" layoutY=\"90.0\" text=\"%CIPHER\" />\n      <Label layoutX=\"15.0\" layoutY=\"118.0\" text=\"%PASSWORD\" />\n      <Label layoutX=\"15.0\" layoutY=\"178.0\" text=\"%LOCAL_PORT\" />\n      <Label layoutX=\"14.0\" layoutY=\"148.0\" text=\"%PROXY_TYPE\" />\n      <TextField fx:id=\"txtServerIP\" layoutX=\"128.0\" layoutY=\"27.0\" prefHeight=\"23.0\" prefWidth=\"150.0\" />\n      <TextField fx:id=\"txtServerPort\" layoutX=\"128.0\" layoutY=\"57.0\" prefHeight=\"23.0\" prefWidth=\"150.0\" />\n      <TextField fx:id=\"txtLocalPort\" layoutX=\"128.0\" layoutY=\"174.0\" prefHeight=\"23.0\" prefWidth=\"150.0\" />\n      <ComboBox fx:id=\"cboCipher\" layoutX=\"128.0\" layoutY=\"86.0\" prefWidth=\"150.0\" />\n      <ComboBox fx:id=\"cboProxyType\" layoutX=\"128.0\" layoutY=\"144.0\" prefWidth=\"150.0\" />\n      <Button fx:id=\"btnStart\" layoutX=\"13.0\" layoutY=\"220.0\" mnemonicParsing=\"false\" onAction=\"#handleStart\" text=\"%START\" />\n      <Button fx:id=\"btnStop\" layoutX=\"56.0\" layoutY=\"220.0\" mnemonicParsing=\"false\" onAction=\"#handleStop\" text=\"%STOP\" />\n      <Button fx:id=\"btnClose\" layoutX=\"232.0\" layoutY=\"220.0\" mnemonicParsing=\"false\" onAction=\"#handleClose\" text=\"%CLOSE\" />\n      <PasswordField fx:id=\"txtPassword\" layoutX=\"128.0\" layoutY=\"114.0\" prefHeight=\"23.0\" prefWidth=\"150.0\" />\n      <Button fx:id=\"btnLog\" layoutX=\"185.0\" layoutY=\"220.0\" mnemonicParsing=\"false\" onAction=\"#handleLog\" text=\"%LOG\" />\n   </children>\n</Pane>\n"
  }
]